We Blog Weblog

リバーシ【CPU戦】

Web

2025年5月25日

みなさんこんにちは。
ケミストのWeb担当みやのです。

今回は「リバーシ」CPU戦です。

CPUは黒(先攻)プレイヤーは白(後攻)です。

プレイヤーが黒(先攻)バージョンはこちら

CPUが白の時より強いような気がします。オセロって後攻の方が有利らしいですがさあレッツトライ!

黒の番です

 

HTMLとCSS

HTMLとCSSは前回Doです。

javascript

前回のスクリプトに、CPUの行動を組み込みます。

function cpuMove() {
  const cells = Array.from(document.querySelectorAll(".cell"));
  const validMoves = cells.filter(cell =>
    !cell.classList.contains("black") &&
    !cell.classList.contains("white") &&
    isValidMoveForPlayer(cell, "black")
  );
  if (validMoves.length > 0) {
    const randomIndex = Math.floor(Math.random() * validMoves.length);
    const chosenCell = validMoves[randomIndex];
    chosenCell.classList.add("black");
    flipDiscs(chosenCell);
    if (checkGameEnd()) return;
    currentPlayer = "white";
    status.textContent = "白の番です";
  } else {
    status.innerHTML = "黒が置けないためパスします<br>白の番です";
    currentPlayer = "white";
  }
}

このページではCPUを黒(先攻)とするために、ボードを初期化する関数にCPUの行動を追加します。

function initializeBoard() {
  board.innerHTML = "";
  for (let i = 0; i < 8; i++) {
    for (let j = 0; j < 8; j++) {
      const cell = document.createElement("div");
      cell.classList.add("cell");
      cell.dataset.row = i;
      cell.dataset.col = j;
      if ((i === 3 && j === 3) || (i === 4 && j === 4)) {
        cell.classList.add("white");
      } else if ((i === 3 && j === 4) || (i === 4 && j === 3)) {
        cell.classList.add("black");
      }
      cell.addEventListener("click", () => handleCellClick(cell));
      board.appendChild(cell);
    }
  }
  currentPlayer = "black";
  status.textContent = "黒の番です";
  setTimeout(() => cpuMove(), 1000);
}

前回の「セルをクリックした時の関数」を少し書き替えて「ターンを進める関数」を追加します。

function handleCellClick(cell) {
  if (cell.classList.contains("black") || cell.classList.contains("white")) {
    return;
  }
  if (!isValidMoveForPlayer(cell, currentPlayer)) {
    return;
  }
  cell.classList.add(currentPlayer);
  flipDiscs(cell);
  if (checkGameEnd()) return;
  advanceTurn();
}

function advanceTurn() {
  const opponent = currentPlayer === "black" ? "white" : "black";
  if (hasValidMove(opponent)) {
    currentPlayer = opponent;
    status.textContent = `${currentPlayer === "black" ? "黒" : "白"}の番です`;
    if (currentPlayer === "black") {
      setTimeout(() => cpuMove(), 300);
    }
  } else if (hasValidMove(currentPlayer)) {
    status.innerHTML = `${opponent === "black" ? "黒" : "白"}が置けないためパスします<br>${currentPlayer === "black" ? "黒" : "白"}の番です`;
  } else {
    checkGameEnd();
  }
}

このページでは、CPUの遅延を300ミリ秒にしてみました。


たまに「黒も白も全部置き終わったのにゲームが終了しない」というケースがあったので、前回の勝敗判定とは別に「ゲーム終了を判定する関数」を追加します。

function checkGameEnd() {
  const cells = Array.from(document.querySelectorAll(".cell"));
  const isBoardFull = cells.every(cell => 
    cell.classList.contains("black") || cell.classList.contains("white")
  );
  const blackHasMove = hasValidMove("black");
  const whiteHasMove = hasValidMove("white");
  if (isBoardFull || (!blackHasMove && !whiteHasMove)) {
    const winner = determineWinner();
    if (winner === "black") {
      status.textContent = "敗北...";
    } else if (winner === "white") {
      status.textContent = "勝利ッ!";
      fireworks();
    } else {
      status.textContent = "引き分け";
    }
    return true;
  }
  return false;
}

今後の課題

・64手目が白で、白を置ける場所がなかった場合、パスの処理が適切に行われずゲームの進行が止まる場合がある

・難易度をもう少し上げたい(例えば優先的に角を取りにくるとか、こっちに角を取らせないよう立ち回るとか)

・先攻(黒)後攻(白)を毎回選べるようにしたい

この記事を書いた人
みやの
Web・DTP担当

Contact Us

ご意見、ご相談、料金のお見積もりなど、お気軽にお問い合わせください。

お問い合わせはこちら

TOP