【ITニュース解説】How to Build a Minesweeper CLI Game in Node.js (Part 1/3)
2025年09月24日に「Dev.to」が公開したITニュース「How to Build a Minesweeper CLI Game in Node.js (Part 1/3)」について初心者にもわかりやすく解説しています。
ITニュース概要
Node.jsでコマンドライン版マインスイーパゲームを作るチュートリアルの第一弾。CLIからのゲーム盤サイズ指定、2次元盤面の作成、地雷のランダム配置、隣接する地雷数のカウント方法を、実際のコードとともに学ぶ。ゲームの基本モデル構築を解説する内容。
ITニュース解説
このニュース記事は、Node.jsというJavaScriptの実行環境を使って、おなじみのマインスイーパゲームをコマンドラインインターフェース(CLI)版として作る方法を解説している。システムエンジニアを目指す初心者でも、Node.jsの基本的な使い方やプログラミングの考え方を学びながら、ステップバイステップでゲームの土台を構築できる内容だ。記事は全3パートのうちの最初のパートで、ゲームの核となる部分、つまりグリッドの作成、地雷の配置、そして地雷がないマスに表示される数字の計算方法に焦点を当てている。
まず、マインスイーパゲームの基本的なルールを理解する必要がある。このゲームは、NかけるNの正方形のグリッドで行われる。ゲーム開始時には、決まった数の地雷がグリッド内に隠されて配置される。プレイヤーの目標は、地雷が埋まっていない「安全なマス」をすべて開くことだ。もし地雷のあるマスを開いてしまうと、ゲームはそこで終了し、プレイヤーは負けとなる。地雷がないマスを開くと、そのマスには0から8までの数字が表示される。この数字は、開いたマスの周囲8方向(上下左右、斜め)にいくつ地雷が隠されているかを示している。例えば、マスに「2」と表示されれば、その周囲8マスのうち2マスに地雷があることを意味する。プレイヤーは地雷があると思われるマスに「旗(フラグ)」を立てることもできる。ゲームの勝利条件は、すべての安全なマスを開くか、またはすべての地雷に正しく旗を立てることのどちらかである。
このゲームをNode.jsで実装していく最初のステップは、開発環境の準備から始まる。具体的には、minesweeper.jsという名前の新しいJavaScriptファイルを作成し、コードを記述していく。このファイルは、JavaScriptのコードが実行されるメインの場所となる。
次に、スクリプトの「エントリーポイント」、つまりプログラムが最初に実行される部分を作成する。記事では「Immediately Invoked Function Expression (IIFE)」という形式を使っている。これは( () => { /* コード */ } )() のように記述されるJavaScriptのテクニックで、関数を定義すると同時にすぐに実行する仕組みだ。これにより、この関数内で定義された変数や関数が、グローバルなスコープに影響を与えるのを防ぎ、コードの独立性を保つことができる。初心者にとっては少し難しく感じるかもしれないが、今は「プログラムが動き出す場所をきれいに作る方法」と理解しておけば良いだろう。
続いて、ゲームの基本となる空のグリッドを作成するcreateGrid()関数を定義する。この関数の中で、まずグリッドのサイズをsizeという定数で定義し、初期値として6が設定されている。つまり、最初は6x6マスのグリッドが作られることになる。次に、gridという定数を宣言し、二次元配列としてグリッドを表現する。各マスは単なる値ではなく、JavaScriptの「オブジェクト」という形で表現される。このオブジェクトには、そのマスが地雷かどうかを示すmine(真偽値)、周囲の地雷数を示すadjacent(数値)、すでに開かれたかどうかを示すrevealed(真偽値)、そして旗が立てられたかどうかを示すflagged(真偽値)という四つのプロパティが含まれる。これらはすべて初期値としてfalseや0が設定され、地雷がなく、開かれておらず、旗も立っていない状態を表している。このcreateGrid()関数は、最終的に完成したグリッドの二次元配列を返す。IIFEのエントリーポイント内でcreateGrid()を呼び出し、その結果をgrid定数に格納することで、ゲームの基盤となる空の盤面が準備される。
グリッドのサイズを固定値にするのではなく、ゲームを実行する際にコマンドラインから指定できるように変更する。そのためにparseGridSize()という新しい関数が導入される。この関数は、Node.jsプログラムが受け取るコマンドライン引数を解析する。具体的には、process.argv[2]という部分で、プログラムの実行時に指定された3番目の引数(例えばnode minesweeper.js 9と入力した場合の「9」)を取得する。これをparseInt()関数で整数に変換し、グリッドのサイズとして利用する。変換された数値が3から10の範囲外だったり、有効な数値でなかったりした場合は、デフォルトのサイズとして6が使われるように工夫されている。これにより、ユーザーはnode minesweeper.jsとだけ入力すればデフォルトの6x6グリッドで、node minesweeper.js 9のように数字を追加すれば9x9グリッドでゲームを開始できるようになる。createGrid()関数もこの変更に合わせて、引数としてサイズを受け取るように修正される。
次に、グリッド内にランダムに地雷を配置する処理を実装する。まず、generateRandomNumber(min, max)というヘルパー関数を作成する。これは、指定された最小値(min)から最大値(max)までの範囲で、ランダムな整数を生成する役割を果たす。Math.random()関数が0以上1未満の浮動小数点数を返すため、これを使って範囲を調整し、Math.floor()で小数点以下を切り捨てることで整数を得る。このヘルパー関数を使って、placeMines(grid)関数内で地雷を配置する。placeMines()関数は、グリッドのサイズと同じ数の地雷を配置することを目標とする。whileループを使い、グリッドの行と列のインデックスをランダムに生成し、そのマスがまだ地雷でなければ地雷を配置し、地雷のカウンターを増やす。すでに地雷があるマスに再度地雷を配置しようとした場合は、そのマスをスキップして別のランダムな位置を探すことで、地雷が重複しないようにする。このplaceMines()関数もcreateGrid()関数内で呼び出され、グリッドが生成されると同時に地雷が配置されるようにする。
最後に、地雷のないマスに表示される「周囲の地雷数」を計算し、配置する処理を実装する。これはplaceNumbers(grid)関数で行われる。この関数では、二重ループを使ってグリッドのすべてのマスを一つずつ確認していく。もし確認中のマス自体が地雷であれば、そのマスに数字を表示する必要はないため、次のマスへスキップする。地雷がないマスの場合、その周囲8方向を調べる必要がある。そこでdeltasという定数配列が用意される。この配列には、現在のマスからの相対的な位置を示すオフセット(行方向の差、列方向の差)が格納されている。例えば、[-1, -1]は現在のマスから見て「1つ上、1つ左」のマスを意味する。for...ofループを使ってdeltas配列の各要素を順番に処理し、現在のマスの位置にdeltaの値を加えることで、周囲のマスそれぞれの座標を計算する。この際、計算された周囲のマスがグリッドの範囲外に出ていないか(例えば、グリッドの左端のマスから「左」を見ようとしていないか)を丁寧にチェックする。もし範囲外であれば、その方向の確認はスキップされる。範囲内の隣接マスであれば、そのマスが地雷を持っているか(grid[row + deltaX][col + deltaY].mineがtrueか)を確認し、地雷があればcount変数を1つ増やす。すべての隣接マスをチェックし終えたら、countの最終的な値を現在のマスのadjacentプロパティに設定する。このplaceNumbers()関数もcreateGrid()関数内で呼び出され、地雷配置の後に数字が計算・設定されるようにする。
ここまでで、マインスイーパゲームの基盤となるモデルが完成した。グリッドが作成され、ランダムな位置に地雷が配置され、さらに地雷がないマスには周囲の地雷数が正しく計算されて設定された状態だ。これは、ゲームロジックの核となる部分であり、この土台があれば、あとはユーザーインターフェースやゲームの操作、勝敗判定などの要素を追加していくだけで、本格的なゲームへと発展させることができる。このニュース記事のパート2では、このモデルを実際にプレイ可能なターミナルゲームにするための、グリッドの表示、コマンドの解析、ゲームの状態更新、そして勝利・敗北条件の処理について学ぶことになる。システムエンジニアを目指す上で、このような段階的な開発プロセスと、各機能がどのように連携して一つのシステムを構築していくかを理解することは、非常に重要な経験となるだろう。