スネークゲーム

マムシ、アオダイショウ、ヤマカガシ、・・・、いきなりニョロニョロと動くものに出遭っても驚くし、トグロを巻いて静かに休んでいる姿を見つけても、ゾクッと悪寒が走る。毒を持たない種類にしても、大蛇を首に巻いて上機嫌な人の気が知れない。どうしても嫌悪感が先に立つ。マムシ酒もハブ酒も興味なし。ヘビの抜け殻も要りません。

そんな前置きは置いといて、日経ソフトウェア2014年6月号「HTML5でゲームを作ろう(第7回)」に掲載された「スネークゲーム」です。

20140428a

右の画像をクリックすると実際のゲームページを表示します。

ソースはほぼオリジナルのままですが、キー入力をすると、FireFoxの開発ツール(Webコンソール)上に「getPreventDefault() の使用は推奨されません。代わりに defaultPrevented を使用してください。」と警告表示が。

試しに、snake.js(120行目)の、e.preventDefault(); を e.defaultPrevented; に変更してみたものの、警告が消えてくれないようで、どうすべきか解らなかったので、元に戻しておきました。

20140428b

ゲームのルールはスタート画面のとおり。

ゲームを開始するには、画面下部にある「START」ボタンをクリックします。キー入力では開始しません。

ゲームクリア後、次のレベルのゲームを開始する場合は、キー入力(「Enter」キー)でも可能です。

レベルが上がるとスピードが早くなり、運悪く画面端に近いところから頭を出し、なおかつ、外側の壁に向かって動き始めた場合は、何もできずゲームオーバーになってしまいます。

title

GIMP2.8で制作したtitle.pngはこちら。

今回のスネークゲームは、右に曲がることしかできません。

しかし、下記ブログで紹介されている「スネークゲームを録画したGIFアニメ」のように、右にも左にも曲がることができるが、進むべき空間が無くなって自身の体にぶつかるとゲームオーバーとなるものもあるようです。

スネークゲームを最後までクリアするとこうなる | ライフ×メモライフ×メモ

むしろ、これこそがアーケードゲームでは一般的だったのでしょう。・・・年代は該当するかも知れないが、昔、ゲームセンターでも「スネークゲーム」は見た記憶がない。ブロック崩しやインベーダーほどメジャーではなかったのでしょう。

これと同じ仕様のスネークゲームにホイホイっと改造できる人も居るのでしょうが、我輩には無理・・・。



ソースはこちら

<!DOCTYPE html>
<html>
<head>
	<meta charset="UTF-8">
	<title>Snake Game</title>
	<script src="snake.js" type="text/javascript" ></script>
	<style>
		body {
			text-align:center;
			background-color:green;
			color:white;
		}
		button {
			width:400px;
			height:32px;
		}
	</style>
</head>
<body>
	<h1 id="info">Snake Game</h1>
	<!-- ゲーム画面用 -->
	<div id="game" data-role="page">
		<canvas id="aCanvas" width="400" height="400"></canvas><br>
		<button id="rButton">方向転換</button>	
	</div>
	<!-- タイトル画面 -->
	<div id="title" data-role="page">
		<img src="title.png" alt="title"><br>
		<button id="startButton">START</button>
	</div>
