山傘のプログラミング勉強日記

プログラミングに関する日記とどうでもよい雑記からなるブログです。

[JavaScript] オセロ

オセロ

今回は JavaScript でオセロを作成しました。

See the Pen RwrrmEa by ヤマカサ (@yamakasa33) on CodePen.

デザイン

labs.timedia.co.jp

主にcssを参考にしました。

アルゴリズム

オセロを実装するには、石を置けるかどうかや、ひっくり返されるマスのリストが大変だと思いますが、愚直に実装します。

IDを付与

各マスにIDを割り振って、管理します。左上から、0, 1, 2, ... と割り振ると、 x 座標に対応するものは、

 x = ID \bmod N

となり、 y 座標は、

 y = \dfrac{ID}{N}

となります。 N は縦と横のマスの数です。 x, y から ID を求めるには、

 ID = x + Ny

となります。

石を置く または 石をひっくり返す
function put(x, y, color) {
  const td = document.getElementById('cell-' + getId(x, y));
  cells[y][x] = color;
  if (color == BLACK) {
    if (td.classList.contains('white')) {
      td.classList.remove('white');
      numWhite--;
    }
    td.classList.add('black');
    numBlack++;
  } else if (color == WHITE) {
    if (td.classList.contains('black')) {
      td.classList.remove('black');
      numBlack--;
    }
    td.classList.add('white');
    numWhite++;
  }
}
マスに石を置けるかどうか

石を置ける条件は、石を置いたときにひっくり返されるマスが存在することなので、あるマスに注目した時、8方向を探索し、同色の意思で挟むことができるかを調べれば良いです。

function canPut(x, y, color) {
  if (cells[y][x] != 0) return false;
  for (let i = 0; i < 8; i++) {
    let flag = false; // 変えるマスが存在するかどうか
    for (let j = 1; j < N; j++) {
      const nx = x + j * dx[i];
      const ny = y + j * dy[i];
      if (nx >= N || nx < 0 || ny >= N || ny < 0) break;
      if (cells[ny][nx] == 0) break;
      if (cells[ny][nx] == color) {
        if (flag) return true;
        break;
      } else {
        flag = true;
      }
    }
  }
  return false;
}
ひっくり返されるマスのリスト

上記の関数を拡張する感じで実装します。

function getReverseCellsId(x, y, color) {
  const ret = [];
  for (let i = 0; i < 8; i++) {
    const tmp = [];
    let flag = false;
    for (let j = 1; j < N; j++) {
      const nx = x + j * dx[i];
      const ny = y + j * dy[i];
      if (nx >= N || nx < 0 || ny >= N || ny < 0) break;
      if (cells[ny][nx] == 0) break;
      if (cells[ny][nx] != color) {
        tmp.push(getId(nx, ny));
        flag = true;
      } else {
        if (flag) {
          Array.prototype.push.apply(ret, tmp);
          break;
        }
        break;
      }
    }
  }
  return ret;
}
コンピュータのルーチン

評価値を計算して、最も価値の高い手を打ちます。ここの部分は改良の余地があります。

// コンピューターの思考
function think() {
  if(myTurn) return;
  let hightScore = -1000;
  let px = -1;
  let py = -1;
  for (let x = 0; x < N; x++) {
    for (let y = 0; y < N; y++) {
      if (!canPut(x, y, enColor)) continue;
      const tmpScore = getEval(x, y, enColor);
      if (hightScore < tmpScore) {
        hightScore = tmpScore;
        px = x;
        py = y;
      }
    }
  }
  if (px == -1) {
    myTurn = true;
    return;
  }
  const aryId = getReverseCellsId(px, py, enColor);
  console.log(aryId);
  put(px, py, enColor);
  aryId.forEach( e=> {
    const x = e % N;
    const y = Math.floor(e / N);
    put(x, y, enColor);
  });
  myTurn = true;
  update();
}

// 評価値
function getEval(x, y, color) {
  const aryId = getReverseCellsId(x, y, color);
  let v = 0;
  aryId.forEach( e => {
    const x = e % N;
    const y = Math.floor(e / N);
    v += WEIGHT[y][x];
  });
  return v;
}