1次元オセロの問題

こんにちは。
今日はひさしぶりにプログラミングの問題です。題材はオセロゲーム。
オセロゲームといえば8x8の2次元の盤面に白と黒の石をおいていくゲームですが、今日はそれを1次元にしたようなお題です。

お題

f:id:naosim:20200502060925p:plain 8x1の盤を使ってオセロをする。
盤面に白と黒の石が置かれている(上図)。
これに白の石を置く。
指定した座標に白の石を置いたときに、オセロのルールにしたがって黒の石がひっくり返った後の盤面を返す関数を作りたい。
下記のプログラムの// TODO 実装の部分にプログラムを下記、この関数を完成させてください。(TODO以外の部分に関数やクラスを作っても良いです。)

<!DOCTYPE html>
<meta charset="utf-8" />
<script>
/**
 * 1次元オセロの盤面に白の石を置く
 * 
 * - 盤は配列で表現する
 *   - 白の石は'白'
 *   - 黒の石は'黒'
 *   - 空の場所はnull
 * 
 * [null, '黒', '白', '黒', null, '黒', '黒', '白']
 * @param {number} diskIndex 白の石を置く場所
 * @param {string[]} board 盤。各要素には'白'または'黒'またはnullが入っている。nullは空の意味。
 * @returns {string[]} 白の石を置いた時に、オセロのルールにしたがって黒が白にひっくり返った盤面を返す。置けない場所に置かれた場合は盤面をそのまま返す。
 */
function putLightDisk(diskIndex, board) {
  // TODO 実装
  return null; // ここにひっくり返った後の配列を返す
}


// 実行
// ひっくり返えるパタン 右
var result1 = putLightDisk(0, [null, '黒', '白', '黒', null, '黒', '黒', '白']);
console.log(result1);// ['白', '白', '白', '黒', null, '黒', '黒', '白']
// ひっくり返えるパタン 左右
var result2 = putLightDisk(4, [null, '黒', '白', '黒', null, '黒', '黒', '白']);
console.log(result2);// [null, '黒', '白', '白', '白', '白', '白', '白']
// ひっくり返らないパタン
var result3 = putLightDisk(0, [null, null, '白', '白', '黒', '黒', '黒', null]);
console.log(result3);// [null, null, '白', '白', '黒', '黒', '黒', null];
</script>

答えは下の方  
 
 
 
 
 
 
 
 

 
 
 
 
 
 
 
 
 

 
 
 
 
 
 
 
 
 

 
 
 
 
 
 
 
 
 

 
 
 
 
 
 
 
 
 

 
 
 
 
 
 
 
 
 

答え

<!DOCTYPE html>
<meta charset="utf-8" />
<script>
/**
 * 1次元オセロの盤面に白の石を置く
 * 
 * - 盤は配列で表現する
 *   - 白の石は'白'
 *   - 黒の石は'黒'
 *   - 空の場所はnull
 * 
 * [null, '黒', '白', '黒', null, '黒', '黒', '白']
 * @param {number} diskIndex 白の石を置く場所
 * @param {string[]} board 盤。各要素には'白'または'黒'またはnullが入っている。nullは空の意味。
 * @returns {string[]} 白の石を置いた時に、オセロのルールにしたがって黒が白にひっくり返った盤面を返す。置けない場所に置かれた場合は盤面をそのまま返す。
 */
function putLightDisk(diskIndex, board) {
  // 事前条件
  if(!isInArrayRange(diskIndex, board)) {
    throw new Error('盤面外の座標が指定されています');
  }

  // 指定した場所に既に石があるならそのまま返す
  if(board[diskIndex] !== null) {
    return board;
  }

  var currentDiskColor = '白';
  var otherDiskColor = '黒';

  // 右方向にある自分の色の石、または、空の位置を取得する
  var limitIndex = getCurrentColorOrEmptyIndexToRight(diskIndex, otherDiskColor, board);
  if(limitIndex !== undefined && board[limitIndex] == currentDiskColor) {
    board[diskIndex] = currentDiskColor; // 石を置く
    flipWithRange(diskIndex, limitIndex, currentDiskColor, board);// diskIndexからlimitIndexまでをひっくり返す
  }

  // 左方向にある自分の色の石、または、空の位置を取得する
  var limitIndex = getCurrentColorOrEmptyIndexToLeft(diskIndex, otherDiskColor, board);
  if(limitIndex !== undefined && board[limitIndex] == currentDiskColor) {
    board[diskIndex] = currentDiskColor; // 石を置く
    flipWithRange(diskIndex, limitIndex, currentDiskColor, board);// diskIndexからlimitIndexまでをひっくり返す
  }
  return board;
}

/**
 * iが配列の範囲内かどうか
 * @param {number} i 
 * @param {Array} ary 
 */
function isInArrayRange(i, ary) {
  return i >= 0 && i < ary.length;
}

/**
 * 右方向にある自分の色の石、または、空の位置を取得する
 * @param {number} diskIndex 
 * @param {string} otherDiskColor '白'または'黒'
 * @param {string[]} board 
 * @returns {number} 自分の色の石、または、空の位置。見つからない場合はundefined。
 */
function getCurrentColorOrEmptyIndexToRight(
  diskIndex,
  otherDiskColor,
  board
) {
  var i = diskIndex + 1;
  while(true) {
    if(!isInArrayRange(i, board)) {
      return undefined;
    }
    if(board[i] != otherDiskColor) {
      return i;
    }
    i++;
  }
}

/**
 * 左方向にある自分の色の石、または、空の位置を取得する
 * @param {number} diskIndex 
 * @param {string} otherDiskColor '白'または'黒'
 * @param {string[]} board 
 * @returns {number} 自分の色の石、または、空の位置。見つからない場合はundefined。
 */
function getCurrentColorOrEmptyIndexToLeft(
  diskIndex,
  otherDiskColor,
  board
) {
  var i = diskIndex - 1;
  while(true) {// returnで
    if(!isInArrayRange(i, board)) {
      return undefined;
    }
    if(board[i] != otherDiskColor) {
      return i;
    }
    i--;
  }
}

/**
 * 範囲内にある石をひっくり返す
 * 
 * @param {number} fromIndexExcluded 開始位置。境界値含まず。
 * @param {number} toIndexExcluded  終了位置。境界値含まず。開始位置より小さい値でもok
 * @param {string} currentDiskColor '白'または'黒'
 * @param {string[]} board 
 */
function flipWithRange(fromIndexExcluded, toIndexExcluded, currentDiskColor, board) {
  var min = Math.min(fromIndexExcluded, toIndexExcluded);
  var max = Math.max(fromIndexExcluded, toIndexExcluded);
  for(var i = min + 1; i < max; i++) {
    board[i] = currentDiskColor;
  }
}


// 実行
// ひっくり返えるパタン 右
var result1 = putLightDisk(0, [null, '黒', '白', '黒', null, '黒', '黒', '白']);
console.log(result1);// ['白', '白', '白', '黒', null, '黒', '黒', '白']
// ひっくり返えるパタン 左右
var result2 = putLightDisk(4, [null, '黒', '白', '黒', null, '黒', '黒', '白']);
console.log(result2);// [null, '黒', '白', '白', '白', '白', '白', '白']
// ひっくり返らないパタン
var result3 = putLightDisk(0, [null, null, '白', '白', '黒', '黒', '黒', null]);
console.log(result3);// [null, null, '白', '白', '黒', '黒', '黒', null];
</script>