見出し画像

学びの宝庫!ド定番パズル、マインスイーパーを作ろう!クミタテ式プログラミングドリル(p5JavaScript / Processing)

-スキルの腕試しに。

今回作るのはなんの変哲もないマインスイーパーです。
わざわざ自分が作んでも世の中にたくさんありふれているマインスイーパーですが、あえて自分で作る理由は、技術的な腕試しとしても、学びの題材としてもマインスイーパーは優れているからです。
腕に自信がある人も、そうでない人もぜひこの教材でチャレンジしてみてください。

デモアプリはこちらから。https://openprocessing.org/sketch/1667364

- クミタテ式プログラミングドリルとは?

クミタテ式は英語のp5JavaScriptのコードを、日本語で書かれた図解(通称、図解くん)を使って解説し、プラモデルのように図解通りにコードを組み立てていきながら学習するテキスト教材型のプログラミング教材です。

- 動画を見ながら学習する

テキスト教材の補助として動画を作成しています。クミタテ式が初めての方は動画と一緒に学習すると良いでしょう。
動画とこのテキスト教材は期間限定で無料公開しています。たくさんのゲームジャンルのプログラミング方法をお伝えしているのでチャンネル登録もよろしくお願いします。

- 開発環境

OpenProcessingを使ったp5js、Processingを環境を前提としています。


■[ここからスタート!]画面のサイズを決める

画面サイズを600x400の固定サイズにしましょう。


■背景を黄色で塗りつぶし続ける

drawの中で背景を黄色で塗りつぶし続けましょう。drawの中で毎回背景を塗りつぶすことでアニメーションが実現できます。


■四角形のマスブロックを1つだけ表示する

マスブロックを1つだけ表示してみましょう。


■四角形のマスブロックを同じところに100個表示する

マスブロックを100個表示してみましょう。
繰り返し処理forに100回繰り返してもらい、四角形を100個表示します。
(同じ場所に100個表示するので、表示上は1個しか表示されません)


■100個のマスブロックを綺麗に敷きつめる

100個のマスブロックをアルゴリズムで綺麗に10x10に敷きつめましょう。
複雑な計算が入るため難しく感じると思いますが、深く理解する必要はありません。なんとなくの理解で十分です。


■マスブロックに爆弾を埋め込む

100個の配列mapListを用意し、壁マスと爆弾マスの情報を埋め込みます。
99は壁、-1は爆弾です。配列の情報をもとに、壁はグレー色に、爆弾はレッド色に、マスに色をつけていきます。
配列は以下のサンプルをお使いください。

// p5.js
let mapList = [
  99, 99, 99, 99, 99, 99, 99, 99, 99, 99,
  99, -1,  0,  0,  0,  0,  0,  0,  0, 99, 
  99,  0, -1,  0,  0,  0,  0,  0,  0, 99, 
  99,  0,  0, -1,  0,  0, -1,  0,  0, 99, 
  99,  0,  0, -1,  0, -1,  0,  0,  0, 99, 
  99,  0,  0,  0, -1, -1,  0,  0,  0, 99, 
  99,  0,  0,  0,  0, -1,  0,  0,  0, 99, 
  99,  0,  0,  0,  0,  0, -1,  0,  0, 99,
  99,  0,  0,  0,  0,  0,  0, -1,  0, 99,
  99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 
];

// Processing Java
int[] mapList = new int[]{
  99, 99, 99, 99, 99, 99, 99, 99, 99, 99,
  99, -1,  0,  0,  0,  0,  0,  0,  0, 99, 
  99,  0, -1,  0,  0,  0,  0,  0,  0, 99, 
  99,  0,  0, -1,  0,  0, -1,  0,  0, 99, 
  99,  0,  0, -1,  0, -1,  0,  0,  0, 99, 
  99,  0,  0,  0, -1, -1,  0,  0,  0, 99, 
  99,  0,  0,  0,  0, -1,  0,  0,  0, 99, 
  99,  0,  0,  0,  0,  0, -1,  0,  0, 99,
  99,  0,  0,  0,  0,  0,  0, -1,  0, 99,
  99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 
};

■マスブロックに爆弾の数を表示する

マスの周りに爆弾がいくつあるのか、マスの上に爆弾の数を表示していきます。
まずは仮に「0」とだけ表示します。


■上下左右の爆弾の数を計算する関数を作る

マスの上下左右に爆弾がいくつあるのかを数える専用のオリジナル命令、getBombCount関数を作ります。
マス番号となるindexを引数に渡すことで、そのマスの上下左右に爆弾がいくつあるか計算してreturnして返す、関数です。

なお、本来であれば、斜めのマスも爆弾を数えなければいけません。斜めについては応用課題で取り組みます。


■getBombCount関数を使って爆弾数を表示する

先ほど作ったオリジナル命令getBombCount関数を使って爆弾の数を表示しましょう。


■マスの状態ステータスを別配列で管理する

マスはオープンした状態なのか、クローズな状態なのかを管理する配列checkListを用意します。
配列checkListの中身が1ならばオープン状態として、オープン状態なときだけ爆弾数を表示します。

// p5.js
let checkList = [
   0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
   0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
   0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
   0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
   0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
   0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
   0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
   0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
   0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
   0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
];

// Processing Java
int[] checkList = new int[]{
   0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
   0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
   0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
   0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
   0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
   0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
   0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
   0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
   0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
   0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
};

■クリックでマスをオープンする

