E – Weighted Tic-Tac-Toe 重み付きの三目並べ の続きです。今回はこれをネタにゲームを作ってみることにします。
乱数を生成して 9 個のマスに数字を表示させます。これを見て先手と後手、勝つことができるのかどちらかを判断して選択してもらいます。あとはコンピュータを相手に三目並べをします。勝敗は Weighted Tic-Tac-Toe の問題と同じです。普通の三目並べで決着がつかない場合は得点で勝敗を決めます。
普通の三目並べは双方が最善を尽くせば必ず引き分けになりましたが、このゲームは違います。マスに書かれている数の総和は奇数なので絶対に引き分けにはなりません。どうです!面白いでしょう!(自画自賛w)
Contents
HTML部分
まずはHTML部分を示します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 |
<!DOCTYPE html> <html lang="ja"> <head> <meta charset="UTF-8"> <title>Weighted Tic-Tac-Toe</title> <meta name = "viewport" content = "width=device-width, initial-scale = 1.0"> <link rel="stylesheet" type="text/css" href="./style.css"> </head> <body> <div id = "container"> <h1>Weighted Tic-Tac-Toe</h1> <p>重み付きの三目並べです。双方が三目並べることができなかった場合はマスの数の合計で勝敗が決まります。引き分けには絶対になりません。</p> <div id = "field"> <div id = "row-0"> <div id = "cell-00" class = "cell"></div> <div id = "cell-01" class = "cell"></div> <div id = "cell-02" class = "cell"></div> </div> <div id = "row-1"> <div id = "cell-10" class = "cell"></div> <div id = "cell-11" class = "cell"></div> <div id = "cell-12" class = "cell"></div> </div> <div id = "row-2"> <div id = "cell-20" class = "cell"></div> <div id = "cell-21" class = "cell"></div> <div id = "cell-22" class = "cell"></div> </div> <div> <div id = "navi"></div> <button id = "game-start">開始</button> </div> <div id = "start-message"> <p>先手または後手を選択してください。</p> <button id = "first">先手</button> <button id = "second">後手</button> </div> <div id = "move-test"> <div id = "move-test-2"> <button id = "move-back">自分の手をひとつ戻す</button> <button id = "show-best">最善手を確認する</button> </div> <button id = "move-test-start">最初から感想戦をする</button> <button id = "move-test-end">感想戦を終了する</button> </div> <div id = "volume"> 音量:<input type="range" id = "volume-range"><span id = "volume-text">0</span> <button id = "volume-test">テスト</button> </div> </div> </div> <script src="./index.js"></script> </body> </html> |
style.css
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 |
body { background-color: #000; color: #fff; } #container { width: 360px; } .cell { background-color: #000; border:4px #fff solid; width: 80px; height: 80px; margin-bottom: 10px; margin-right: 10px; display: inline-block; text-align: center; vertical-align: middle; font-weight: bold; } #game-start, #volume-test, #move-test-start, #move-test-end, #move-back, #show-best { margin-top: 20px; margin-right: 15px; width: 150px; height: 50px; vertical-align: middle; } #start-message, #move-test, #move-test-2 { display: none; } #first, #second { width: 120px; height: 50px; margin-right: 30px; } #volume { margin-top: 20px; } #volume-range { width: 200px; margin-left: 10px; margin-right: 10px; vertical-align: middle; } |
PositionクラスとBestMoveクラスの定義
最初に位置を表現するためのPositionクラスとコンピュータにとっての最善手を表すためのBestMoveクラスを定義します。
index.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
class Position { constructor(row, col) { this.Row = row; this.Col = col; } } class BestMove { constructor(winner, row, col, first, second, moveCount) { this.Winner = winner; // 1 または 2。どちらが勝つのか? this.Row = row; // コンピュータはどこに着手すれば勝てるのか? this.Col = col; // Row と Col は 0 ~ 2。-1 なら候補点なし this.Scores = []; // 先手と後手のスコア this.Scores.push(first); this.Scores.push(second); this.MoveCount = moveCount; // これまでの手数 } } |
グローバル変数・定数
グローバル変数と定数を以下のように定義します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 |
const inf = 100000; // 無限大の定義 let playerNumber = 1; // プレイヤーが先手を選択した場合は プレイヤーが 1、CPUが 2 となる let cpuNumber = 2; // プレイヤーが後手を選択した場合は プレイヤーが 2、CPUが 1 となる let ignoreClick = true; // true のときはユーザーがマスをクリックしてもなにもしない let winningStreak = 0; // 連勝数 let isDiscussing = false; // 感想戦モードかどうか? // DOM要素 const $gameStart = document.getElementById('game-start'); // ゲームスタート用のボタン const $first = document.getElementById('first'); // 「先手」選択用のボタン const $second = document.getElementById('second'); // 「後手」選択用のボタン const $startMessage = document.getElementById('start-message'); // 先手後手選択メッセージの表示 const $navi = document.getElementById('navi'); // ユーザーに対するナビゲーション // DOM要素(感想戦用) const $moveTestStart = document.getElementById('move-test-start'); // 開始ボタン const $moveTestEnd = document.getElementById('move-test-end'); // 終了ボタン const $moveBack = document.getElementById('move-back'); // 自分の手を戻すボタン const $showBest = document.getElementById('show-best'); // 最善手を確認するボタン const $moveTest = document.getElementById('move-test'); // ボタンの表示非表示用 const $moveTest2 = document.getElementById('move-test-2'); // DOM要素(セルの部分) const cells = [ [null, null, null], [null, null, null], [null, null, null], ]; // セルに書かれている数字 let cellNumbers = [ [1, 2, 3], [4, 5, 6], [7, 8, 9], ]; // セルの選択状態 0 なら未選択。1 または 2 ならプレイヤーまたはCPUによって選択されている const selects = [ [0, 0, 0], [0, 0, 0], [0, 0, 0], ]; const moves = []; // これまでの着手 // 効果音 const soundPlayer = new Audio('./sounds/player.mp3'); // プレイヤーの着手音 const soundCpu = new Audio('./sounds/cpu.mp3'); // CPUの着手音 const soundBad = new Audio('./sounds/bad.mp3'); // プレイヤーが不正な場所に着手しようとした const soundWin = new Audio('./sounds/win.mp3'); // プレイヤーの勝ち const soundLose = new Audio('./sounds/lose.mp3'); // プレイヤーの負け let volume = 0.2; // ボリューム |
ページが読み込まれたときの処理
ページが読み込まれたときの処理を示します。ページが読み込まれたらセルに相当するボタン要素を2次元配列 cells に格納します。そしてイベントリスナを追加してボリュームの初期設定をおこないます。
1 2 3 4 5 |
window.onload = () => { getCellElements(); // 後述 addEventListeners(); // 後述 initVolume(); // 後述 } |
getCellElements関数はセルに相当するボタン要素を2次元配列 cells に格納します。
1 2 3 4 5 6 |
function getCellElements(){ for(let row = 0; row < 3; row++){ for(let col = 0; col < 3; col++) cells[row][col] = document.getElementById(`cell-${row}${col}`); } } |
イベントリスナの追加
addEventListeners関数は、マスをクリックしたとき、スタートボタン、先手選択、後手選択のボタンをクリックしたときのイベントリスナを追加します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
function addEventListeners(){ for(let row = 0; row < 3; row++){ for(let col = 0; col < 3; col++){ cells[row][col].addEventListener('click', async() => { if(ignoreClick) return; await onPlayerSelect(row, col); }); } } // 呼び出される関数については後述 $gameStart.addEventListener('click', () => gameStart()); $first.addEventListener('click', () => onSelectFirst()); $second.addEventListener('click', async() => await onSelectSecond()); $moveTestStart?.addEventListener('click', () => moveTestStart()); $moveTestEnd?.addEventListener('click', () => moveTestEnd()); $moveBack?.addEventListener('click', async() => await onMoveBack()); $showBest?.addEventListener('click', () => showBest()); } |
initVolume関数はボリューム調整用のスライダーを初期化します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
function initVolume(){ const $volumeRange = document.getElementById('volume-range'); const $volumeText = document.getElementById('volume-text'); $volumeRange.addEventListener('input', () => { const value = $volumeRange.value; $volumeText.innerText = value; volume = value / 100; setVolume(); }); setVolume(); $volumeText.innerText = volume * 100; $volumeRange.value = volume * 100; function setVolume(){ soundPlayer.volume = volume; soundCpu.volume = volume; soundBad.volume = volume; soundWin.volume = volume; soundLose.volume = volume; } const $volumeTest = document.getElementById('volume-test'); $volumeTest.addEventListener('click', () => soundPlayer.play()); } |
ゲーム開始時の処理
ゲーム開始ボタンをクリックしたらマスに数字を表示してプレイヤーに先手と後手どちらを選択するかを問うメッセージを表示します。
そのまえに2次元配列 selects の全要素を 0 で初期化することで未選択の状態にしています。同時にこれまでの着手の履歴をクリアしています。
次に問題を生成します。マスに表示する数字は乱数で生成します。ただ引き分けにならないようにするために総和は奇数にしなければなりません。とりあえず -1000 ~ 999 の乱数を生成して総和を求め、偶数であった場合は左上のマスの数を 1 増やすことで総和が偶数になることを回避しています。
そのあとナビゲーションの文字列を初期化して、スタートボタンを非表示にしてユーザーに先手または後手の選択を問うメッセージとボタンを表示するなどの処理をしています。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
function gameStart(){ // すべてのセルを未選択の状態にする selects[0] = [0, 0, 0]; selects[1] = [0, 0, 0]; selects[2] = [0, 0, 0]; moves.length = 0; // これまでの着手の履歴もクリア cellNumbers[0] = [createNumber(), createNumber(), createNumber()]; cellNumbers[1] = [createNumber(), createNumber(), createNumber()]; cellNumbers[2] = [createNumber(), createNumber(), createNumber()]; let sum = 0; for(let i = 0; i < 3; i++) sum += cellNumbers[i][0] + cellNumbers[i][1] + cellNumbers[i][2]; // 総和が偶数になっている場合は奇数にする if(sum % 2 == 0) cellNumbers[0][0]++; // セルに配列に格納した値を表示する setCellNumbers(); // ナビゲーションの文字列の初期化、各ボタンの表示と非表示、効果音など $navi.innerText = ''; $gameStart.style.display = 'none'; $moveTest.style.display = 'none'; $startMessage.style.display = 'block'; soundPlayer.play(); } |
createNumber関数はマスに表示される数を生成する関数です。
1 2 3 |
function createNumber(){ return Math.floor(Math.random() * 2000) - 1000; } |
setCellNumbers関数はセルの背景色を元に戻すとともに、createNumber関数が生成して配列に格納した乱数をマスに表示させる処理をおこなっています。
1 2 3 4 5 6 7 8 |
function setCellNumbers(){ for(let row = 0; row < 3; row++){ for(let col = 0; col < 3; col++){ cells[row][col].style.backgroundColor = '#000'; cells[row][col].innerText = cellNumbers[row][col]; } } } |
先手と後手の選択
ゲームが開始されてユーザーが先手または後手を選択したらゲームが開始されます。
先手を選択した場合はただちに、後手を選択した場合はCPUが着手した段階で ignoreClick フラグがクリアされ、プレイヤーはマスをクリックすることで着手できるようになります。
また感想戦モードなら感想戦操作用のボタンを表示させます。
1 2 3 4 5 6 7 8 9 10 11 |
function onSelectFirst(){ soundPlayer.play(); $startMessage.style.display = 'none'; playerNumber = 1; cpuNumber = 2; $navi.innerHTML = 'あなたの手番です。'; ignoreClick = false; if(isDiscussing) $moveTest.style.display = 'block'; // 感想戦モードなら感想戦操作用のボタンを表示する } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
async function onSelectSecond(){ if(isDiscussing) $moveTest.style.display = 'block'; soundPlayer.play(); $startMessage.style.display = 'none'; playerNumber = 2; cpuNumber = 1; $navi.innerText = 'コンピュータが考えています。'; await sleep(1000); // CPUが着手するまで間をもたせるため 2 秒待つ const position = cpuThink(); // コンピュータ側が次の一手を考慮する処理(後述) moves.push(position); // コンピュータ側の着手を履歴に保存 ignoreClick = false; $navi.innerText = 'あなたの手番です。'; } async function sleep(ms){ await new Promise(resolve => setTimeout(resolve, ms)); } |
プレイヤーによる着手
プレイヤーが着手ときはプレイヤーの手番のときに空いているマスをクリックします。すでに着手されているマスをクリックするなど不正な操作をしたときは効果音を鳴らして正しい操作をするようユーザーに伝えます。
正常な着手がされたときは配列内に着手の履歴を保存します。プレイヤーの着手によって勝敗がつくかもしれないのでチェックします。ゲームセットになった場合は後述するcheckGameSet関数内でゲームの結果を表示したあと、新しいゲームを開始するためのスタートボタンを再表示させますが、感想戦モードでゲームセットになった場合はゲーム開始ボタンは再表示させません。ユーザーが感想戦モードを終了させた段階で次のゲーム開始の処理がおこなわれます。
プレイヤーの着手してゲームセットにならなかった場合、手番をコンピュータに移行します。コンピュータ側の着手が終了していないのにプレイヤーが連続して着手できては困るので、コンピュータ側が着手するまでユーザーがマスをクリックしても無反応状態にします。
コンピュータ側の着手が決まったら着手の処理をおこない、これによってゲームセットとなるかもしれないのでチェックの処理をおこないます。コンピュータ側の着手によって勝敗がつかない場合はプレイヤーに手番を移します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 |
async function onPlayerSelect(row, col){ if(selects[row][col] == 0) { // 未着手のマスをクリックした場合 soundPlayer.play(); moves.push(new Position(row, col)); // 着手の履歴を保存する ignoreClick = true; // コンピュータ側の着手が終了するまでマスをクリックしても無反応にする selects[row][col] = playerNumber; // セルの選択状態を更新 cells[row][col].style.backgroundColor = '#f00'; // セルの見た目(背景色)を変更する cells[row][col].style.color = '#fff'; // プレイヤーの着手によって勝敗がつくかもしれないのでチェック // 感想戦モードでゲームセットになった場合はゲーム開始ボタンは再表示させない // (感想戦モード終了時にゲーム開始の処理をおこなう) if(await checkGameSet(selects, !isDiscussing)){ if(!isDiscussing) $gameStart.style.display = 'block'; $moveTest.style.display = 'block'; return; } // プレイヤーの着手によって勝敗がつかない場合はコンピュータ側に手番を移す $navi.innerText = 'コンピュータが考えています。'; await sleep(800); const position = cpuThink(); // コンピュータ側の着手を考える moves.push(position); // コンピュータ側の着手を履歴に保存 // コンピュータ側の着手によって勝敗がつくかもしれないのでチェック if(await checkGameSet(selects, !isDiscussing)){ if(!isDiscussing) $gameStart.style.display = 'block'; $moveTest.style.display = 'block'; return; } // コンピュータ側の着手によって勝敗がつかない場合はプレイヤーに手番を移す ignoreClick = false; $navi.innerText = 'あなたの手番です。'; } else{ // 着手済みのマスをクリックした場合(不正な操作) soundBad.play(); $navi.innerText = '未着手のセルを選択してください。'; } } |
コンピュータ側の着手と最善手
コンピュータ側の着手を考えるアルゴリズムは E – Weighted Tic-Tac-Toe 重み付きの三目並べ と同じです。
コンピュータ側はつねに最善手を選択します。勝つ方法が存在する場合はそれを選択し、ない場合はできるだけ僅差で負ける手を選択します。
またコンピュータが∞ vs 0 で勝つ手段が複数ある場合(すでに 2 がふたつできている場合)、このままだとすぐに三目並べて勝たずに他のところに着手する場合があります。そこですでにリーチができている場合はその場所へ着手してすぐに終わらせるようにしています。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
function cpuThink(){ let move = GetBestMove(selects, cellNumbers); // 最善手を探す const move2 = getRearchPosition(selects); // これがないと勝てるのに別の着手をする場合がある if(move2 != null) move = move2; let row = move.Row; let col = move.Col; selects[row][col] = cpuNumber; cells[row][col].style.backgroundColor = '#00f'; cells[row][col].style.color = '#fff'; soundCpu.play(); return new Position(row, col); } |
最善手の探し方は前の記事:E – Weighted Tic-Tac-Toe 重み付きの三目並べ と似たような再帰呼び出しによる処理で探します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 |
function GetBestMove(selects0, cellNumbers0){ // すでに終局しているか調べる // あとで必要になるので先に点数計算と手数のカウントをする let firstScore = 0; let secondScore = 0; let moveCount = 0; for (let row = 0; row < 3; row++) { for (let col = 0; col < 3; col++) { if (selects0[row][col] == 1){ firstScore += cellNumbers0[row][col]; moveCount++; } if (selects0[row][col] == 2){ secondScore += cellNumbers0[row][col]; moveCount++; } } } // 三目並んでいるならそれが勝者なのでスコアは ∞ : 0 で結果を返す const winner = getWinner0(selects0); // getWinner0関数は後述 if(winner == 1) return new BestMove(winner, -1, -1, inf, 0, moveCount); if(winner == 2) return new BestMove(winner, -1, -1, 0, inf, moveCount); // 終局しているなら点数計算をして結果を返す if(isALLSelected(selects0)){ // isALLSelected関数は後述 if (firstScore > secondScore) return new BestMove(1, -1, -1, firstScore, secondScore, moveCount); else return new BestMove(2, -1, -1, firstScore, secondScore, moveCount); } // 終局していないなら考えられる次の手をすべて列挙する。getNextPositions関数は後述 const positions = getNextPositions(selects0); // positions.length が 奇数なら 1 の手番 const x = positions.length % 2 == 1 ? 1 : 2; const y = positions.length % 2 == 0 ? 1 : 2; const winMoves = []; // 勝つ場合を保存する const loseMoves = []; // 負ける場合を保存する for (let i = 0; i < positions.length; i++) { const row = positions[i].Row; const col = positions[i].Col; selects0[row][col] = x; // 試しに着手 const bestMove = GetBestMove(selects0, cellNumbers0); // 自分自身を再帰呼び出し if (bestMove.Winner == x) winMoves.push(new BestMove(x, row, col, bestMove.Scores[0], bestMove.Scores[1], moveCount)); if (bestMove.Winner == y) loseMoves.push(new BestMove(y, row, col, bestMove.Scores[0], bestMove.Scores[1], moveCount)); selects0[row][col] = 0; // 他の手も試すので着手は取り消す } // 勝てる場合はもっとも大きく勝てるものを取得する if(winMoves.length > 0){ let ret = null; let maxDeff = 0; for(let i=0; i < winMoves.length; i++){ const deff = Math.abs(winMoves[i].Scores[0] - winMoves[i].Scores[1]); if(maxDeff < deff){ ret = winMoves[i]; maxDeff = deff; } } return ret; } else { // 勝てない場合はもっとも少なく負けるものを取得する let ret = null; let minDeff = 10000000; for(let i=0; i<loseMoves.length; i++){ const deff = Math.abs(loseMoves[i].Scores[0] - loseMoves[i].Scores[1]); if(minDeff > deff){ ret = loseMoves[i]; minDeff = deff; } } return ret; } } |
getWinner0関数は、縦横斜めのいずれかで 3 つそろっているかを調べます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
function getWinner0(selects0){ for (let row = 0; row < 3; row++) { if (selects0[row][0] != 0 && selects0[row][0] == selects0[row][1] && selects0[row][0] == selects0[row][2]) return selects0[row][0]; } for (let col = 0; col < 3; col++) { if (selects0[0][col] != 0 && selects0[0][col] == selects0[1][col] && selects0[0][col] == selects0[2][col]) return selects0[0][col]; } if (selects0[0][0] != 0 && selects0[0][0] == selects0[1][1] && selects0[0][0] == selects0[2][2]) return selects0[0][0]; if (selects0[0][2] != 0 && selects0[0][2] == selects0[1][1] && selects0[0][2] == selects0[2][0]) return selects0[0][2]; return 0; } |
isALLSelected関数はすべてのマスが埋まっているかどうかを調べます。
1 2 3 4 5 6 7 8 9 |
function isALLSelected(selects0) { for (let row = 0; row < 3; row++) { for (let col = 0; col < 3; col++) { if (selects0[row][col] == 0) return false; } } return true; } |
勝敗がついていない状態で与えられた盤面から次の手の候補を列挙する処理を示します。
1 2 3 4 5 6 7 8 9 10 |
function getNextPositions(selects0){ let rets = []; for (let row = 0; row < 3; row++){ for (let col = 0; col < 3; col++){ if (selects0[row][col] == 0) // 未着手のマスを探して配列に格納する rets.push(new Position(row, col)); } } return rets; } |
コンピュータ側がすでにリーチが掛かっている部分を探す処理を示します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
function getRearchPosition(selects0){ for (let row = 0; row < 3; row++) { if (selects0[row][0] == cpuNumber && selects0[row][1] == cpuNumber && selects0[row][2] == 0) return new Position(row, 2); if (selects0[row][0] == cpuNumber && selects0[row][1] == 0 && selects0[row][2] == cpuNumber) return new Position(row, 1); if (selects0[row][0] == 0 && selects0[row][1] == cpuNumber && selects0[row][2] == cpuNumber) return new Position(row, 0); } for (let col = 0; col < 3; col++) { if (selects0[0][col] == cpuNumber && selects0[1][col] == cpuNumber && selects0[2][col] == 0) return new Position(2, col); if (selects0[0][col] == cpuNumber && selects0[1][col] == 0 && selects0[2][col] == cpuNumber) return new Position(1, col); if (selects0[0][col] == 0 && selects0[1][col] == cpuNumber && selects0[2][col] == cpuNumber) return new Position(0, col); } if (selects0[0][0] == 0 && selects0[1][1] == cpuNumber && selects0[2][2] == cpuNumber) return new Position(0, 0); if (selects0[0][0] == cpuNumber && selects0[1][1] == 0 && selects0[2][2] == cpuNumber) return new Position(1, 1); if (selects0[0][0] == cpuNumber && selects0[1][1] == cpuNumber && selects0[2][2] == 0) return new Position(2, 2); if (selects0[0][2] == 0 && selects0[1][1] == cpuNumber && selects0[2][0] == cpuNumber) return new Position(0, 2); if (selects0[0][2] == cpuNumber && selects0[1][1] == 0 && selects0[2][0] == cpuNumber) return new Position(1, 1); if (selects0[0][2] == cpuNumber && selects0[1][1] == cpuNumber && selects0[2][0] == 0) return new Position(2, 0); return null; } |
終了判定
checkGameSet関数はプレイヤーまたはコンピュータ側の着手で勝敗がついてしまったかどうかを調べます。そして勝敗を表示します。また勝った場合は連勝数も更新し負けた場合はリセットします。ただし感想戦モードで勝敗がついた場合は連勝数の更新処理はおこないません。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 |
async function checkGameSet(selects0, checkWinningStreak){ let isPlayerWin = false; let isCpuWin = false; let playerScore = 0; let cpuScore = 0; const winner = getWinner0(selects0); if(winner == playerNumber){ isPlayerWin = true; playerScore = inf; } if(winner == cpuNumber){ isCpuWin = true; cpuScore = inf; } if(!isPlayerWin && !isCpuWin && isALLSelected(selects0)){ for (let row = 0; row < 3; row++) { for (let col = 0; col < 3; col++) { if (selects0[row][col] == playerNumber) playerScore += cellNumbers[row][col]; if (selects0[row][col] == cpuNumber) cpuScore += cellNumbers[row][col]; } } if (playerScore > cpuScore) isPlayerWin = true; else isCpuWin = true; } if(!isPlayerWin && !isCpuWin) return false; await sleep(800); if(isPlayerWin){ let winningStreakText = ''; if(checkWinningStreak){ winningStreak++; winningStreakText = `${winningStreak} 連勝中`; } if(playerScore == inf) playerScore = '∞'; $navi.innerHTML = `あなた ${playerScore} : CPU ${cpuScore} で<br>あなたの勝ちです。${winningStreakText}`; soundWin.play(); return true; } if(isCpuWin){ if(checkWinningStreak) winningStreak = 0; if(cpuScore == inf) cpuScore = '∞'; $navi.innerHTML = `あなた ${playerScore} : CPU ${cpuScore} で<br>あなたの負けです`; soundLose.play(); return true; } } |