CSSHTMLJavaScript

【ゲーム開発】マインスイーパーを作成してみよう。JavaScriptを使ってWeb上で動作させよう。

2024.12.21 土
114 view
【ゲーム開発】マインスイーパーを作成してみよう。JavaScriptを使ってWeb上で動作させよう。

プログラミングを行っていて、作りたいものがなくなったときなど無いだろうか。

恐らく沢山の人がこの現象を経験しているだろう。

しかし、そこでプログラミングをやめてしまっては、プログラミング能力が成長しない。

そんな時におすすめなのが、簡単なゲームを開発することだ。

そして今回は、マインスイーパーを例として行う。

それでは実際にマインスイーパーを作成していく。

マインスイーパーとは

地雷が埋められた盤面を推理しながら掘り進めるパズルゲームだ。

ゲームの目的は、地雷をすべて特定し、それ以外のマスを全て開けることだ。

ルール

  1. 盤面の構成
    • 格子状のマス目で構成されており、各マスには「地雷」が埋められているか、または「数字」が表示される。
  2. 地雷の位置を推測
    • ゲーム開始時、盤面のどこに地雷があるかは分からない。
    • マスをクリックすると、地雷がない場合はそのマスが開き、以下のいずれかが表示される
      • 数字: そのマスの周囲8マスにある地雷の数。
      • 空白: 周囲8マスに地雷がない場合。
    • 地雷をクリックしてしまうとゲームオーバーになる。
  3. フラグ機能
    • プレイヤーが地雷の可能性があるマスには「フラグ」を立ててマークすることができる。
  4. 勝利条件
    • 地雷が埋まっていないすべてのマスを開くことができれば勝利。

以上のことを踏まえた上でマインスイーパーを作成していく。

基本的な骨格の作成 -HTML-

まずはHTMLを使い、マインスイーパーの基本的な骨格を作成していく。

ファイル名は index.html として作成する。

ゲーム構造の作成

マインスイーパーの盤面となる要素を配置する。

<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>マインスイーパー</title>
    <link rel="stylesheet" href="style.css">
</head>
<body>
    <div id="gameBoard"></div>

    <script src="script.js"></script>
</body>
</html>

コードの解説

デザインの適用

<link rel="stylesheet" href="style.css">

外部CSSファイル(style.css)をリンクして、スタイルを適用する。

マインスイーパーを表示するコンテナ

<div id="gameBoard"></div>

盤面を表示するためのコンテナを用意する。

スクリプトの適用

<script src="script.js"></script>

外部のJavaScriptファイル(script.js)をリンクしてスクリプトを適用する。

基本的なロジックの作成 -JavaScript-

次にJavaScriptを使い、マインスイーパーの基本的なロジックを作成していく。

ファイル名は script.js として作成する。

ゲーム盤面の作成

ゲームの盤面を生成し、セル情報を初期化する処理を行う。

const ROWS = 10; // 行数
const COLS = 10; // 列数
const MINES = 10; // 地雷の数

let board = []; // ゲーム盤面
let revealedCellsCount = 0; // 公開されたセル数を追跡

// ゲームを初期化
function initGame() {
    board = [];
    revealedCellsCount = 0;
    gameBoard.innerHTML = "";

    // 盤面の作成
    for (let row = 0; row < ROWS; row++) {
        board[row] = [];
        for (let col = 0; col < COLS; col++) {
            board[row][col] = {
                isMine: false, // 地雷かどうか
                isRevealed: false, // 開かれているか
                isFlagged: false, // フラグが立っているか
                adjacentMines: 0, // 隣接する地雷の数
            };

            // セルをHTML上に作成
            const cell = document.createElement("div");
            cell.classList.add("cell");
            cell.dataset.row = row;
            cell.dataset.col = col;

            // イベントリスナーを追加
            cell.addEventListener("click", handleCellLeftClick);
            cell.addEventListener("contextmenu", handleCellRightClick);

            gameBoard.appendChild(cell);
        }
    }

    // 地雷をランダム配置
    randomPlaceMines();

    // 隣接する地雷数を計算
    calculateAdjacentMines();

    // グリッドのスタイルを設定
    gameBoard.style.gridTemplateRows = `repeat(${ROWS}, 30px)`;
    gameBoard.style.gridTemplateColumns = `repeat(${COLS}, 30px)`;
}

