プロデルでマインスイーパを作る

# 地雷探しゲームを作る

今回は、日本語プログラミング言語「プロデル」でマインスイーパを作ってみます。

## マインスイーパとは
マインスイーパは、地面に埋められた地雷を探すゲームです。
このゲームでは、地面の1マスをクリックで解放して、地面の数字を手がかりに地雷がある場所を避けながら、すべてのマスを解放していきます。
かつてWindows 7までは標準にインストールされているゲームだったこともあり、当時は誰でも知っているゲームでした。しかしWindows 8以降は無くなってしまったので馴染みがない方もいるかと思います。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/101225/ec8d29b4-d6c3-42ae-fa0e-2df49ce46d8d.png)

##ロジックを考える
マインスイーパゲームでは、主に次のようなプログラムを作る必要があります。

- マス目の配置
- 地雷の配置
- 隣接する場所での個数の設定
- クリックしたマスの解放と地雷の爆発
- 安全地帯の解放

###マスの目配置
地面には、8×8のマスを敷き詰めます。マスには、安全なマスと地雷が落ちているマスとがあります。また、安全なマスには、マスの付近にいくつ地雷があるかを表す数字が書かれています。さらに、マスが解放されているとマスの色が変化します。

上記のマスの状態をまとめて、マスごとに次のような情報を変数で持たせることにします。

- 種別:整数 (0なら安全, 1以上は付近にある地雷の数,-1なら地雷,-2なら領域外)
- 解放済:真偽値 (マスが解放されたかどうか)
- 座標:配列 (マスがある座標)

これらの情報は「マス」種類で定義することにします。「マス」種類は、「子キャンバス」部品を継承します。「子キャンバス」部品を継承すると、マスのオブジェクトをそのままキャンバスの図形として扱うことができます。

一方で「マス」種類のオブジェクトは、「地雷区画」という10×10の2次元配列に入れておきます。ゲーム上は8×8のマスですが、それらを囲むように縦横それぞれ2マス分多く確保しておきます。これら周囲のマスは、「種別」変数を領域外を表す-2としておきます。このような壁を“番兵”と呼びます。番兵を作っておくと、この後で行う、周囲の地雷数を数える時や安全なマスの解放の時に、もし文で書く条件が簡単になります。

### 地雷の配置
最初に地雷区画に地雷を配置しておきます。今回は5つの地雷を埋めることにします。
地雷を埋めるために「地雷を配置する」手順(47行目)を定義します。
どこのマスに地雷を埋め込むかは、```(1からマス数までの乱数)+1```を横方向(X)と縦方向(Y)でそれぞれ決めて乱数で決めます。番兵のマスを除くために乱数の結果に+1しておきます。

地雷を配置する時には、条件がない繰り返す文を使います。ここでは、地雷が同じマスに配置しないように、乱数で決めたマスにすでに地雷が配置されているかどうか調べて、配置されていれば、再度場所を決めます。つまり繰り返し文になっていますが、地雷が被らなければ繰り返しは1回となります。

```マインスイーパ.rdr(47行目)
繰り返す
	X=(1からマス数までの乱数)+1
	Y=(1からマス数までの乱数)+1
	もし地雷区画(X,Y)の種別が-1でないなら
		地雷区画(X,Y)の種別=-1
		繰り返しを抜け出す
	もし終わり
繰り返し終わり
```

なお、マスの種別を確認するために、次のようなプログラムで、マスの地雷の位置や周囲の地雷の数を表示できます。プログラムの誤りを見つける時に、このようなプログラムがあると便利です。 

```
//「地雷を配置する」手順の末尾
マス数+2回Yにカウントして繰り返す
	マス数+2回Xにカウントして繰り返す
		地雷区画(X,Y)の種別を改行せず報告
		「 」を改行せず報告
	繰り返し終わり
	「」を報告
繰り返し終わり
```

### 隣接する場所での個数の設定
このゲームでは、地雷の周囲のマスに近くに地雷があることを表す数字が隠されています。
隠された数字を設定するために、地雷を配置した時に周囲のマスに地雷がある数(種別)を加算していきます。

周囲のマスに加算するために繰り返し文を使います。次のプログラムのDX,DYは、地雷のマスの上下左右を表すカウンタ変数です。DXが-1だと左側、DYが1だと下側、といった具合に地雷の周囲を繰り返しながら数を加算していきます。

