JavaScript:落ちものゲームでブロックが3つ以上そろったら消す

JavaScript

縦と横方向に落ちてきたブロックが3つ以上そろったら消す
落ちている最中にそろっても消さない。ブロックが全て落ちてから消すようにしている。

ソースコード

index.html

<!DOCTYPE html>
<html lang="ja">
<head>
	<meta charset="UTF-8">
	<meta name="viewport" content="width=device-width, initial-scale=1.0">
	<title>OTIMONO(07ブロックを消す)</title>
	<style>
		*{
			margin: 0;
			padding: 0;
		}
		body{
			margin: 0 50px;
		}
	</style>
	<script src="main.js" type="text/javascript"></script>
</head>
<body>
	<canvas id="canvas" width="512" height="512"></canvas>
</body>
</html>

main.js

// 07ブロックを消す
let canvas = null;
let g = null;
let pos = {x:0, y:0};		// キャンバス上のマウス座標
let cpos = {x:0, y:0};		// マウス座標から得られるセル位置
let isMouseDown = false;	// マウスを押しているか?
let dropSpeed = 3;			// 落下速度(10-->1秒, 1-->0.1秒)
let gameTime = {start: 0, end: 0};	// 落下速度計測用

const CELL_SIZE = 64;							// パズルキャラのサイズ(64x64ピクセル)
const ROW_SIZE = COLUMN_SIZE = 512 / CELL_SIZE;	// 行と列に何個並べるか?
const CELL_LENGTH = ROW_SIZE * COLUMN_SIZE;		// パズルキャラの数

// ゲームに使う画像ファイル
const image_files = ["space.png", "alien.png", "apple.png", "dog.png", "obake.png", "ultra.png", "usi.png"];

// 画像オブジェクト(画像ファイルをゲームで利用するため)
let images = [];

// ステージ情報配列
let stageState = new Array(CELL_LENGTH);

/*
 * マウスを動かしている時の処理
 */
const mouseMoveListener = (e) => {
	// オフセット位置:キャンバスがブラウザの左上からどれくらいの位置にあるか?(rect.left, rect.top)
	const rect = e.target.getBoundingClientRect();

	// e.clientXとe.clientYはブラウザ左上からのキャンバスクリック位置なのでオフセット分を引くとキャンバス上の座標が分かる
	pos.x = e.clientX - rect.left;
	pos.y = e.clientY - rect.top;

	// マウス座標からセル位置を求める
	cpos.x = Math.floor(pos.x / CELL_SIZE);
	cpos.y = Math.floor(pos.y / CELL_SIZE);
};

/*
 * マウスボタンを押した時の処理
 */
const mouseDownListener = () => {
	isMouseDown = true;
	// ステージのクリック位置に画像ブロックを設定
	//const index = Math.floor(Math.random() * (images.length-1)) + 1;	// ランダム
	stageState[cpos.x + cpos.y * COLUMN_SIZE] = 1;	// とりあえず1番目のブロック(インベーダー)をセット
	console.log(cpos);
};

/*
 * マウスボタンを離した時の処理
 */
const mouseUpListener = () => {
	isMouseDown = false;
};

/*
 * 背景を表示
 */
const drawBackground = ()=> {
	let x = 0, y = 0;	// 描画するセル位置
	let color1 = "pink", color2 = "skyblue";	// 市松模様のステージ色

	for(i=0; i<CELL_LENGTH; i++){
		// 1段下がると先頭の色を入れ替える
		if(i % 8 == 0) [color1, color2] = [color2, color1];
		// 格子状にブロックを表示
		x = i % COLUMN_SIZE;			// x方向セル位置
		y = Math.floor(i / ROW_SIZE);	// Y方向セル位置

		// 色を交互に設定
		bgColor = (i % 2 == 0) ? color1: color2;
		g.fillStyle = bgColor;
		g.fillRect(x * CELL_SIZE, y * CELL_SIZE, CELL_SIZE, CELL_SIZE);
	}
}

/*
 * カーソルを表示
 */
const drawCursor = ()=> {
	g.fillStyle = "rgba(128, 128, 128, 0.5)";	// グレー色で透過
	g.fillRect(cpos.x * CELL_SIZE, cpos.y * CELL_SIZE, CELL_SIZE, CELL_SIZE);
}

/*
 * ステージを表示
 */
const drawStage = ()=> {
	for(let i=0; i<CELL_LENGTH; i++){
		x = i % COLUMN_SIZE;			// x方向セル位置
		y = Math.floor(i / ROW_SIZE);	// Y方向セル位置

		// ブロックを表示
		g.drawImage(images[stageState[i]], x*CELL_SIZE, y*CELL_SIZE);
		// ステージの状態を表す配列内容を表示(確認用)
		g.fillStyle = "#fffa";
		g.textBaseline = "top";
		g.fillText(stageState[i], x*CELL_SIZE+20, y*CELL_SIZE+20);
	}
}

/*
 * 画像ファイル読み込み
 */
