【C#】マインスイーパーの作り方を解説【GUI】

C#

はじめに

ふとマインスイーパーで遊びたくなりスタートメニューのアクセサリを探したのですが、マインスイーパーが見当たりませんでした…

昔のWindowsにはスタートメニューのアクセサリにマインスイーパーがデフォルトで入っていたのですが、最近のWindowsには入っていない?ようです。

探せば無料でダウンロードできるマインスイーパーは無数にあるのですが、どうせなら暇つぶしがてら作ってみようと思いマインスイーパーを作成してみました。

さらにさらにどうせならブログのネタにしようとこの記事を執筆した次第です。

ゴリヘイは高度な数学の知識があるわけではないので、非効率な処理をしているかもしれません。
(なのでつよつよプログラマーの方は温かい目で見守ってください。アドバイスは大歓迎です。)

以下が完成形です。

マインスイーパーとは

皆さんはマインスイーパーのルールを正確に理解していますか?

ごりへいは、地雷以外のマスを全て開いたらクリアでしょ!くらいの認識でした。

ゲームを遊ぶだけならそれでも問題ないのですが、作るとなるとしっかりとルールを決めなければなりません。

なので、改めてルールを調べてみました。

ゲーム画面は正方形のマスが敷き詰められた長方形のフィールドから構成されている。それぞれのマスは開けることができるが、地雷の置かれているマスを開けると負けとなる。地雷の置かれていないマスを開けたときは、隣接する8方向のマスのいずれかに地雷がある場合はその個数が表示され、隣接するマスに地雷が置かれていないときは、それらが自動的に開けられる。地雷の置かれていないマスをすべて開ければ勝ちとなる。

マインスイーパ – Wikipedia

ざっくりまとめると、以下がマインスイーパーの基本ルールみたいです。

  1. 盤面は正方形or長方形どちらでもOK。
  2. 地雷のマスを開いたらゲームオーバー。
  3. 地雷ではないマスを開いた場合、隣接する8方向のマスのいずれかに地雷がある場合はその個数を表示する。
    隣接するマスに地雷がない場合は隣接するマスを自動で開く。
  4. 地雷がではないマスをすべて開いたらゲームクリア。

他にユーザーフレンドリーな機能として、以下があるようです。

  • 地雷があると思われるマスに旗を立てられる。
  • 一番初めに開くマスは必ず地雷以外のマスとなる。(開幕ゲームオーバーを防ぐ)

今回作成するマインスイーパーでは、上記の1~4と旗を立てる機能、初めの1マス目は地雷にならない機能を実装していこうと思います。

大まかな処理フローを考える

まず日本語で大まかな処理フローを考えてからコーディングに着手します。

  1. 盤面の作成
  2. 地雷の配置
  3. クリックされたマスが地雷の場合→ゲームオーバー判定
    ①クリックされたマスが地雷ではない場合
     →②周囲8マスに地雷があるか判定
      →③地雷がある場合→マスに地雷の数を表示
      →③地雷がない場合→周囲のマスに対して②の処理を実行する
  4. 地雷以外のマスが全て開かれた場合→ゲームクリア判定

雑ですがこんな感じでしょうか。

それでは各処理を実装していこうと思います。

実装

開発環境
 VisualStudio2022
 .NET Framework4.8
 WinForms

素材の作成

まずはじめに、今回作成するマインスイーパーはGUIアプリケーションなので、アプリケーションで使用する素材を作ります。

必要な素材は以下の12種類です。

  • 空のマス
  • 1~8のマス
  • 地雷のマス
  • 旗のマス
  • 開いていないマス

12個のファイルに分けるのは取り込むのが面倒なので、全て並べて1つの画像として作成します。

プログラム側で分割して取り込みます。

以下が作成した素材になります。

右クリックで画像を保存してもらってもいいですし、記事の下で紹介しているソースコードからダウンロードしてもらってもOKです。(1つのマスは100px*100px、全体で1200px*100pxとなっていればOK)

上記素材をプロジェクトのリソースにminesweeper.pngという名称で設定しておきましょう。
(ソリューションエクスプローラー>プロジェクト右クリック>プロパティ>リソースにドラッグアンドドロップ)

コントロールの配置

フォームにPictureBoxを貼り付けて以下のプロパティを変更します。

コントロール名プロパティ名
FormNameMainForm
TextMineSweeper
PictureBoxNamepicBoard

ソースコード

まずは全体のソースコードを掲載します。

以下のソースコードを処理毎に解説していきます。

定数、変数、クラス

定数に関してはコメントで記載している通りなので省略します。

変数(プロパティ)は以下の4つ

・Bitmap[] CellImages
素材を12分割したBitmapオブジェクトの配列です。

