JavaScriptで753ゲームをつくります。753ゲームは紙と鉛筆を使って2人で遊びます。紙に書いた棒を消していきます。
ルールは以下のとおりです。
ひとつでも複数でもかまわない
1度に2段以上にまたがってはならない
すでに消されている所を消すことはできない
1度に消す領域は連続していること
パスはできない
一般的には最後の1個を消した方が負けなのですが、ここでは最後の1個を消したほうが勝ちとします。
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 |
<!DOCTYPE html> <html lang="ja"> <head> <meta charset="UTF-8"> <title>鳩でもわかる753ゲーム</title> <meta name = "viewport" content = "width=device-width, initial-scale = 1.0"> <link rel = "stylesheet" href = "./style.css" type = "text/css" media = "all"> </head> <body> <div id = "container"> <div id = "cells7" class = "cells"></div> <div id = "cells5" class = "cells"></div> <div id = "cells3" class = "cells"></div> <button id = "ok" class = "button" onclick="clickOk()">OK</button> <button id = "cancel" class = "button" onclick="clickCancel()">キャンセル</button> <button id = "retry" class = "button" onclick="retry()">もう一回挑戦する</button> <p id = "result">同じ段の連続しているひよこを選択してください。最後の1個を選択したら勝ちです。</p> </div> <script src="./app.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 |
#container{ margin-top: 50px; width: 360px; } .cell { width: 36px; height: 36px; border: 1px solid #000; margin-right: 12px; } .cells { display: flex; margin-bottom: 24px; justify-content: center; } .button { width: 130px; height: 30px; } #retry { display: none; } |
JavaScript部分
主なグローバル変数を示します。
1 2 3 4 5 6 7 8 9 10 11 12 13 |
let $cells7 = document.getElementById('cells7'); let $cells5 = document.getElementById('cells5'); let $cells3 = document.getElementById('cells3'); let $ok = document.getElementById('ok'); let $cancel = document.getElementById('cancel'); let $retry = document.getElementById('retry'); let $result = document.getElementById('result'); let soundSelected = new Audio('./selected.mp3'); let soundBadSelect = new Audio('./bad-select.mp3'); let soundWin = new Audio('./win.mp3'); let soundLose = new Audio('./lose.mp3'); |
初期化
セルを生成してクリック時の処理ができるようにイベントリスナを追加します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
window.onload = () => { let arr1 = [7, 5, 3]; let arr2 = ['cells7-', 'cells5-', 'cells3-']; let arr3 = [$cells7, $cells5, $cells3]; for(let i = 0; i < 3; i++){ for(let k = 0; k < arr1[i]; k++){ let $cell = document.createElement('div'); $cell.id = arr2[i] + k; $cell.className = 'cell'; $cell.addEventListener('click', cellClicked); // cellClicked関数は後述 arr3[i].appendChild($cell); } } init(); } |
未選択のセル(ゲーム開始時はすべて未選択)にイメージを描画します。イメージは7個の行、5個の行、3個の行で変えてあります。
1 2 3 4 5 6 7 8 9 10 11 12 |
function init(){ let arr1 = [7, 5, 3]; let arr2 = ['cells7-', 'cells5-', 'cells3-']; for(let i = 0; i < 3; i++){ for(let k = 0; k < arr1[i]; k++){ let $cell = document.getElementById(arr2[i] + k); if($cell.childElementCount == 0) addImage($cell, arr1[i].toString()); } } } |
イメージを追加する関数を示します。
1 2 3 4 5 6 7 8 9 10 11 12 13 |
function addImage($target, type){ let image = new Image(); if(type == '7') image.src = './7.png'; if(type == '5') image.src = './5.png'; if(type == '3') image.src = './3.png'; image.width = 36; image.height = 36; if($target.childElementCount == 0) $target.appendChild(image); } |
セルをクリックしたときの処理
セルをクリックしたときの処理を示します。
自分の番であれば複数選択をすることができるのですが、同じ行(上から何段目か?)のものでなければならない、横に連続していなければならないというルールがあります。
そこでまず引数の要素が何行何列目にあるそのセルなのかを調べて結果を返すGetCellPosition関数を定義します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
function GetCellPosition($cell){ let arr = $cell.id.replace('cells', '').split('-'); let position = { row: 0, col: 0, }; position.col = Number(arr[1]); if(arr[0] == 7) position.row = 0; if(arr[0] == 5) position.row = 1; if(arr[0] == 3) position.row = 2; return position; } |
クリックで一つ目のセルが選択されたらその行をグローバル変数に格納します。以降はその行以外のものが選択されたときは不正な選択として扱います。
また選択された列もグローバル変数の配列に格納します。こうすることで本当に選ぶことができるものかを判定しています。新たに選択された列の前後の番号が配列に格納されているなら選択可能です。正しく選択された場合はセルのなかに描画されているイメージをクリアします。
もし不適切な選択をした場合は警告音を鳴らしてメッセージを表示します。また選択されたものがキャンセルされるかもしれません。そのときにもとに戻せるように選択された要素を配列に格納しておきます。
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 |
let clickedArray = []; // 正常に選択されたセル let selectedRow = 0; // 正常に選択されたセルの行番号 let selectedCols = []; // 正常に選択されたセルの列番号 function cellClicked(ev){ let pos = GetCellPosition(ev.currentTarget); if(clickedArray.length == 0) // 最初に選択されたセルの行を記録する selectedRow = pos.row; else { // 2番目以降に選択されたセルの列番号は選択可能であるか調べる // 前後の番号が配列に格納されているなら選択可能 if(selectedRow != pos.row || selectedCols.filter(col => col == pos.col + 1 || col == pos.col - 1).length == 0){ $result.innerText = '選択できるのは同じ段の連続しているものだけです'; soundBadSelect.currentTime = 0; soundBadSelect.play(); return; } } // すでに選択されているものは選択できない if(ev.currentTarget.childElementCount == 0){ $result.innerText = '一度選択したものは選択できません'; soundBadSelect.currentTime = 0; soundBadSelect.play(); return; } // 正しく選択されたのであればイメージをクリア、その要素と列番号を配列に格納する ev.currentTarget.children[0].remove(); clickedArray.push(ev.currentTarget); selectedCols.push(pos.col); $result.innerText = ''; soundSelected.currentTime = 0; soundSelected.play(); } |
選択の取り消しと確定
[キャンセル]がクリックされたときの処理を示します。
この場合はクリアしたセルのイメージを復元します。どのイメージを復元するかはselectedRowに格納されている値からわかります。
1 2 3 4 5 6 7 |
function clickCancel(){ let arr = ['7', '5', '3']; clickedArray.forEach(cell => addImage(cell, arr[selectedRow])); clickedArray = []; selectedCols = []; } |
[OK]がクリックされたときの処理を示します。
最低ひとつは選択していないといけないので確認します。確認できたらコンピュータ側の着手をします。
1 2 3 4 5 6 7 8 9 10 11 12 13 |
function clickOk(){ if(clickedArray.length == 0){ $result.innerText = '必ず1つは選択してください'; soundBadSelect.currentTime = 0; soundBadSelect.play(); return; } clickedArray = []; selectedCols = []; think(); } |
コンピュータ側の着手
コンピュータ側の着手は今回はランダムに選択するものとします。まず隣り合っている未選択のセルのグループを取得します。あとは乱数でどのグループから?選択の開始はグループの先頭から何番目か?何個選択するのか?を決めます。ただしグループがひとつしかない場合はそれを丸ごと選択して勝利することにします。
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 getUnselectedCellsGroups(){ let unselectedCells = []; let unselectedCellsGroups = []; let arr1 = [7, 5, 3]; let arr2 = ['cells7-', 'cells5-', 'cells3-']; for(let i = 0; i < 3; i++){ for(let k = 0; k < arr1[i]; k++){ let $cell = document.getElementById(arr2[i] + k); if($cell.childElementCount > 0) unselectedCells.push($cell); else{ if(unselectedCells.length > 0) unselectedCellsGroups.push(unselectedCells); unselectedCells = []; } } if(unselectedCells.length > 0) unselectedCellsGroups.push(unselectedCells); unselectedCells = []; } return unselectedCellsGroups; } |
コンピュータ側に着手させる処理を示します。乱数を生成してどのグループのどの部分から何個選択するのかを決めて選択する処理をおこなっています。ただしグループがひとつしかない場合はそれを丸ごと選択して勝つようにしています。
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 |
function think(){ let groups = getUnselectedCellsGroups(); if(groups.length == 0){ onPlayerWin(); return; } let r = Math.floor(Math.random() * groups.length); let group = groups[r]; let start = Math.floor(Math.random() * group.length); let count = Math.floor(Math.random() * (group.length - start)) + 1; if(groups.length == 1) count = group.length; for(let i = 0; i < count; i++) group[start + i].children[0].remove(); groups = getUnselectedCellsGroups(); if(groups.length == 0){ onPlayerLose(); return; } $result.innerText = 'あなたの番です'; } |
勝敗決着後の処理
勝敗を表示して効果音を鳴らします。また再挑戦用のボタンを表示するとともにそれ以外のボタンを非表示にします。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
function onPlayerWin(){ $result.innerText = 'あなたの勝ちです'; soundWin.currentTime = 0; soundWin.play(); $ok.style.display = 'none'; $cancel.style.display = 'none'; $retry.style.display = 'block'; } function onPlayerLose(){ $result.innerText = 'あなたの負けです'; soundLose.currentTime = 0; soundLose.play(); $ok.style.display = 'none'; $cancel.style.display = 'none'; $retry.style.display = 'block'; } |
再挑戦用のボタンを非表示するとともに操作用のボタンを表示します。そしてinit関数を呼び出してセルにイメージをセットします。
1 2 3 4 5 6 7 8 9 |
function retry(){ $ok.style.display = 'inline-block'; $cancel.style.display = 'inline-block'; $retry.style.display = 'none'; clickedArray = []; selectedCols = []; init(); } |