```マインスイーパ.rdr(57行目)
-1から1までDYにカウントして繰り返す
	-1から1までDXにカウントして繰り返す
		対象は、地雷区画(X+DX,Y+DY)
		もし対象の種別が0未満なら繰り返しを続ける
		対象の種別を増やす
	繰り返し終わり
繰り返し終わり
```

### クリックしたマスの解放と地雷の爆発
プレーヤーがマスをクリックした時には、「マスクリックされた」手順が実行されます。「発生元」特殊変数には、クリックした時の「マス」種類のオブジェクトが格納されています。
発生元の種別が-1(地雷)なら、「爆発する」手順を実行して、他の地雷も表示してゲームオーバーとします。「爆発する」手順では、地雷区画を左上から右下へすべて調べていき、種別が-1のマスについては、背景色を赤色に変えて、地雷のマスを爆発させます。

種別がそれ以外の場合は、そのマスを解放します。解放するときには、マスの「解放済」に○を入れます。種別が0より大きい時には、隠された数字を表示します。また、種別が0(安全地帯)の場合には、上下左右の安全地帯のマスを解放していきます。

### 安全地帯の解放
プレーヤーがクリックしたマスを解放するための「[座標]を解放する」手順を定義します。
クリックしたマスが安全地帯(種別が0)の場合には、さらに上下左右の4マスを辿って安全地帯なら解放していきます。この場合、その先の安全地帯を解放するために、この手順で同じ「解放する」手順を再び呼び出します。このように自分の手順で自分の手順を実行することを再帰といいます。

:::note info
再帰は、この手順で実行している処理を残したまま、同じ手順で別の処理を実行する状態のことを言います。ただし、無条件に自分の手順で自分の手順を実行すると、いつまでも最初の処理が終わらず、無限ループとなってしまいますので、再帰を利用する時は、特定の条件を満たす時だけ手順を呼び出すようにプログラムを作ります。
:::

「解放する」手順では、あるマスの上下左右の4マスで安全地帯であるマスを解放します。その先のマスについても上下左右をさらに調べて、番兵や地雷周囲のマスに当たるまで、安全地帯を解放していきます。なお、次のプログラムのXとYは、解放したマスの座標を表します。

```マインスイーパ.rdr(111行目)
もし地雷区画(X,Y+1)の種別が0なら{X,Y+1}を解放する
もし地雷区画(X+1,Y)の種別が0なら{X+1,Y}を解放する
もし地雷区画(X,Y-1)の種別が0なら{X,Y-1}を解放する
もし地雷区画(X-1,Y)の種別が0なら{X-1,Y}を解放する
```

## 完成したプログラム