・Cell[,] Board
Cellクラス(1マスの状態を管理するクラス)の2次元配列です。
盤面の状態を管理するプロパティです。

・bool IsGameEnd
ゲーム終了(ゲームオーバーorゲームクリア)しているかどうかを判定する為のプロパティです。

・bool IsFirstOpen
始めてマスを開くかどうかを判定するためのプロパティです。
初回は地雷マスにならないようにするために使用します。

クラスは以下の1つ

  • Cellクラス
    1つのマスの状態を管理するクラスです。

    以下の2つのプロパティをもっています。
    1)int State
     マスの状態
      -3:番兵(盤面範囲外を判定するための余白)
      -2:地雷
      -1:開かれていない
      0~8:周囲8マスの地雷の数

    2)bool Flag
     旗が立てられているかどうか

初期化処理~盤面描画処理

・InitializeForm
フォームの初期化処理です。
フォームのサイズ、ピクチャーボックスのサイズ、クリックイベントを設定しています。

・InitializeCellImages
素材で作成したイメージを12分割してCellImagesプロパティに取り込みます。

・InitializeBoard
盤面(Boardプロパティ)を初期化して地雷を配置します。
イメージとしては、以下の様になります。

1)外周の辺のマスは番兵とする。
2)内周のマスは開かれていないマスとする。
3)内周のマスにランダムで10個地雷を設定する。

・DrawBoard
現在の盤面の状態(Boardプロパティの状態)をPictureBoxに描画します。
各マスの状態によって描画する内容を切り替えます。
 1)マスの状態が-3の場合は描画しません。
 2)マスの状態が-2の場合は開かれていないマス(CellImages[10])を描画します。
  ゲームが終了している場合は地雷のマスを公開したいので、地雷のマスを描画します。
 3)マスの状態が-1の場合は開かれていないマス(CellImages[10])を描画します。
 4)マスの状態が0~8の場合は数字(CellImages[0]~[8])を描画します。
 5)マスに旗が立てられている場合は(CellImages[11])を描画します。

初回はすべてのマスが開かれていないマスで描画されることになります。
また、マスがクリックされるたびにこの処理を実行して盤面を更新していきます。

キーダウンイベント、マウスクリックイベント

・Form_KeyDown
Escapeキーが押下された場合はゲームを初期化してリスタートします。

・Board_Click
マスがクリックされた時の処理です。
処理の流れは以下になります。

 1)マウスポインタの座標から、クリックされたマスの位置(2次元配列のインデックス)を計算して取得する。
 2)マウス左クリックの場合
  →クリックしたマスに旗が立っている場合は何もしない。
  →クリックしたマスに旗が立っていない場合、マスを開く処理を実行する。
 3)マウス右クリックの場合
  クリックしたマスに旗を立てて盤面を再描画する。

旗が立っているマスはクリックしても開けない仕様にしていますが、これは公式の仕様とは異なっているかもしれません。
旗のマスでも開けるようにする場合は上記2)処理の旗が立っているかチェックする条件を削除してください。

マスを開く処理~ゲーム終了判定処理

・CellOpen
指定の位置のマスを開く処理です。

1)開くマスが地雷のマスではない場合は、周囲8マスの地雷の数をチェックして自動で開けるマスを開いていきます。
また、自動で開き終わった後、ゲームクリア判定を行います。

開くマスが地雷のマスの場合かつ、マスを開くのが初回の場合は地雷をなかったものとして処理します。
開くマスが地雷のマスの場合かつ、マスを開くのが初回ではない場合はゲームオーバーとします。

・IsGameClear
ゲームクリア判定処理です。
すべてのマスをチェックして、開いていないマスがない場合はゲームクリアと判定します。

・GameEnd
ゲーム終了処理です。
ゲームクリアorゲームオーバーを画面に表示して、地雷のマスを公開します。

・OpenThatCanbeOpend
開けるマスを自動で開く処理です。
マインスイーパーのキモとなる処理だと思います。
文章だけだと説明しづらいので、図を掲載します。

・CountMines
周囲8マスの地雷の数を取得する処理です。

解説で使用しているソースコード

今回作成したマインスイーパーのソースコードは以下のGithubからダウンロードできます。

まとめ

基本的な機能のみしか実装していませんが、マインスイーパーの作り方の紹介でした。

自動で開けるマスを開く処理が少し難解でしたが、それ以外は結構簡単なプログラムなのでプログラミング初心者の方にはオススメの題材だと思いました。

要望があれば、制限時間機能、未開封のマスの数を表示する機能、難易度選択機能等の追加方法も解説しようと思います。

ベタベタとコードを張り付けた解説でしたが、読んでいただきありがとうございました!

C#プログラミング
凡人プログラマーのブログ

コメント