</body>
</html>
// snake.js
// 定数の宣言(大文字の変数を定数として扱う)
var COLS = 20;	// 画面の列数
var ROWS = 20;	//	画面の行数
var TILE_W = 400 / COLS;
var TILE_H = 400 / ROWS;
var DEFAULT_WAIT_TIME = 300;	// ヘビの動作間隔
// 移動方向を表すテーブル
var DIR_TBL = [[0,-1],[1,0],[0,1],[-1,0]];
// 変数の宣言
var aCanvas;		// キャンバス
var ctx;			// 描画コンテキスト
var dir = 0;		// ヘビの進行方向
var waitTime = DEFAULT_WAIT_TIME;
var sx,sy;			// ヘビの頭の位置
var snakeArray = [];	// ヘビの頭の軌跡(しっぽ)
var snakeLen;
var ax,ay;			// リンゴの位置
var needRotate = false;	// ヘビ回転要求
var level = 1;
// 初期化処理
window.onload = function () {
	// 描画コンテキストの取得
	aCanvas = $("aCanvas");
	ctx = aCanvas.getContext("2d");
	// キーボードイベントの設定
	window.onkeydown = keyHandler;
	// 方向転換ボタンの設定
	$("rButton").onclick = rButtonHandler;
	initGame();
};
// ゲームの初期設定を行う
function initGame() {
	// ゲームのパラメータを初期化
	waitTime = DEFAULT_WAIT_TIME;
	level = 1;
	snakeLen = 4;
	// タイトル画面を表示
	showPage('title');
	$("info").innerHTML = "Snake Game";
	$("startButton").onclick = function () {
		showPage('game');
		nextGame();
	};
}
function nextGame() {
	// ヘビの初期位置と進行方向を決定
	sx = rnd(COLS-2);
	sy = rnd(ROWS-2)+1;
	dir = (sx > (COLS/2)) ? 3 : 1;
	snakeArray = [[sx, sy]];
	// リンゴの位置を決定
	ax = rnd(COLS);
	ay = rnd(ROWS);
	// 現在のレベルを表示
	$("info").innerHTML = "Snake Game level." + level;
	drawScreen();
	setTimeout(gameLoop, 100);
}
function gameLoop() {
	// ユーザからの入力があったか?
	if (needRotate) {
		needRotate = false;
		dir = (dir + 1) % 4;
		console.log("dir=" + dir);
	}
	// ヘビの頭の移動
	var x = sx + DIR_TBL[dir][0];
	var y = sy + DIR_TBL[dir][1];
	if (0 <= x && x < COLS && 0 <= y && y < ROWS) {
		sx = x; sy = y;
		snakeArray.unshift([x,y]);
		snakeArray = snakeArray.slice(0,snakeLen);
	}else{
		alert("Game Over");
		initGame();
		return;
	}
	drawScreen();
	// レベルクリア?
	if (sx == ax && sy == ay) {
		alert("Level Clear!");
		level++;				// レベルを上げる
		snakeLen += 2;		// しっぽを長くする
		waitTime *= 0.8;	// 移動を早くする
		nextGame();
		return;
	}
	setTimeout(gameLoop, waitTime);
}
function drawScreen() {
	// 背景の描画
	ctx.clearRect(0,0,400,400);
	ctx.fillStyle="white";
	ctx.fillRect(0,0,400,400);
	// ヘビの描画
	ctx.fillStyle = "green";
	for (var i in snakeArray) {
		var x = snakeArray[i][0];
		var y = snakeArray[i][1];
		ctx.fillRect(x * TILE_W, y * TILE_H, TILE_W, TILE_H);
	}
	// リンゴの描画
	ctx.fillStyle = "red";
	var w2 = TILE_W / 2;
	fillCircle(ax * TILE_W + w2, ay * TILE_W + w2, w2);
}
function fillCircle(x, y, r) {
	ctx.beginPath();
	var w2 = TILE_W / 2;
	ctx.arc(x, y, r, 0, Math.PI*2, false);
	ctx.fill();
}
// キー入力
function keyHandler(e) {
	var k = e.keyCode;
	console.log("key=" + k);
	if (k == 13 || k == 32) {
		needRotate = true;
		e.preventDefault();
	}
}
// ボタン入力
function rButtonHandler(e) {
	needRotate = true;
}
// 整数の乱数を返す
function rnd(n) {
	return Math.floor(Math.random() * n);
}
// DOM要素の取得
function $(id) {
	return document.getElementById(id);
}

// 指定のページを表示
function showPage(pageId) {
	var ps = document.querySelectorAll("[data-role='page']");
	for (var i = 0; i < ps.length; i++) {
		var e = ps[i];
		if (e.id == pageId) {
			e.style.display = "block";
		}else{
			e.style.display = "none";
		}
	}
}