リバーシ【CPU戦】
Web
2025年5月25日

みなさんこんにちは。
ケミストのWeb担当みやのです。
今回は「リバーシ」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, "white")
);
if (validMoves.length > 0) {
const randomIndex = Math.floor(Math.random() * validMoves.length);
const chosenCell = validMoves[randomIndex];
chosenCell.classList.add("white");
flipDiscs(chosenCell);
if (checkGameEnd()) return;
currentPlayer = "black";
status.textContent = "黒の番です";
} else if (hasValidMove("black")) {
status.innerHTML = "白が置けないためパスします<br>黒の番です";
} else {
checkGameEnd();
}
}
前回の「セルをクリックした時の関数」を少し書き替えて「ターンを進める関数」を追加します。
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 === "white") {
setTimeout(() => cpuMove(), 1000);
}
} else if (hasValidMove(currentPlayer)) {
status.innerHTML = `${opponent === "black" ? "黒" : "白"}が置けないためパスします<br>${currentPlayer === "black" ? "黒" : "白"}の番です`;
} else {
checkGameEnd();
}
}
CPUのターンに1000ミリ秒の遅延を設定してさもCPUが最適な手を考えているかのように見せかけていますが、実際はただランダムに置いているなのでもうちょっと短くしてもいいかもしれません。
たまに「黒も白も全部置き終わったのにゲームが終了しない」というケースがあったので、前回の勝敗判定とは別に「ゲーム終了を判定する関数」を追加します。
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 = "勝利ッ!";
fireworks();
} else if (winner === "white") {
status.textContent = "敗北...";
} else {
status.textContent = "引き分け";
}
return true;
}
return false;
}
今後の課題
・難易度をもう少し上げたい(例えば優先的に角を取りにくるとか、こっちに角を取らせないよう立ち回るとか)
・先攻(黒)後攻(白)を毎回選べるようにしたい