// ゲームを初期化
initGame();

コードの解説

盤面に使用する変数の定義

const ROWS = 10; // 行数
const COLS = 10; // 列数
const MINES = 10; // 地雷の数

let board = []; // ゲーム盤面
let revealedCellsCount = 0; // 公開されたセル数を追跡

ROWSCOLS は盤面の行数と列数を定義している。今回は10×10で配置する。

MINES はゲーム内の地雷の総数を設定している。

board は盤面情報を格納する2次元配列だ。

revealedCellsCount は公開されたセル数を格納する。

盤面の初期化

board = [];
revealedCellsCount = 0;
gameBoard.innerHTML = "";

ゲーム盤面に使用する変数を初期化する。

セルの生成

// 盤面の作成
for (let row = 0; row < ROWS; row++) {
    board[row] = [];
    for (let col = 0; col < COLS; col++) {
        board[row][col] = {
            isMine: false, // 地雷かどうか
            isRevealed: false, // 開かれているか
            isFlagged: false, // フラグが立っているか
            adjacentMines: 0, // 隣接する地雷の数
        };

各セルの情報を格納するオブジェクトを作成している。

isMine は地雷かどうかを判定する。

isRevealed はセルが開かれているかどうかを判定する。

isFlagged はフラグが設置されているかどうかを判定する。

adjacentMines は隣接する地雷の数をカウントする。

HTMLへ要素を追加

// セルをHTML上に作成
const cell = document.createElement("div");
cell.classList.add("cell");
cell.dataset.row = row;
cell.dataset.col = col;

各セルに対応するHTML要素を生成している。

classList.add("cell") でCSSクラス cell を追加し、見た目やスタイルを設定する。

dataset.rowdataset.col を設定し、セルの行と列の情報をHTML属性として保持する。

appendChild により、生成したセル要素をゲーム盤面(gameBoard)の子要素として配置する。これで、HTMLのDOMツリーにセルが表示されるようになる。

イベントリスナーの追加

cell.addEventListener("click", handleCellLeftClick);
cell.addEventListener("contextmenu", handleCellRightClick);

左クリック時に handleCellLeftClick を呼び出す。

右クリック(コンテキストメニュー)時に handleCellRightClick を呼び出す。

セルをゲーム盤に追加

gameBoard.appendChild(cell);

作成したセルを盤面(gameBoard)に追加する。

地雷の配置

randomPlaceMines();

地雷をランダムに配置する関数を呼び出す。

隣接地雷数の計算

calculateAdjacentMines();

各セルに隣接する地雷の数を計算する関数を呼び出す。

盤面のスタイル設定

// グリッドのスタイルを設定
gameBoard.style.gridTemplateRows = `repeat(${ROWS}, 30px)`;
gameBoard.style.gridTemplateColumns = `repeat(${COLS}, 30px)`;

CSSグリッドを使用して、盤面の行と列のサイズを動的に設定する。

各セルは 30px x 30px で描画される。

地雷の設置

ゲーム盤面上に指定された数(MINES)の地雷をランダムに配置する処理を行う。

// 地雷をランダムに配置
function randomPlaceMines() {
    let minesPlaced = 0; // 配置された地雷の数を記録する変数

    // 指定した数の地雷が配置されるまでループ
    while (minesPlaced < MINES) {
      // ランダムに行と列を選択
      let row = Math.floor(Math.random() * ROWS); // ROWS: 盤面の行数
      let col = Math.floor(Math.random() * COLS); // COLS: 盤面の列数
  
      // 選ばれたセルにまだ地雷が置かれていない場合
      if (!board[row][col].isMine) {
        board[row][col].isMine = true; // 地雷を配置
        minesPlaced++; // 配置した地雷の数を1増やす
      }
    }
}

コードの解説

設置した地雷数をカウントする変数の定義

let minesPlaced = 0; // 配置された地雷の数を記録する変数

minesPlaced現在までに配置した地雷の数をカウントする変数だ。

全ての地雷を配置するためにループ処理

// 指定した数の地雷が配置されるまでループ
while (minesPlaced < MINES) {

地雷を配置した数(minesPlaced)が、指定された地雷の数(MINES)に達するまで繰り返す。

地雷を配置するセルをランダムに選択

// ランダムに行と列を選択
let row = Math.floor(Math.random() * ROWS); // ROWS: 盤面の行数
let col = Math.floor(Math.random() * COLS); // COLS: 盤面の列数

行(row)と列(col)をランダムに選択する

これにより、ランダムに 0 から ROWS-1(行数)および 0 から COLS-1(列数)のインデックスが生成される。

地雷が未配置のセルを確認

// 選ばれたセルにまだ地雷が置かれていない場合
if (!board[row][col].isMine) {

選ばれたセルの isMine プロパティを確認する。

すでに地雷が配置されているセルはスキップして、別のセルを選び直す。

地雷を配置

board[row][col].isMine = true; // 地雷を配置
minesPlaced++; // 配置した地雷の数を1増やす

もし未配置セルであれば、そのセルに地雷を配置する。

そして配置した地雷の数(minesPlaced)を1増やす。

隣接する地雷数の計算

ゲーム盤面のセルについて、隣接する地雷の数を計算して記録する処理を行う。

// 隣接する地雷数を計算
function calculateAdjacentMines() {
    // 周囲8方向を表す相対座標のリスト
    const directions = [
        [-1, -1], [-1,  0], [-1,  1], // 左上、真上、右上
        [ 0, -1],           [ 0,  1], // 左、右
        [ 1, -1], [1,  0],  [ 1,  1], // 左下、真下、右下
    ];

    // 全てのセルをループで確認
    for (let row = 0; row < ROWS; row++) {
        for (let col = 0; col < COLS; col++) {
            // セルが地雷であれば周囲の地雷数を計算する必要はない
            if (board[row][col].isMine) continue;

            let count = 0; // 周囲の地雷数をカウントする変数

            // 各方向に移動し、地雷があるかを確認
            for (let [dx, dy] of directions) {
                const newRow = row + dx; // 新しい行の位置
                const newCol = col + dy; // 新しい列の位置

                // 新しい位置が盤面の範囲内かどうかを確認
                if (newRow >= 0 && newRow < ROWS && newCol >= 0 && newCol < COLS) {
                    if (board[newRow][newCol].isMine) {
                        count++; // 周囲に地雷があればカウントを増加
                    }
                }
            }

            // 現在のセルに周囲の地雷数を設定
            board[row][col].adjacentMines = count;
        }
    }
}

コードを解説

セルの周囲8方向を定義

// 周囲8方向を表す相対座標のリスト
const directions = [
    [-1, -1], [-1,  0], [-1,  1], // 左上、真上、右上
    [ 0, -1],           [ 0,  1], // 左、右
    [ 1, -1], [1,  0],  [ 1,  1], // 左下、真下、右下
];

各セルの周囲8方向を相対座標で表す。

全てのセルを確認するためにループ処理

// 全てのセルをループで確認
for (let row = 0; row < ROWS; row++) {
    for (let col = 0; col < COLS; col++) {
        // セルが地雷であれば周囲の地雷数を計算する必要はない
        if (board[row][col].isMine) continue;

盤面全体を走査します。

地雷セルは隣接地雷数を計算する必要がないためスキップする。

周囲の地雷数を確認

let count = 0; // 周囲の地雷数をカウントする変数

// 各方向に移動し、地雷があるかを確認
for (let [dx, dy] of directions) {
    const newRow = row + dx; // 新しい行の位置
    const newCol = col + dy; // 新しい列の位置

    // 新しい位置が盤面の範囲内かどうかを確認
    if (newRow >= 0 && newRow < ROWS && newCol >= 0 && newCol < COLS) {
        if (board[newRow][newCol].isMine) {
            count++; // 周囲に地雷があればカウントを増加
        }
    }
}

newRownewCol で新しいセルの位置を計算する。

newRownewCol が盤面外(負値や行・列の超過)にならないようにチェックする。

隣接セルが地雷(isMine = true)なら周囲にある地雷の総数 (count)を増やす。

// 現在のセルに周囲の地雷数を設定
board[row][col].adjacentMines = count;

現在のセルに、周囲にある地雷の総数(count)を記録する。

セルの公開処理

左クリックしたセルが空白(周囲に地雷がない)であった場合に、そのセルを中心として再帰的に周囲の空白セルを公開する処理を行う。

// セルが左クリックされたとき
function handleCellLeftClick(e) {
    // クリックされたセルの行と列を取得(HTMLのデータ属性から)
    const row = parseInt(e.target.dataset.row);
    const col = parseInt(e.target.dataset.col);

    // セルが既に公開されている、またはフラグが立てられている場合は処理をスキップ
    if (board[row][col].isRevealed || board[row][col].isFlagged) return;

    // セルを公開済みとしてマーク
    board[row][col].isRevealed = true;
    const cell = e.target; // 対象となるDOM要素(セル)

    // クリックされたセルが地雷だった場合
    if (board[row][col].isMine) {
        cell.classList.add("mine"); // 地雷を視覚的に表示
        gameOver(); // ゲームオーバー
    } else {
        // セルが地雷でない場合
        cell.classList.add("revealed"); // セルを公開済みとして表示

        // 周囲に地雷がある場合、その数をセルに表示
        if (board[row][col].adjacentMines > 0) {
            cell.textContent = board[row][col].adjacentMines;
        } else {
            // 指定したセルから周囲の空のセルを再帰的に公開
            revealEmptyCells(row, col);
        }
    }

    const nonMineCells = ROWS * COLS - MINES; // 地雷以外のセル数
    if (revealedCellsCount === nonMineCells) {
        gameClear(); // ゲームクリア
    }
}

// 周囲に地雷がない場合、空のセルを再帰的に公開
function revealAllMines() {
    // 盤面全体を走査
    for (let row = 0; row < ROWS; row++) {
        for (let col = 0; col < COLS; col++) {
            // 地雷が配置されているセルを確認
            if (board[row][col].isMine) {
                // 対応するDOM要素(セル)を取得
                const cell = document.querySelector(
                    `.cell[data-row='${row}'][data-col='${col}']`
                );

                // 地雷を表すクラスを追加して視覚的に表示
                if (cell) {
                    cell.classList.add("mine");
                }
            }
        }
    }
}

// 指定したセルから周囲の空のセルを再帰的に公開
function revealEmptyCells(row, col) {
    // 周囲の8方向を表す相対座標のリスト
    const directions = [
        [-1, -1], [-1,  0], [-1,  1], // 左上、真上、右上
        [ 0, -1],           [ 0,  1], // 左、右
        [ 1, -1], [ 1,  0], [ 1,  1], // 左下、真下、右下
    ];

    // 周囲8方向を確認
    for (let [dx, dy] of directions) {
        const newRow = row + dx; // 新しい行の位置
        const newCol = col + dy; // 新しい列の位置

        // 新しい位置が盤面の範囲内で、未公開かつ地雷でないセルを対象とする
        if (
            newRow >= 0 &&                 // 行が範囲内か確認
            newRow < ROWS &&               // 行が範囲内か確認
            newCol >= 0 &&                 // 列が範囲内か確認
            newCol < COLS &&               // 列が範囲内か確認
            !board[newRow][newCol].isRevealed && // 既に公開されていないか確認
            !board[newRow][newCol].isMine       // 地雷でないか確認
        ) {
            // セルを「公開済み」に設定
            board[newRow][newCol].isRevealed = true;

            // DOM要素を取得して「公開済み」のスタイルを適用
            const cell = document.querySelector(
                `.cell[data-row='${newRow}'][data-col='${newCol}']`
            );
            cell.classList.add("revealed");

            // セルに周囲の地雷数を表示(0の場合は何も表示しない)
            if (board[newRow][newCol].adjacentMines > 0) {
                cell.textContent = board[newRow][newCol].adjacentMines;
            } else {
                // 周囲に地雷がない場合は再帰的に空のセルを開く
                revealEmptyCells(newRow, newCol);
            }
        }
    }
}

コードの解説

クリックされたセルの行と列を取得

// クリックされたセルの行と列を取得(HTMLのデータ属性から)
const row = parseInt(e.target.dataset.row);
const col = parseInt(e.target.dataset.col);

HTML内のセル要素には、行と列の情報が data-rowdata-col の属性として保存されている。

クリックされたセル (e.target) の dataset からこれらの情報を取得し、整数に変換している。

セルの状態を確認して処理をスキップ

// セルが既に公開されている、またはフラグが立てられている場合は処理をスキップ
if (board[row][col].isRevealed || board[row][col].isFlagged) return

すでに公開されている(isRevealed = true)セル、または旗が立てられている(isFlagged = true)セルの場合、以降の処理をスキップする。

セルを公開済みとしてマーク

// セルを公開済みとしてマーク
board[row][col].isRevealed = true;
const cell = e.target; // 対象となるDOM要素(セル)

セルの内部状態 (board[row][col]) を更新して「公開済み」を示す。

また、クリックされたDOM要素を cell 変数に格納する。これは、この後の処理で視覚的な変更に使用するために行う。

地雷セルの処理

// クリックされたセルが地雷だった場合
if (board[row][col].isMine) {
    cell.classList.add("mine"); // 地雷を視覚的に表示
    gameOver(); // ゲームオーバー
}

もしクリックしたセルが地雷だったら、cell"mine" クラスを追加する。

ゲームオーバーを表示する関数を呼び出す。

地雷でないセルの処理

// セルが地雷でない場合
cell.classList.add("revealed"); // セルを公開済みとして表示

地雷でない場合、"revealed" クラスをセルに追加する。

周囲の地雷数を表示

// 周囲に地雷がある場合、その数をセルに表示
if (board[row][col].adjacentMines > 0) {
    cell.textContent = board[row][col].adjacentMines;
}

セルの周囲に地雷が存在する場合、その地雷数をセルに表示する。

adjacentMines は、セルの周囲にある地雷の数を保持している。

周囲に地雷がない場合の再帰処理

else {
    // 指定したセルから周囲の空のセルを再帰的に公開
    revealEmptyCells(row, col);
}

周囲に地雷がない場合、revealEmptyCells 関数を呼び出して、クリックされたセルに隣接する空のセルを再帰的に公開する。

この処理を行うことで、地雷のない空間が連鎖的に広がるように公開される。

ゲームクリアと表示

const nonMineCells = ROWS * COLS - MINES; // 地雷以外のセル数
if (revealedCellsCount === nonMineCells) {
    gameClear(); // ゲームクリア
}

プレイヤーが公開したセル数(revealedCellsCount)と地雷以外のセル数を計算した数(nonMineCells)が一致したらゲームクリア関数を呼び出す。

盤面全体を走査

// 盤面全体を走査
for (let row = 0; row < ROWS; row++) {
    for (let col = 0; col < COLS; col++) {

盤面のすべてのセルを順にチェックする。

地雷セルの確認

// 地雷が配置されているセルを確認
if (board[row][col].isMine) {

現在のセル (board[row][col]) が地雷である(isMine プロパティが true)かを確認する。

対応するDOM要素(セル)を取得

// 対応するDOM要素(セル)を取得
const cell = document.querySelector(
    `.cell[data-row='${row}'][data-col='${col}']`
);

HTML内の盤面を表す各セルには、data-rowdata-col 属性が設定されている。

それをdocument.querySelector を使い、現在のセル (row, col) に対応するDOM要素を取得する。

地雷セルの視覚的表示

// 地雷を表すクラスを追加して視覚的に表示
if (cell) {
    cell.classList.add("mine");
}

取得したセルに "mine" クラスを追加します。

セルの周囲8方向を定義

// 周囲8方向を表す相対座標のリスト
const directions = [
    [-1, -1], [-1,  0], [-1,  1], // 左上、真上、右上
    [ 0, -1],           [ 0,  1], // 左、右
    [ 1, -1], [1,  0],  [ 1,  1], // 左下、真下、右下
];

各セルの周囲8方向を相対座標で表す。

周囲8方向を確認

// 周囲8方向を確認
for (let [dx, dy] of directions) {
    const newRow = row + dx; // 新しい行の位置
    const newCol = col + dy; // 新しい列の位置

各方向をループで処理する。

現在のセル位置 (row, col) に dx, dy を加算して、新しいセルの位置 (newRow, newCol) を計算する。

公開対象の条件確認

// 新しい位置が盤面の範囲内で、未公開かつ地雷でないセルを対象とする
if (
    newRow >= 0 &&                 // 行が範囲内か確認
    newRow < ROWS &&               // 行が範囲内か確認
    newCol >= 0 &&                 // 列が範囲内か確認
    newCol < COLS &&               // 列が範囲内か確認
    !board[newRow][newCol].isRevealed && // 既に公開されていないか確認
    !board[newRow][newCol].isMine       // 地雷でないか確認
)

newRownewCol が盤面の範囲(ROWS × COLS)内かつ、

セルがまだ公開されていないかつ、

セルが地雷でない場合が条件となっている。

セルを公開

// セルを「公開済み」に設定
board[newRow][newCol].isRevealed = true;

// DOM要素を取得して「公開済み」のスタイルを適用
const cell = document.querySelector(
    `.cell[data-row='${newRow}'][data-col='${newCol}']`
);
cell.classList.add("revealed");

セルを公開済み(isRevealed = true)としてマークする。

対応するHTML要素を取得して "revealed" クラスを追加する。

地雷数を表示

// セルに周囲の地雷数を表示(0の場合は何も表示しない)
if (board[newRow][newCol].adjacentMines > 0) {
    cell.textContent = board[newRow][newCol].adjacentMines;
}

セルの周囲に地雷が存在する場合、その数(adjacentMines プロパティ)を表示する。

地雷数が 0 の場合は何も表示しない。

再帰的に空白セルを公開

else {
    // 周囲に地雷がない場合は再帰的に空のセルを開く
    revealEmptyCells(newRow, newCol);
}

セルが空白(adjacentMines === 0)であれば、再帰的に revealEmptyCells を呼び出し、周囲のセルを同様に公開する。

フラグの設置機能

右クリックされたセルにフラグを立てたり、フラグを解除する処理を行う。

// セルが右クリックされたとき
function handleCellRightClick(e) {
    // 右クリックのデフォルト動作(コンテキストメニュー表示)を無効化
    e.preventDefault();

    // クリックされたセルの行と列を取得(HTML要素のカスタムデータ属性を使用)
    const row = parseInt(e.target.dataset.row);
    const col = parseInt(e.target.dataset.col);
    const cell = e.target; // クリックされたセルのDOM要素

    // セルが既に開かれている場合は何もしない
    if (board[row][col].isRevealed) return;

    // フラグの状態を切り替える(true ↔ false)
    board[row][col].isFlagged = !board[row][col].isFlagged;

    // フラグの状態に応じてセルに "flag" クラスを追加または削除
    cell.classList.toggle("flag");
}

コードの解説

イベントのデフォルト動作を無効化

// 右クリックのデフォルト動作(コンテキストメニュー表示)を無効化
e.preventDefault();

右クリックすると表示されるコンテキストメニューを無効化することで、右クリックがゲーム内の動作にのみ反応するようにする。

クリックされたセルの情報を取得

// クリックされたセルの行と列を取得(HTML要素のカスタムデータ属性を使用)
const row = parseInt(e.target.dataset.row);
const col = parseInt(e.target.dataset.col);
const cell = e.target; // クリックされたセルのDOM要素

クリックされたセル要素に埋め込まれている data-row および data-col 属性の値を取得する。

セルがすでに公開されている場合は何もしない

// セルが既に開かれている場合は何もしない
if (board[row][col].isRevealed) return;

セルが既に「公開済み(isRevealedtrue)」であれば、右クリックの操作を無効化し、公開済みセルにフラグを付けられないようにする。

フラグ状態を切り替える

// フラグの状態を切り替える(true ↔ false)
board[row][col].isFlagged = !board[row][col].isFlagged;

セルのフラグ状態(isFlagged)を反転させ、フラグの ON/OFF を切り替える。

フラグの見た目を切り替える

// フラグの状態に応じてセルに "flag" クラスを追加または削除
cell.classList.toggle("flag");

HTML要素に flag クラスの追加/削除を切り替える。

ゲームオーバー時の処理

地雷をクリックしたときのゲームオーバー処理を行う。

// 地雷のセルをクリックしたとき
function gameOver() {
    alert("ゲームオーバー"); // ゲームオーバーのメッセージを表示
    revealAllMines(); // すべての地雷を公開
}

コードの解説

ゲームオーバーのメッセージを表示

alert("ゲームオーバー"); // ゲームオーバーのメッセージを表示

alert でゲームオーバーとモーダルダイアログを表示する。

すべての地雷を公開

revealAllMines(); // すべての地雷を公開

全ての地雷を公開する関数を呼び出す。

ゲームクリア時の処理

地雷以外のセルを公開したときのゲームクリア処理を行う。

// 地雷以外のセルがすべて公開されたとき
function gameClear() {
    alert("ゲームクリア"); // ゲームクリアのメッセージを表示
    revealAllMines(); // すべての地雷を公開
}

コードの解説

ゲームクリアのメッセージを表示

alert("ゲームクリア"); // ゲームクリアのメッセージを表示

alert でゲームクリアとモーダルダイアログを表示する。

すべての地雷を公開

revealAllMines(); // すべての地雷を公開

全ての地雷を公開する関数を呼び出す。

基本的なスタイルの作成 -CSS-

最後にCSSを使い、マインスイーパーの基本的なスタイルを作成していく。

ファイル名は style.css として作成する。

全体のスタイル

全体にスタイルを適用する。

body {
    font-family: Arial, sans-serif;
    text-align: center;
    background-color: #f4f4f9;
}

コードの解説

body要素

font-family: Arial, sans-serif;

ページ全体のフォントをArial(またはsans-serif系フォント)に設定し、数字の視認性を向上させる。

text-align: center;

ページ内のコンテンツ(ゲームボードなど)を中央揃えにする。

background-color: #f4f4f4;

ページ背景を薄い灰色に設定し、視認性を向上させる。

ゲーム盤面のスタイル

ゲーム盤面にスタイルを適用する。

#gameBoard {
    display: grid;
    margin: 20px auto;
    gap: 2px;
    width: fit-content;
}

.cell {
    width: 30px;
    height: 30px;
    text-align: center;
    line-height: 30px;
    font-size: 16px;
    background-color: #ccc;
    border: 1px solid #999;
    cursor: pointer;
    user-select: none;
}

.cell.revealed {
    background-color: #eee;
    cursor: default;
}

.cell.mine {
    background-color: #f44336;
    color: white;
}

.cell.flag {
    background-color: #ffa726;
}

コードの解説

ゲームボードのスタイル

#gameBoard {
    display: grid;
    margin: 20px auto;
    gap: 2px;
    width: fit-content;
}

ゲームボードにスタイルを適用する。

display: grid;

ゲームボードをグリッドレイアウトで表示し、セルを格子状に配置する。

margin: 20px auto;

ゲームボードを上下に20pxの余白を持たせ、左右は中央揃えにする。

gap: 2px;

各セル間に2pxの隙間を配置する。

width: fit-content;

左右を中央寄せにする。

各セルのスタイル

.cell {
    width: 30px;
    height: 30px;
    text-align: center;
    line-height: 30px;
    font-size: 16px;
    background-color: #ccc;
    border: 1px solid #999;
    cursor: pointer;
    user-select: none;
}

各セルにスタイルを適用する。

width: 30px; height: 30px;

各セルを30x30pxの正方形にする。

text-align: center; line-height: 30px;

テキスト(数字や旗)をセル中央に配置する。

font-size: 16px;

セル内の文字サイズを16pxにする。

background-color: #ccc;

セルの背景色を薄いグレーにする。

border: 1px solid #999;

セルの境界線をグレーにする。

cursor: pointer;

セルがクリック可能であることを示すマウスカーソルにする。

user-select: none;

テキスト選択を無効化し、ゲームの操作性を向上させる。

地雷のスタイル

.cell.mine {
    background-color: #f44336;
    color: white;
}

地雷にスタイルを適用する。

background-color: #f44336;

地雷セルの背景色を赤にする。

color: white;

地雷セルの文字色を白に設定し、視認性を向上させる。

旗のスタイル

.cell.flag {
    background-color: #ffa726;
}

旗にスタイルを適用する。

background-color: #ffa726;

旗が置かれたセルの背景色をオレンジ色にする。

ソースコードのダウンロード

これらのコードはGithubにアップロードしている。

コードのダウンロードはこちら (github.com)

上記のページに、すべてアップロードされているので、必要な方はぜひ使ってみてくれ。

まとめ

いかがだっただろうか。

無事にマインスイーパーは作成できただろうか。

この状態のマインスイーパーはまだまだ素朴なデザインである。

次回はデザイン改良と機能の追加を行っていく。

ぜひ期待していてくれ。

コメントはこちらから

必須コメント

必須ハンドルネーム

プロフィールアイコン画像
空気
プログラマー兼ブロガー。様々なプログラミング言語を使いアプリやゲーム、ツールなどを開発。
月別アーカイブ