【JavaScript】ワンピースのオセロゲーム(赤犬vs青雉)
今回はJavaScriptでオセロゲームを作成しました。
赤犬と青雉との戦いを再現してみました。オセロで笑
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="utf-8">
<title>オセロゲーム</title>
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<link rel="stylesheet" media="screen" href="style.css">
</head>
<body>
<p>
<span id="current-turn">赤犬の番です</span>
<button id="pass">パスする</button>
</p>
<div id="stage" class="stage"></div>
<div id="square-template" class="square">
<div class="stone"></div>
</div>
<script src="index.js"></script>
</body>
</html>
* { /*要素の幅と高さにpaddingとborderを含める*/
box-sizing: border-box;
}
html, body {
margin: 0;
text-align: center; /*pタグを中央寄せ*/
background-color: #bcf0bc;
}
.stage {
display: flex; /*<div id="stage" class="stage"></div>の中の要素を*/
flex-wrap: wrap;/*折り返して表示する*/
margin: 0 auto; /*中央寄せ*/
width: 644px; /* 84(一番左)+80(それ以外の四角)×7(個) */
height: 644px;
}
.square {
position: relative;
width: 80px; /* 一番左以外の四角形に適用 */
height: 80px;/* 一番上以外の四角形に適用 */
border: solid black;
border-width: 0 4px 4px 0; /*右と下の枠*/
cursor: pointer;
background-color: rgb(6, 136, 26);
}
.square:nth-child(-n + 8) { /*一番上の四角形8個*/
border-width: 4px 4px 4px 0;/*左以外の枠*/
height: 84px;
}
.square:nth-child(8n + 1) { /*一番左の四角形8個*/
border-width: 0 4px 4px 4px;/*上以外の枠*/
width: 84px;
}
.square:first-child { /*先頭の四角形(左隅)*/
border-width: 4px;/*上下左右*/
width: 84px;
height: 84px;
}
.stone {
position: absolute;
width: 76px;
height: 76px;
background-size: contain; /*背景画像全体が見えるように*/
background-repeat: no-repeat; /*背景画像の繰り返しをしない*/
background-position: center; /*背景画像を中央にする*/
}
.stone[data-state="0"] {
display: none;
}
.stone[data-state="1"] {
background-color: red; /*背景画像を指定*/
background-image: url('img/akainu.png');
}
.stone[data-state="2"] {
background-color: blue; /*背景画像を指定*/
background-image: url('img/aokiji.png');
}
#square-template {
display: none;
}
const stage = document.getElementById("stage");
const squareTemplate = document.getElementById("square-template");
const stoneStateList = []; //配列で石の状態を管理
let currentColor = 1; //1は赤犬 2は青雉
const currentTurnText = document.getElementById("current-turn");
const passButton = document.getElementById("pass");
const changeTurn = () => {
currentColor = 3 - currentColor; //「1なら2」「2なら1」にする
if (currentColor === 1) {
currentTurnText.textContent = "赤犬の番です";
} else {
currentTurnText.textContent = "青雉の番です";
setTimeout(CPU, 1000);
}
}
const CPU = () => { //コンピューター
let clickNums = []; //クリックできるマスのindex番号を入れる配列
for (let i = 0; i < 64; i++) { //これ以降の処理を実行させない
if (stoneStateList[i] !== 0 || !getReversibleStones(i).length) continue;
clickNums.push(i);
}
let n = Math.floor(Math.random() * clickNums.length);
onClickSquare(clickNums[n]);
}
const getReversibleStones = (idx) => { //ひっくり返せる石の番号をゲットする
const squareNums = [ //各方向にマスがいくつあるか
7 - (idx % 8), //右に何マスあるか
Math.min(7 - (idx % 8), (56 + (idx % 8) - idx) / 8),//右下に何マス
//右のマス数 下のマス数
(56 + (idx % 8) - idx) / 8, //下に何マス
Math.min(idx % 8, (56 + (idx % 8) - idx) / 8),//左下に何マス
idx % 8, //左に何マス
Math.min(idx % 8, (idx - (idx % 8)) / 8), //左上に何マス
(idx - (idx % 8)) / 8, //真上に何マス
Math.min(7 - (idx % 8), (idx - (idx % 8)) / 8), //右上に何マス
];
const parameters = [1, 9, 8, 7, -1, -9, -8, -7]; //隣のマスに行くため
//右 右下 真下 左下 左 左上 真上 右上
let results = [];//ひっくり返せると確定した石の番号を入れる配列
for (let i = 0; i < 8; i++) { //8方向で調査
const box = []; //ひっくり返せる可能性のある石の情報を入れる配列
const squareNum = squareNums[i]; //現在調べている方向にいくつマスがあるか
const param = parameters[i];
const nextStoneState = stoneStateList[idx + param];//ひとつ隣の石の状態
//隣に石がない or 自分の色 -> 次のループへ
if (nextStoneState === 0 || nextStoneState === currentColor) continue;
box.push(idx + param); //ひとつ隣の石のindex番号
for (let j = 0; j < squareNum - 1; j++) { //さらにその延長線
const targetIdx = idx + param * 2 + param * j;
//2回目 次から足される
const targetColor = stoneStateList[targetIdx];
if (targetColor === 0) break; //その方向は石がないので終了
if (targetColor === currentColor) { //自分の色なら仮ボックスの石がひっくり返せることが確定
results = results.concat(box); //新しい配列を追加
break;
} else { //相手の色なら仮ボックスにその石の番号を格納
box.push(targetIdx);
}
}
}
return results; //ひっくり返せると確定した石の番号を戻り値にする
}
const onClickSquare = (index) => {
const reversibleStones = getReversibleStones(index); //ひっくり返せる石の数を取得
//他の石(1 or 2)
if (stoneStateList[index] !== 0 || !reversibleStones.length) {
return;//これ以降の処理を実行させない
}
//自分の石を置く
stoneStateList[index] = currentColor;
document.querySelector(`[data-index='${index}']`).setAttribute("data-state", currentColor);
//相手の石をひっくり返す
reversibleStones.forEach((key) => {
stoneStateList[key] = currentColor;
document.querySelector(`[data-index='${key}']`).setAttribute("data-state", currentColor);
});
//もし盤面がいっぱいだったら、集計してゲームを終了する
if (stoneStateList.every((state) => state !== 0)) { //すべて0でない(1or2)なら真
const redNum = stoneStateList.filter(state => state === 1).length;
const blueNum = 64 - redNum;
let winnerText = "";
if (redNum > blueNum) {
winnerText = "赤犬の勝ちです!";
} else if (redNum < blueNum) {
winnerText = "青雉の勝ちです!";
} else {
winnerText = "引き分けです";
}
setTimeout(
() => { currentTurnText.textContent = `青${blueNum}、赤${redNum}で、${winnerText}`},
2000
);
}
changeTurn(); //ゲーム続行なら相手のターンにする
}
const createSquares = () => {
for (let i = 0; i < 64; i++) {
const square = squareTemplate.cloneNode(true);//クローンを作成
square.removeAttribute("id"); //idが重複しないように
stage.appendChild(square); //盤に要素を追加
const stone = square.querySelector('.stone');
let defaultState; // 0:何もなし 1:赤犬 2:青雉
if (i == 27 || i == 36) { //iの値によってデフォルトの石の状態を分岐する
defaultState = 1; //赤犬
} else if (i == 28 || i == 35) {
defaultState = 2; //青雉
} else {
defaultState = 0;
}
stone.setAttribute("data-state", defaultState);
stone.setAttribute("data-index", i); //インデックス番号をHTML要素に保持させる
stoneStateList.push(defaultState); //初期値を配列に格納
square.addEventListener('click', () => {
onClickSquare(i); //すべてにクリックイベントを登録
});
}
}
window.onload = () => {
createSquares();
passButton.addEventListener("click", changeTurn);
}