15 PUZZLE Ver.1.02(MENU追加)

20131116a

これまでは、画像をプレーヤーが選択できず、ゲーム開始時にランダム選択としていました。その為、特定の画像を選ぶには、何度かブラウザで再読み込みが必要でした。

今回は、最初に、画像を選択するメニュー画面「15 PUZZLE MENU (写真16枚)」を表示するようにしました。

前回から更に6枚追加し、全16枚から選んで開始します。

実を言うと、この画面の画像は16枚の写真を縮小表示しているのではなく、まとめた画像1枚を表示しています。

Canvasの場合、画像のロードが完了していなくても描画処理を始めてしまうため画像が表示されない場合がありました。なぜかFirefoxは正常表示。複数画像のプレロード処理は対応が面倒そうなのと、表示に時間がかかりそうだったので、今回は複数画像の縮小表示はあきらめました。(単画像のプレロードは行っています。・・・15-puzzle102.jsの45~47行)

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

20131116b



メニュー画面で選んだ写真を表示し、これまでと同じ15パズルのゲームが開始されます。

上部タイトルは「15 PUZZLE」に変わります。

空白ブロック上の数字「0」の表示は必要ないと判断し非表示に。

見た目がスッキリしたと思います。

また、シャッフルのスピードを速くしました。

20131116c



ゲームをクリアすると上部タイトルが「15 PUZZLE CLEAR !」に変わります。

前回は3秒間だけ完成した画像を表示し、その後ポップアップ(alert)で「ゲームクリアしました!」を表示していましたが、これは廃止。

今回は、時間制限無く、完成した画像は表示したままに変更。

ただし、画像をクリックすると、最初のメニュー画面に戻ります。

だんだんと判読しにくくなってきたソースはこちらです。
メニュー用単画像をキャプチャーする為の関数(15-puzzle102.jsの49~69行)を入れています。


<!doctype html>
<html lang="ja">
<head>
	<meta charset="utf-8">
	<title>15パズル</title>
	<meta name="viewport" content="width=600">
	<link rel="stylesheet" type="text/css" href="../css/15-puzzle.css">
	<script type="text/javascript" src="../js/15-puzzle102.js">
	</script>
</head>
<body onload="init()">
	<div id="top_title"></div>
	<canvas id="gameCanvas" width="600" height="600"></canvas>
</body>
</html>