クリックされた時にどのマスがクリックされたかを位置座標から特定し、そのマスをオープンさせます。


ここから先は図解くんはありません。
自分で頭を整理して課題に取り組んでみてください。

■(★☆☆)課題1. 壁はオープンできないようにする

壁マスをクリック、オープンできてしまいます。
クリックしたマスが壁マスであれば無視するようにしましょう。

■(★☆☆)課題2.斜めも判定しgetBombCountを完成させる

爆弾の数を計算してくれるオリジナル命令getBombCount関数は、上下左右しか数えてくれません。斜めも数えられるように関数を完成させましょう。

■(★★☆)課題3.目印フラッグを立てられるようにする

爆弾があると思われる箇所に目印となるフラッグを立てられるようにしましょう。

■(★★☆)課題4.ゲームオーバーを作ろう

爆弾をチェックしてしまったらゲームオーバーにしましょう。

■(★★☆)課題5.ゲームクリアを作ろう

爆弾以外の全てのマスをチェックしたらゲームクリアにしましょう。

■(★★★)課題6.広範囲オープンを作ろう

あたり一面に爆弾がないようであれば広範囲をオープンしてあげましょう。再起処理が必要で難易度は非常に高いです。

■操作感を向上させよう

マインスイーパーは完成されているゲームであり、ゲーム性に改良の余地はなかなか見つからないと思います。
今回は、ゲーム性ではなく、操作性の向上やサウンドや演出でプレイヤーを楽しませる方向にチャレンジしてみてください。
マインスイーパーは探せばいくつもフリーゲームが見つかります。他のマインスイーパーがどういう点に手を加えているのか観察してみるのも良いでしょう。

完成コード

// p5.js
let mapList = [
  99, 99, 99, 99, 99, 99, 99, 99, 99, 99,
  99, -1,  0,  0,  0,  0,  0,  0,  0, 99, 
  99,  0, -1,  0,  0,  0,  0,  0,  0, 99, 
  99,  0,  0, -1,  0,  0, -1,  0,  0, 99, 
  99,  0,  0, -1,  0, -1,  0,  0,  0, 99, 
  99,  0,  0,  0, -1, -1,  0,  0,  0, 99, 
  99,  0,  0,  0,  0, -1,  0,  0,  0, 99, 
  99,  0,  0,  0,  0,  0, -1,  0,  0, 99,
  99,  0,  0,  0,  0,  0,  0, -1,  0, 99,
  99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 
];
let checkList = [
   0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
   0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
   0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
   0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
   0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
   0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
   0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
   0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
   0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
   0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
];

let check_type = 1;
let gameover_flag = 0;
let gameclear_flag = 0;
function setup() {
	createCanvas(600, 400);
}

function draw() {
	background(255, 200, 0);
	for(let i=0; i<100; i++){
		let x = 150 + i % 10 * 30;
		let y = 30 + int(i / 10) * 30;
		fill(255, 255, 255);
		if(mapList[i] == 99){
			fill(100, 100, 100);
		}
		if(mapList[i] == -1){
			fill(255, 100, 0);
		}
		rect(x, y, 30, 30);
		
		if(checkList[i] == 1){
			textAlign(CENTER, CENTER);
			textSize(16);
			fill(30, 30, 30);
			text(getBombCount(i), x + 15, y + 15);
		}
		if(checkList[i] == 2){
			textAlign(CENTER, CENTER);
			textSize(16);
			fill(255, 100, 0);
			text("!", x + 15, y + 15);
		}
	}
	
	if(gameover_flag == 1){
		textAlign(CENTER, CENTER);
		textSize(64);
		fill(255, 100, 0);
		text("GAME OVER", 300, 200);
	}
	if(gameclear_flag == 1){
		textAlign(CENTER, CENTER);
		textSize(64);
		fill(255, 100, 0);
		text("GAME CLEAR", 300, 200);
	}
}

function mousePressed(){
	for(let i=0; i<100; i++){
		let x = 150 + i % 10 * 30;
		let y = 30 + int(i / 10) * 30;
		if(mouseX > x && mouseX < x + 30){
			if(mouseY > y && mouseY < y + 30){
				if(mapList[i] != 99){
					// どのマスか特定できた
					if(check_type == 1){
						open(i);
					}else{
						checkList[i] = 2;
					}
					
					if(isClear() == 1){
						gameclear_flag = 1;
					}
					if(mapList[i] == -1){
						gameover_flag = 1;
					}
				}
			}
		}
	}
}
function keyPressed(){
	if(check_type == 1){
		check_type = 2;
	}else{
		check_type = 1;
	}
}


function open(index){
	checkList[index] = 1;
	if(getBombCount(index) == 0){
		let directions = [
			0, 1, -1, 10, -10, -11, -9, 9, 11,
		];
		for(let i=0; i<9; i++){
			let target = index + directions[i];
			if(checkList[target] == 0){
				if(mapList[target] == 0){
					open(index + directions[i]);
				}
			}
		}
	}
}

function isClear(){
	for(let i=0; i<100; i++){
		if(mapList[i] == 0){
			if(checkList[i] != 1){
				return 0;
			}
		}
	}
	return 1;
}


function getBombCount(index){
	let directions = [
		0, 1, -1, 10, -10, -11, -9, 9, 11,
	];
	let count = 0;
	for(let i=0; i<9; i++){
		let target = index + directions[i];
		if(mapList[target] == -1){
			count = count + 1;
		}
	}
	return count;
}





この記事が気に入ったらサポートをしてみませんか?