const loadImages = async ()=> {
	const images = [];
	const promises = [];

	image_files.forEach((url) =>{
		const promise = new Promise((resolve)=>{
			const img = new Image();
			img.addEventListener("load", ()=>{
				console.log("loaded " + url);
				resolve();	// 読み込み完了通知
			});
			img.src = "./asset/" + url;	// 画像ファイル読み込み開始
			images.push(img);	// 画像オブジェクト配列に格納
		});
		promises.push(promise);
	});
	await Promise.all(promises);
	return images;
}

/*
 * ステージ情報を生成
 */
const createStage = (stage)=> {
	console.log(stage);
	stageState = [];
	stageState = [...stage];	// ステージ情報をコピー(stageStage <-- stage)
	console.log(stageState);
}

/*
 * ブロックを落とす
 */
const dropBlock = ()=> {
	// 落下タイミングチェック
	gameTime.end = new Date().getTime();
	const passed = (gameTime.end - gameTime.start) / 100;	// 10分の1秒に直す

	if(passed <= dropSpeed){
		return;	// 落下しない
	}

	let dropCount = 0;	// 落としたブロックの数
	for(let y=ROW_SIZE-2; y>=0; y--){	// ステージの下方向からチェック
		for(let x=0; x<COLUMN_SIZE; x++){
			const upIndex = y*ROW_SIZE+x;
			const downIndex = (y+1)*ROW_SIZE+x;
			if(stageState[upIndex] != 0 && stageState[downIndex] == 0){
				// 入れ替え
				[ stageState[upIndex], stageState[downIndex] ]= [ stageState[downIndex], stageState[upIndex] ];
				dropCount++;	// 落とすブロックの数をカウント
			}
		}
	}
	gameTime.start = new Date().getTime();	// 落下タイマー再スタート

	return dropCount;	// 落としたブロック数を返す
}

/*
 * ブロックが消去できるかチェックする(仮)
 */
const checkBlock = () =>{
	// ステージの状態をコピー
	const check = [...stageState];

	// 横方向チェック
	for(let y=0; y<ROW_SIZE; y++){
		for(let x=1; x<COLUMN_SIZE-1; x++){
			const currentIndex = y*ROW_SIZE + x;	// 中央
			const rightIndex = y*ROW_SIZE + (x + 1);// 右側
			const leftIndex = y*ROW_SIZE + (x - 1);	// 左側
			if(stageState[currentIndex] > 0){
				if((stageState[currentIndex] === stageState[rightIndex] && stageState[currentIndex] === stageState[leftIndex])){
					check[currentIndex] = check[rightIndex] = check[leftIndex] = 0;
				}
			}
		}
	}
	// 縦方向チェック
	for(let y=1; y<ROW_SIZE-1; y++){
		for(let x=0; x<COLUMN_SIZE; x++){
			const currentIndex = y*ROW_SIZE + x;	// 中央
			const upIndex = (y-1)*ROW_SIZE + x;	// 上側
			const downIndex = (y+1)*ROW_SIZE + x;	// 下側
			if(stageState[currentIndex] > 0){
				if((stageState[currentIndex] === stageState[upIndex] && stageState[currentIndex] === stageState[downIndex])){
					check[currentIndex] = check[upIndex] = check[downIndex] = 0;
				}
			}
		}
	}

	// チェック後の状態をステージ情報に戻す
	stageState = [...check];
}

/*
 * ゲームのメイン処理(まだゲームではない)
 */
const GameMain = () =>{
	// キャンバスクリア
	g.clearRect(0, 0, canvas.width, canvas.height);
	// ブロックを落とす
	if(dropBlock() == 0){	// 落とすブロックが無くなったなら
		// ブロックの状態をチェック(ブロックが縦と横に3つ以上そろっていれば消す)
		checkBlock();
	}
	// ステージ描画
	drawBackground();
	// カーソル描画
	drawCursor();
	// ステージ表示
	drawStage();
	// フレーム再描画
	requestAnimationFrame(GameMain);
}

/*
 * 起動時の処理
 */
window.addEventListener("load", async ()=>{
	canvas = document.getElementById("canvas");
	g = canvas.getContext("2d");
	g.font = "bold 24px System";	// フォントサイズ・フォント種類 設定

	// マウスイベント設定
	canvas.addEventListener("mousemove", mouseMoveListener, false);
	canvas.addEventListener("mousedown", mouseDownListener, false);
	canvas.addEventListener("mouseup", mouseUpListener, false);

	// 画像ファイルを読み込む
	images = await loadImages(image_files);
	console.log(images);

	// ステージ情報を生成する
	const stage1 = [
		0, 0, 0, 0, 0, 0, 0, 0,
		0, 0, 4, 0, 0, 0, 0, 0,
		0, 0, 4, 0, 0, 0, 0, 0,
		0, 0, 2, 0, 4, 5, 6, 0,
		0, 0, 2, 0, 0, 0, 0, 0,
		0, 0, 1, 1, 0, 0, 0, 0,
		0, 4, 2, 4, 0, 0, 0, 0,
		0, 1, 2, 1, 0, 0, 0, 0
	];
	createStage(stage1);

	// 落下タイマースタート
	gameTime.start = new Date().getTime();

	// ゲームのメイン処理呼び出し(ゲーム開始)
	GameMain();
});

関連記事

コメント

タイトルとURLをコピーしました