```マインスイーパ.rdr
//マインスイーパ//
地雷区画={}
ゲーム中=○
メイン画面を表示する
待機する

メイン画面とは
	ウィンドウを継承する
	マスサイズ=20
	地雷数=5
	マス数=8
	解放数=0
はじめの手順
	初期化する
	ーー貼り付けた部品に対する操作をここに書きます
	マス数+2回Yにカウントして繰り返す
		マス数+2回Xにカウントして繰り返す
			マス(キャンバス1)を作って地雷区画(X,Y)とする
			キャンバス1に地雷区画(X,Y)を加える
				その位置と大きさは｛X*マスサイズ,Y*マスサイズ,マスサイズ+1,マスサイズ+1｝
				その線色を灰に変える
				その太さを1に変える
				その背景色をネズミに変える
				そのクリックされた時の手順は、マスクリック
			地雷区画(X,Y)へ文字を描いて地雷区画(X,Y)のラベルとする
				その大きさ調整を×に変える
				そのフォントを「メイリオ,10,太字」に変える
				その文字色を緑色に変える
				その文字配置を「中央」に変える
				その位置と大きさは｛0,0,マスサイズ+1,マスサイズ+1｝
				その表示を×に変える
			地雷区画(X,Y)の座標={X,Y}
			もしXが1またはXがマス数+2なら
				地雷区画(X,Y)の種別=-2
				地雷区画(X,Y)の解放済は、○
				地雷区画(X,Y)の表示を×に変える
			他でもしYが1またはYがマス数+2なら
				地雷区画(X,Y)の種別=-2
				地雷区画(X,Y)の解放済は、○
				地雷区画(X,Y)の表示を×に変える
			そうでなければ
				地雷区画(X,Y)の種別=0
			もし終わり
		繰り返し終わり
	繰り返し終わり
	地雷を配置する
終わり
地雷を配置する手順
	地雷数回iにカウントして繰り返す
		繰り返す
			X=(1からマス数までの乱数)+1
			Y=(1からマス数までの乱数)+1
			もし地雷区画(X,Y)の種別が-1でないなら
				地雷区画(X,Y)の種別=-1
				繰り返しを抜け出す
			もし終わり
		繰り返し終わり
		-1から1までDYにカウントして繰り返す
			-1から1までDXにカウントして繰り返す
				対象は、地雷区画(X+DX,Y+DY)
				もし対象の種別が0未満なら繰り返しを続ける
				対象の種別を増やす
			繰り返し終わり
		繰り返し終わり
	繰り返し終わり
終わり
	初期化する手順
	ーー自動生成された手順です。ここにプログラムを書き加えても消える場合があります
		この実質大きさを｛428,394｝に変える
		この内容を「マインスイーパ」に変える
		初期化開始する
		キャンバス1というキャンバスを作る
			その位置と大きさを｛0,0,428,394｝に変える
			その移動順を1に変える
			そのドッキング方向を「全体」に変える
		初期化終了する
		この設計スケール比率を｛144,144｝に変える
終わり
マスクリックされた手順
	もしゲーム中でないなら返す
	もし発生元の種別が-1なら
		爆発する
		ゲーム中は、×
	そうでなければ
		発生元の座標を解放する
	もし終わり
	キャンバス1を再描画する
終わり
[座標]を解放する手順
	【X】=座標(1)
	【Y】=座標(2)
	もし地雷区画(X,Y)の解放済なら返す
	地雷区画(X,Y)の解放済は、○
	地雷区画(X,Y)の背景色を白色に変える
	解放数を増やす
	もし解放数がマス数*マス数-地雷数なら
		「おめでとうございます！クリアしました」を表示する
		ゲーム中は、×
		返す
	もし終わり
	もし地雷区画(X,Y)の種別が0より大きいなら
		種別は、地雷区画(X,Y)の種別
		ラベルは、地雷区画(X,Y)のラベル
		ラベルの内容を種別に変える
		もし種別が1ならラベルの文字色は、青
		他でもし種別が2ならラベルの文字色は、緑
		他でもし種別が3ならラベルの文字色は、赤
		他でもし種別が4ならラベルの文字色は、紺
		ラベルの表示を○に変える
	そうでなければ
		もし地雷区画(X,Y+1)の種別が0なら{X,Y+1}を解放する
		もし地雷区画(X+1,Y)の種別が0なら{X+1,Y}を解放する
		もし地雷区画(X,Y-1)の種別が0なら{X,Y-1}を解放する
		もし地雷区画(X-1,Y)の種別が0なら{X-1,Y}を解放する
	もし終わり
終わり
爆発する手順
	Y=2
	マス数+2回Yにカウントして繰り返す
		X=2
		マス数+2回Xにカウントして繰り返す
			もし地雷区画(X,Y)の種別が-1なら
				地雷区画(X,Y)の背景色を赤色に変える
			もし終わり
		繰り返し終わり
	繰り返し終わり
	キャンバス1を再描画する
終わり
終わり
マスとは
	子キャンバスを継承する
	+座標
	-内種別
	+解放済
	+ラベル
	はじめ(キャンバス)の手順=(キャンバス)
	終わり
	種別という属性
		取得する手順
			内種別を返す
		終わり
		設定する手順
			内種別=設定値
			もし解放済なら背景色は「灰色」
			他でもし設定値が-2なら背景色は「白色」
			そうでなければ背景色は「ネズミ」
		終わり
	終わり
終わり
```

## まとめ

今日は、マインスイーパを作ってみました。ゲームそのものはシンプルですが、いつまででも遊んでいられる飽きないゲームの一つかと思います。
このようなマスを使ったパズルゲームでは、再帰処理が使われることがほとんどです。再帰は、プログラミングを勉強するときに難しい事柄の一つですが、今回の例を通じて理解できたら嬉しいです。
一方で、マス目を増やしてみたり、凝ったデザインの部品を作るのもプログラミングの楽しみ方だと思いますので、ぜひこのマインスイーパを改造して遊んでみてください。

## 参考ページ
https://produ.irelang.jp/blog/2020/04/1987/

https://produ.irelang.jp/blog/2020/05/89/