マムシ、アオダイショウ、ヤマカガシ、・・・、いきなりニョロニョロと動くものに出遭っても驚くし、トグロを巻いて静かに休んでいる姿を見つけても、ゾクッと悪寒が走る。毒を持たない種類にしても、大蛇を首に巻いて上機嫌な人の気が知れない。どうしても嫌悪感が先に立つ。マムシ酒もハブ酒も興味なし。ヘビの抜け殻も要りません。
そんな前置きは置いといて、日経ソフトウェア2014年6月号「HTML5でゲームを作ろう(第7回)」に掲載された「スネークゲーム」です。
右の画像をクリックすると実際のゲームページを表示します。
ソースはほぼオリジナルのままですが、キー入力をすると、FireFoxの開発ツール(Webコンソール)上に「getPreventDefault() の使用は推奨されません。代わりに defaultPrevented を使用してください。」と警告表示が。
試しに、snake.js(120行目)の、e.preventDefault(); を e.defaultPrevented; に変更してみたものの、警告が消えてくれないようで、どうすべきか解らなかったので、元に戻しておきました。
ゲームのルールはスタート画面のとおり。
ゲームを開始するには、画面下部にある「START」ボタンをクリックします。キー入力では開始しません。
ゲームクリア後、次のレベルのゲームを開始する場合は、キー入力(「Enter」キー)でも可能です。
レベルが上がるとスピードが早くなり、運悪く画面端に近いところから頭を出し、なおかつ、外側の壁に向かって動き始めた場合は、何もできずゲームオーバーになってしまいます。
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";
}
}
}