このページではソースコードの一部分を簡単な解説をしています。
完成品で遊ぶにはコチラから
完成品のソースコードはgithubにあります
三目並べゲーム概要
先手・後手をプレイヤーが選びコンピューターと対戦できます。相手より先に三つ同じ色を縦・横・斜めのいずれかに並べれば勝ちのゲームです。このゲームをウィキペディアで調べたところ、先手・後手ともに最善を尽くすと、必ず引き分けとなるのですが今回作ったプログラムではコンピューター側は結構負けます。
勝敗表機能を実装しました。ブログ記事は↓にあります。
ミニマックス法を実装した三目並べのブログ記事は↓にあります。
JavaScriptを使ってマスがクリックされた時の処理をする
let winflag = true;//勝敗が決まったらfalseにしてゲーム終了
let field = document.querySelectorAll(".field");
let board = Array(9);//三つ揃ったか判定のために使う
//マスがクリックされた時の処理
function player() {
for (let i = 0; i < field.length; i++) {
field[i].onclick = () => {
if (board[i] == undefined) {
field[i].style.backgroundColor = "pink";
board[i] = 1;
turn_action();
if (winflag) {
com();
}
}
}
}
}プレイヤーがマスをクリックするとplayer関数が実行され、押されたマスの場所はfield変数によって判断をし、変数boardの配列に押された場所をプレイヤーは1、コンピューターは2の数値を追加していき、勝敗の判定やコンピューターの指し手の判定に使います。続いてturn_action関数へ処理は移ります。
let count = 0;//偶数ならコンピューター奇数ならプレイヤーの手番
function turn_action() {
if (count % 2 == 0) {
turn.textContent = "コンピューターの番です"
} else {
turn.textContent = "あなたの番です"
}
Judgment();
count++;
}turn_action関数は手番の管理を行っています。count変数を使い偶数ならコンピューター、奇数ならプレイヤーの手番と判定をして表示させています。続いてJudgment関数へ処理は移ります。
勝敗の判定処理
Judgment関数はマスに3つ揃っているか判定する関数です。
const win_patterns = [
[0, 1, 2],
[3, 4, 5],
[6, 7, 8],
[0, 3, 6],
[1, 4, 7],
[2, 5, 8],
[0, 4, 8],
[2, 4, 6]
];
function Judgment() {
for (let i = 0; i < win_patterns.length; i++) {
let patterns = win_patterns[i];
let square1 = (board[patterns[0]]);
let square2 = (board[patterns[1]]);
let square3 = (board[patterns[2]]);
completed = square1 && square1 == square2 && square2 == square3 && square3 == square1;
if (completed) {
if (count % 2 == 0) {
judgtextcreate(0);
return;
} else {
judgtextcreate(1);
return;
}
}
}
if (board.includes(undefined) == false && winflag) {
judgtextcreate(2)
return;
}
}win_patterns配列を使い縦・横・斜めにプレイヤーなら1、コンピューターなら2の数値が3つ並んでいるか調べ、並んでいた場合はjudgtextcreate関数でどちらが勝ったかを表示します。
コンピューターの指し手の処理
com関数はコンピューターの指し手を決める関数です。
//コンピュータ側の応手
function com() {
game.classList.add('pointer-none');
//コンピューターが後手の場合の一手目
if (board[4] == 1 && count == 1) {
drawingpiece(0);
return;
} else if (count == 1) {
drawingpiece(4)
return;
}
//2手目以降
if (count > 2) {
for (let j = 2; j > 0; j--) {
for (let i = 0; i < win_patterns.length; i++) {
let patterns = win_patterns[i];
let square1 = (board[patterns[0]]);
let square2 = (board[patterns[1]]);
let square3 = (board[patterns[2]]);
let x = square1 == undefined && square2 == j && square3 == j
let y = square1 == j && square2 == undefined && square3 == j
let z = square1 == j && square2 == j && square3 == undefined
if (x) {
drawingpiece(patterns[0]);
return;
}
else if (y) {
drawingpiece(patterns[1]);
return;
}
else if (z) {
drawingpiece(patterns[2]);
return;
}
}
}
}
if (!count % 2 == 0) {
let flag = true;
while (flag) {
let random = Math.floor(Math.random() * board.length);
if (board[random] == undefined) {
drawingpiece(random);
flag = false;
}
}
}
function drawingpiece(place) {
setTimeout(function () {
field[place].style.backgroundColor = "skyblue";
board[place] = 2;
game.classList.remove('pointer-none');
turn_action();
}, 1000);
}
}ウィキペディアで三目並べのことを調べたところ、先手・後手ともに最善を尽くすと、必ず引き分けとなります。しかし後手番の場合下記の図のように先手番が〇に置いた場合、後手番が×以外に置くと後手番の負けが確定してしまいます。
そのためコンピューターが後手番の場合は×を外さないように先手が置いた場所によってどこに置くかをあらかじめ決めておき、com関数の5行目から11行目のようにプログラムしてみました。それ以降の指し手は判定でも使っていたwin_patterns配列を使い、まずコンピューター側に三つ置ける場所があったらそこに置き、無ければ相手が三つ置ける場所を探し阻止するプログラムにしました。
このプログラムのコンピューターが弱い理由
コンピューターが先手番の初手や三つ置ける場所や阻止する場所がない場合はランダムにマスに置くようにプログラムしてあります。その結果、下記の図のような局面をコンピューターが後手番で迎えた場合Aのマスに置くと負けが確定してしまうのですが、指し手をランダムにプログラムしているため、Aのマスにも置いてしまい弱いプログラムになってしまいました。
最後までお読みいただきありがとうございました。