// 各種設定
var PICTURES = ["nobana.jpg", "risu.jpg", "matterhorn.jpg", "beach.jpg", "foods.jpg","Blue_Moon.jpg","golden_gate_bridge.jpg","orgel.jpg","rose.jpg","sweets.jpg","Space_Man.jpg","holiday.jpg","cat.jpg","dog.jpg","Canadian_Rockies.jpg","earth.jpg"];
var PICTURE_URL = "nobana.jpg";	// 画像ファイルのURL
var menu_msg = "<h1>15 PUZZLE MENU (写真" + PICTURES.length + "枚)</h1>";	// MENUのタイトル
var play_msg = "<h1>15 PUZZLE</h1>";	// PUZZLEのタイトル
var clear_msg = "<h1>15 PUZZLE CLEAR !</h1>";	// PUZZLE クリア時のタイトル
var BLOCK_W = 150;	// ブロックの幅
var BLOCK_H = 150;	// ブロックの高さ
var ROW_COUNT = 4;	// 列を何枚に切るか
var COL_COUNT = 4;	// 行を何枚に切るか
var NUM_BLOCKS = ROW_COUNT * COL_COUNT;
// 上下左右の相対座標を定義したもの
var UDLR = [[0,-1],[0,1],[-1,0],[1,0]];
// ゲーム全体で使う変数
var context, image, selImage;	// 描画用
var selBlocks = [];	// 各ブロックを管理する配列変数
var blocks = [];	// 各ブロックを管理する配列変数
var isLock;	// マウス操作をロックするかどうか
var selFlag = true;	// 画像選択フラグ
var clearFlag = false;	// ゲームクリアフラグ
// 初期化処理
function init() {
	// 描画コンテキストの取得
	var canvas = document.getElementById("gameCanvas");
	if (!canvas.getContext) {
		alert("Canvasをサポートしていません。");
		return;
	}
	context = canvas.getContext("2d");
	// マウスイベントの設定
	canvas.onmousedown = mouseHandler;
	// 画像を選択
	selectImage();	// ■画像1枚でMENU表示へ
	// メニュー用画像を縮小表示
 // 画像キャプチャー用(使用時は上記selectImageを無効に下記を有効に)
 // drawMenu();	// ■PICTURES縮小でNEMU表示へ
}
// ゲーム画像の選択画面(MENU)を表示
function selectImage() {
	document.getElementById('top_title').innerHTML = menu_msg;
	selFlag = true;
	// 画像を描画
	selImage = new Image();
	selImage.src = "menu.jpg?" + new Date().getTime();	// IE対策
	selImage.onload = function() {
		context.drawImage(selImage, 0, 0);
	}
}
// ゲーム開始時の画面を描画する(画像キャプチャー用)
// この関数はMENU画像作成時にのみ使用
function drawMenu() {
	document.getElementById('top_title').innerHTML = menu_msg;
	for (var i = 0; i < PICTURES.length; i++) {
		// 描画先座標を計算
		var dx = (i % COL_COUNT) * BLOCK_W;
		var dy = Math.floor(i / COL_COUNT) * BLOCK_H;
		selImage = new Image();
		selImage.src = PICTURES[i];
		// 画像を縮小して描画
		context.drawImage(selImage, 0, 0, 600, 600, dx, dy, BLOCK_W, BLOCK_H);
		// 描画の枠を表示
		context.beginPath();
		context.strokeStyle = "white";
		context.lineWidth = 3;
		context.rect(dx, dy, BLOCK_W, BLOCK_H);
		context.stroke();
		context.closePath();
	}
}
// ゲーム開始
function gameStart() {
	document.getElementById('top_title').innerHTML = play_msg;
	// メイン画像を読み出す
	image = new Image();
	image.src = PICTURE_URL;
	image.onload = initGame;	// 読み込んだらゲームを初期化
}
// ゲームの初期化
function initGame() {
	isLock = true;	// ユーザ操作をロックする
	// パズルのブロックを作成する
	for (var i = 0; i < NUM_BLOCKS; i++) {
		blocks[i] = i;
	}
	// 末尾(右下)を空きブロックとする
	blocks[NUM_BLOCKS -1] = -1;
	drawPuzzle();	// 見本を表示する
	// 1秒後にシャッフルを開始する
	setTimeout(shufflePuzzle,1000);
}
// パズルの各ピースをシャッフルする
function shufflePuzzle() {
	var scount = 160;	// シャッフルする回数を指定
	var blank = NUM_BLOCKS - 1;	// 空きブロック位置
	// 一回のみシャッフルを行う関数
	var shuffle = function () {
		scount--;
		if (scount <= 0) {
			isLock = false;	// ゲーム開始
			return;
		}
		var r, px, py, no;
		while (1) {
			r = Math.floor(Math.random() * UDLR.length);
			px = getCol(blank) + UDLR[r][0];
			py = getRow(blank) + UDLR[r][1];
			if (px < 0 || px >= COL_COUNT) continue;
			if (py < 0 || py >= ROW_COUNT) continue;
			no = getIndex(px, py);
			break;
		}
		blocks[blank] = blocks[no]
		blocks[no] = -1;
		blank = no;
		drawPuzzle();
		setTimeout(shuffle, 2);
	};
	shuffle();
}
// パズルの画面を描画する
function drawPuzzle() {
	for (var i = 0; i < NUM_BLOCKS; i++) {
		// 描画先座標を計算
		var dx = (i % COL_COUNT) * BLOCK_W;
		var dy = Math.floor(i / COL_COUNT) * BLOCK_H;
		// 描画元座標を計算
		var no = blocks[i];
		if (no < 0) {	// 空きブロック
			context.fillStyle = "#0000FF";
			context.fillRect(dx, dy, BLOCK_W, BLOCK_H);
		} else {
			var sx = (no % COL_COUNT) * BLOCK_W;
			var sy = Math.floor(no / COL_COUNT) * BLOCK_H;
			// 画像の一部を切り取って描画
			context.drawImage(image, sx, sy, BLOCK_W, BLOCK_H, dx, dy, BLOCK_W, BLOCK_H);
		}
		// 描画の枠を表示
		context.beginPath();
		context.strokeStyle = "white";
		context.lineWidth = 3;
		context.rect(dx, dy, BLOCK_W, BLOCK_H);
		context.stroke();
		context.closePath();
		// ブロック番号を描画する
		context.fillStyle = "rgba(255, 255, 255, 0.8)";
		context.font = "bold 48px Arial";
		var cx = dx + (BLOCK_W - 40) / 2;
		var cy = dy + BLOCK_H /2;
		if (no != -1){
			context.fillText((no+1), cx, cy);
			context.strokeStyle = "black";
			context.strokeText((no+1), cx, cy);
		}
	}
}
// マウスで移動先をクリックした時の処理
function mouseHandler(t) {
	if (isLock) return;
	if (selFlag) {	// ----- MENU のマウス処理 -----
		// タッチ座標の取得
		var px = t.offsetX, py = t.offsetY;
		if (px == undefined) {	// FireFox対策
			var p = t.currentTarget;
			px = t.layerX - p.offsetLeft;
			py = t.layerY - p.offsetTop;
		}
		// 何番目の画像をクリックしたのか計算する
		var px2 = Math.floor(px / BLOCK_W);
		var py2 = Math.floor(py / BLOCK_H);
		var selNo = getIndex(px2, py2);
		PICTURE_URL = PICTURES[selNo];
		selFlag = false;
		gameStart();
	}else{
		if (clearFlag) {	// ----- CLEAR 時の処理 -----
			clearFlag = false;
			selectImage();	// 再度ゲームを実行
		}else{	// ----- PUZZLE プレイ中のマウス処理 -----
			// タッチ座標の取得
			var px = t.offsetX, py = t.offsetY;
			if (px == undefined) {	// FireFox対策
				var p = t.currentTarget;
				px = t.layerX - p.offsetLeft;
				py = t.layerY - p.offsetTop;
			}
			// 何番目のピースを動かしたいのか計算する
			var px2 = Math.floor(px / BLOCK_W);
			var py2 = Math.floor(py / BLOCK_H);
			var no = getIndex(px2, py2);
			// 空白ブロックなら動かせない
			if (blocks[no] == -1) return;
			// 上下左右に動かせるブロックがあるか確認
			for (var i = 0; i < UDLR.length; i++) {
				var pt = UDLR[i];
				var xx = px2 + pt[0];
				var yy = py2 + pt[1];
				var no = getIndex(xx, yy);
				if (xx < 0 || xx >= COL_COUNT) continue;
				if (yy < 0 || yy >= ROW_COUNT) continue;
				if (blocks[no] == -1) {	// 移動可能か
					blocks[no] = blocks[getIndex(px2,py2)];
					blocks[getIndex(px2,py2)] = -1;
					drawPuzzle();
					checkClear();
					break;
				}
			}
		}
	}
}
// クリアしたかどうかチェックする
function checkClear() {
	var flag = true;
	for (var i = 0; i < (NUM_BLOCKS -1); i++) {
		if (blocks[i] != i) { flag = false; break; }
	}
	if (flag) {
		document.getElementById('top_title').innerHTML = clear_msg;
		context.drawImage(image, 0, 0);	// 元画像を描画する
		clearFlag = true;
	}
}
// 列と行からブロック番号を調べる関数
function getIndex(col, row) {
	return row * COL_COUNT + col;
}
function getCol(no) { return no % COL_COUNT; }
function getRow(no) {
	return Math.floor(no / COL_COUNT);
}

* {
	padding:0;
	margin:0;
	text-align:center;
}

h1 {
	background-color:blue;
	color:white;
	font-size:20px;
	padding:4px;
}