##はじめに
初めまして、こんにちは!
私は現在32歳で未経験からエンジニアへの転職を目指して学習しています。
トレーニングとしてJavaScriptを使い、⭕️❌ゲームを作成し勝敗判定ができるコードを書いてみました。
その過程を記事にまとめていきます。
##要件定義
今回はJavaScriptを中心に書き、HTMLとCSSでは簡単な枠組みを用意します。
・ブラウザを開くと、5X5のマスが表示される。
・いずれかのマスをクリックすると、初めに⭕️、次に❌と交互に反映される。
・縦横斜めの何かで一列揃うと勝敗が判定され、アラートが表示される
この内容を実現できる内容を目指します。
##実装
###1 レイアウト
初めにマスを用意します。
今回は5x5のマスを用意します。
<div id="mas">
<table id="board">
<!-- 行1 -->
<tr>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
</tr>
<!-- 行2 -->
<tr>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
</tr>
<!-- 行3 -->
<tr>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
</tr>
<!-- 行4 -->
<tr>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
</tr>
<!-- 行5 -->
<tr>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
</tr>
</table>
</div>
そして装飾も簡単に行います。
<style type="text/css">
#mas {
padding: 50px;
}
table {
border: 1px solid #999;
max-width: 100%;
background: blue;
text-align: center;
}
td {
border: 1px solid #fff;
width: 80px;
height: 80px;
font-size: 35px;
color: #fff;
}
</style>
###2 マスをクリックした時のイベント実装
次にマスをクリックした時に⭕️や❌という文字を表示させるようにします。
//行列の変数
const lineNumber = 5;
let board = []
//配列を変数を使い列の数だけ作成
const newArray = new Array(lineNumber).fill(0);
for (let j = 0; j < newArray.length; j++) {
board[j] = newArray.map((num) => {
return 0;
});
}
この後に実装していく勝敗判定についてですが、配列を用います。
boardという空の配列を用意します。
ここに配列を複数格納し、多次元配列とします。
それぞれの要素に数字の0を入れるためにfor文とmapメソッドを使い実装しました。
結果として初期状態としては、このようになっています。
board = [
[0,0,0,0,0],
[0,0,0,0,0],
[0,0,0,0,0],
[0,0,0,0,0],
[0,0,0,0,0],
];
この0が1になると⭕️、2になると❌と表示され、
それぞれの行で⭕️が5個格納されれば勝利という判定方法を実行することを目指します。
//1回のみのクリックを許容する変数
const oneTime = { once: true };
//td要素の取得
let td = document.getElementsByTagName("td");
//クリックカウント
let count = 0;
//tdの要素1つ1つにクリックイベントを起こせるようにする
for (let index = 0; index < td.length; index++) {
td[index].addEventListener('click', (event) => {
let action = event.target;
//カウントを増やしながら1回事に交互に繰り返すことを書く
let text = null;
if (count % 2 === 0) {
text = "○"
//マルだったら配列に1を入れる
board[Math.floor(index / lineNumber)][index % lineNumber] = 1;
} else {
text = "✖︎";
//バツだったら配列に2を入れる
board[Math.floor(index / lineNumber)][index % lineNumber] = 2;
}
count++;
action.textContent = text;
}, oneTime)
}
for分を用いて列ごとに配列を取得します。
その中でaddEventListenerでそれぞれのtdタグ要素にクリックイベントを起こせるようにします。
マスのそれぞれの場所はboard[ ][ ]と記すことで指定することができます。
例えばboard[0][0]は左上のマスになります。
board[Math.floor(index / lineNumber)][index % lineNumber]
ここでクリックした場所がどのマスかを特定して、⭕️の時は1が入る、❌の時には2が入るようにしています。
そしてクリックしたらカウントが増え、textContentに⭕️もしくは❌が反映されるようになります。
###3 横判定の実装
ここまでで準備は整いました。
ここからは判定するコードを書いていきます。
//勝敗結果のアラート関数
function alert_Maru() {
setTimeout(() => {
alert('○の勝ちです');
}, 300);
};
function alert_Batsu() {
setTimeout(() => {
alert('✖︎の勝ちです');
}, 300);
}
}, false);
まずは勝敗判定の関数を定義しておきます。
//横列のマルとバツの勝ち判定関数
function besideWin() {
for (let i = 0; i < lineNumber; i++) {
//縦を固定しその配列内で1と2の要素の配列をそれぞれarr1とarr2へ格納しその長さで判定
const arr1 = board[i].filter((one) => {
return one === 1;
});
const arr2 = board[i].filter(two => two === 2);
if (arr1.length === lineNumber) {
alert_Maru();
};
if (arr2.length === lineNumber) {
alert_Batsu();
};
}
}
横列の判定は一番簡単です。
それぞれの配列の要素の数をカウントすればいいです。
配列内の要素が1もしくは2になればarr1、arr2に格納されるようにし、
その長さが列の長さと一致すれば勝敗判定ができるという仕組みです。
###4 縦列判定の実装
続いて縦列の判定です。これは少し考える必要があります。
基本的には横列の判定と同じ考え方をしますが、縦で見ると配列を跨ぐので、
全く同じにはできません。
//縦列を判定する関数
function verticalWWin() {
for (let i = 0; i < lineNumber; i++) {
//現在の配列から縦でみる配列を生成する。
const oneCount = board.map((one) => {
return one[i]
});
const twoCount = board.map((two) => {
return two[i];
});
//縦で見れる配列で各要素の1と2を探しそれぞれ変数へ格納。
const arr1 = oneCount.filter((one) => {
return one === 1;
});
const arr2 = twoCount.filter((two) => {
return two === 2;
});
if (arr1.length === lineNumber) {
alert_Maru();
};
if (arr2.length === lineNumber) {
alert_Batsu();
}
};
横列判定と比較すると1つ手順を追加しています。
oneCountとtwoCountという変数を用意します。
mapメソッドを使うことで縦を判断できる配列を作成し、その配列内の1と2の数をカウントして
勝敗判定を実装しています。
### 5 斜め列の実装
最後の判定に斜めの列の判定を実装します。
//斜めでの勝利判定
function xWin() {
//斜め判定のループ 添字とその逆で斜めを判定
const diagonal = board.map((one, index) => {
return one[index] || one[(lineNumber - 1) - index];
})
const arr1 = diagonal.filter((one) => {
return one === 1;
});
const arr2 = diagonal.filter((two) => {
return two === 2;
})
if (arr1.length === lineNumber) {
alert_Maru();
};
if (arr2.length === lineNumber) {
alert_Batsu();
}
};
斜めは縦列判定の応用です。
diagonalという配列で斜めのマスをカウントしてもらわないといけません。
左上から右斜め下への場合は、
board[0][0],board[1][1],board[2][2],board[3][3],board[4][4]
右上から左斜め下への場合は、
board[0][4],board[1][3],board[2][2],board[3][1],board[4][0]
このマスで配列を作ることができれば、その後は今までと同じ方法で判定できます。
今回はmapメソッドのcallback関数の第二引数を使い、配列のindexを取得できます。
このindexを利用して、斜めに格納されている値を取り出した配列を作りました。
const diagonal = board.map((one, index) => {
return one[index] || one[(lineNumber - 1) - index];
})
6 仕上げ
これまで実装した判定の関数をマスをクリックした時のイベントに追加して、
動作を確認します。
ここまで書いた全体のコードを記載します。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>マルバツゲーム 5 x 5</title>
<style type="text/css">
#mas {
padding: 50px;
}
table {
border: 1px solid #999;
max-width: 100%;
background: blue;
text-align: center;
}
td {
border: 1px solid #fff;
width: 80px;
height: 80px;
font-size: 35px;
color: #fff;
}
</style>
</head>
<body>
<div id="mas">
<table id="board">
<!-- 行1 -->
<tr>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
</tr>
<!-- 行2 -->
<tr>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
</tr>
<!-- 行3 -->
<tr>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
</tr>
<!-- 行4 -->
<tr>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
</tr>
<!-- 行5 -->
<tr>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
</tr>
</table>
</div>
<script>
"use strict";
//行列の変数
const lineNumber = 5;
let board = []
//配列を変数を使い列の数だけ作成
const newArray = new Array(lineNumber).fill(0);
for (let j = 0; j < newArray.length; j++) {
board[j] = newArray.map((num) => {
return 0;
});
}
//勝敗結果のアラート関数
function alert_Maru() {
setTimeout(() => {
alert('○の勝ちです');
}, 300);
};
function alert_Batsu() {
setTimeout(() => {
alert('✖︎の勝ちです');
}, 300);
}
//1回のみのクリックを許容する変数
const oneTime = { once: true };
//td要素の取得
let td = document.getElementsByTagName("td");
//クリックカウント
let count = 0;
//tdの要素1つ1つにクリックイベントを起こせるようにする
for (let index = 0; index < td.length; index++) {
td[index].addEventListener('click', (event) => {
let action = event.target;
//カウントを増やしながら1回事に交互に繰り返す変数
//カウントの名前をクリックカウントへ変更する
let text = null;
if (count % 2 === 0) {
text = "○"
//マルだったら配列に1を入れる
board[Math.floor(index / lineNumber)][index % lineNumber] = 1;
} else {
text = "✖︎";
//バツだったら配列に2を入れる
board[Math.floor(index / lineNumber)][index % lineNumber] = 2;
}
count++;
action.textContent = text;
verticalWWin();
besideWin();
xWin();
//引き分け判定
if (count === lineNumber * lineNumber) {
setTimeout(() => {
alert('引き分けです');
}, 300);
}
}, oneTime)
}
//横列のマルとバツの勝ち判定関数
function besideWin() {
for (let i = 0; i < lineNumber; i++) {
//縦を固定しその配列内で1と2の要素の配列をそれぞれarr1とarr2へ格納しその長さで判定
const arr1 = board[i].filter((one) => {
return one === 1;
});
const arr2 = board[i].filter(two => two === 2);
if (arr1.length === lineNumber) {
alert_Maru();
};
if (arr2.length === lineNumber) {
alert_Batsu();
};
}
}
//縦列を判定する関数
function verticalWWin() {
for (let i = 0; i < lineNumber; i++) {
//現在の配列から縦でみる配列を生成する。
const oneCount = board.map((one) => {
return one[i]
});
const twoCount = board.map((two) => {
return two[i];
});
//縦で見れる配列で各要素の1と2を探しそれぞれ変数へ格納。
const arr1 = oneCount.filter((one) => {
return one === 1;
});
const arr2 = twoCount.filter((two) => {
return two === 2;
});
if (arr1.length === lineNumber) {
alert_Maru();
};
if (arr2.length === lineNumber) {
alert_Batsu();
}
};
}
//斜めでの勝利判定
function xWin() {
//斜め判定のループ添字とその逆で斜めを判定
const diagonal = board.map((one, index) => {
return one[index] || one[(lineNumber - 1) - index];
})
const arr1 = diagonal.filter((one) => {
return one === 1;
});
const arr2 = diagonal.filter((two) => {
return two === 2;
})
if (arr1.length === lineNumber) {
alert_Maru();
};
if (arr2.length === lineNumber) {
alert_Batsu();
}
};
</script>
</body>
</html>
##最後に
このゲームの作成はかなり苦戦しました。
特に2つの部分で実力不足だと感じました。
・実現したいものをどのデータをどのように操作することで実現できるのかを言語化できないこと
・データアルゴリズムの理解が浅いこと
ここが本当に足りてませんでした。
まだまだ学習し続けていきます。
何か気になる点があれば遠慮なくコメントください。
最後まで見ていただきありがとうございました!!