箱入り娘パズル Ver.1.03

箱入り娘と言っても、竈門 禰豆子(かまど ねずこ)の事ではありません。

・・・「箱入り娘」で画像検索したら「ねずこ」がヒットしてワロタ・・・。

今回パズルの方は、以下の2点を改良しただけで、使用画像は前回と変えていません。

  • タブレット端末での表示が小さかったので、大きく表示されるように改善。(ただし、自分の持っている端末でしか判断できていませんので、悪しからず。)
  • ゲームクリア後に、駒の選択(と移動)ができてしまうバグを改修。

以下の画像をクリックすると実際のゲームを表示します。
タブレットでの表示を改善しました! Ver.1.02 → Ver.1.03

HTMLのmeta要素で、viewportの設定を変えてみました。

これまでより、駒をタップしやすくなったと思います。

(縦持ちの画面に合わせています。横持ちでははみ出します。)

(あくまでも、自分の端末での表示です。解像度によっては小さくなるかも知れません。)

スマホの表示も多少改善されました! Ver.1.02 → Ver.1.03

ちなみに、スマートフォンでの表示も、若干大きくなりました。

(こちらも、縦持ちの画面に合わせています。横持ちでははみ出します。)

(あくまでも、自分の端末での表示です。端末によっては問題があるかも知れません。)

ソースはこちら
hakoiri_103.html
  • (5行目)
    viewpointのcontentで、width=640、user-scalable=no としています。
    PC以外(タブレットやスマホ)では、ここの設定が適用されます。
    これが最適解かは不明です。
  • (7行目)
    ヘッダ内で「hakoiri_103.js」を指定しています。
  • (12行目)
    aCanvasエリアのブロック指定と、センタリングの為にmargin-rightとmargin-leftにautoを指定。
<! DOCTYPE html>
<html>
	<head>
		<meta charset="UTF-8">
		<meta name="viewport" content="width=640,user-scalable=no" />
		<title>箱入り娘</title>
		<script type="text/javascript" src="hakoiri_103.js"></script>
		<style>
			* { margin:0; padding:0; text-align:center; }
			body { background-image: url('back.png'); }
			h1 { background-color: #cccc99; color:white; padding:7px; font-size:1.5em; }
			#aCanvas { display:block; margin-right:auto; margin-left:auto; }
			html, body { width:100%; height:100%; }
		</style>
	</head>
	<body>
		<div id="top_title"><!--<h1>箱入り娘</h1>--></div>
		<div id="info">-</div>
		<canvas id="aCanvas"></canvas>
	</body>
</html>
hakoiri_103.js
  • (7行目)
    パズル用画像は前Ver.と同じ「resource_102.png」を指定しています。
  • (83~85行目)
    ゲームクリア後の場合は、駒の選択を拒否。(選択できなければ移動も不可。)
  • (121~145行目)
    前回、「2クリックで移動していたところを、1クリックで移動可能」とした変更箇所ですが、「//斜めへの移動」に関する条件式を簡略化(136~139行目)、一部の変数を入れ替え(CH_SIZE[sel_no][1]をsel_hに、CH_SIZE[sel_no][0]をsel_wに入れ替え)、無駄な空白を切り詰めて多少閲覧性を改善しました。やっていることは変化なしです。
// file: hakoiri_103.js
// ---  定数の宣言 ---
var CW = 100; // 駒の最小幅
var COLS = 6, ROWS = 7;
var CANVAS_W = CW * COLS;
var CANVAS_H = CW * ROWS;
var RESOURCE_FILE = "resource_102.png";
// 隙間と駒、 箱の枠を定義
var __=0,DT=1,K2=2,K3=3,K4=4,K5=5;
var K6=6,K7=7,K8=8,K9=9,KA=10,XX=11;
// 隙間と駒、 箱の枠のサイズを定義
var CH_SIZE = [
[1,1],[2,2],[1,2],[1,2],[1,2],[1,2],
[2,1],[1,1],[1,1],[1,1],[1,1],[1,1]];
// resource.png上の隙間と駒、箱の立置を定義
var CH_POS = [0,1,3,4,5,6,7,9,10,11,12,13];
// 箱の中(ステ一ジ)の初期状態を定義
var DEF_STAGE = [
[XX,XX,XX,XX,XX,XX],
[XX,K2,DT,DT,K3,XX],
[XX,K2,DT,DT,K3,XX],
[XX,K4,K6,K6,K5,XX],
[XX,K4,K7,K8,K5,XX],
[XX,K9,__,__,KA,XX],
[XX,XX,XX,XX,XX,XX]
];
// --- 大域変数の定義 ---
var play_msg = "<h1>箱入り娘</h1>";	// PUZZLEのタイトル
var clear_msg = "<h1>箱入り娘 CLEAR !</h1>";	// PUZZLE クリア時のタイトル
var aCanvas, ctx; // キヤンバスとコンテキスト
var images;			// 画像オブジエク卜
var stage;			// ステ一シを表す変数
var turn;			// 手数
var sel_x, sel_y, sel_no; // 選択中の駒情報
var t_out = 15;	//クリア後のタイムアウト秒数
// --- 初期化イべント ---
window.onload = function() {
	// キヤンバスのサイズをセット
	aCanvas = $("aCanvas");
	aCanvas.width = CANVAS_W;
	aCanvas.height = CANVAS_H;
	// キヤンバスのイべン卜をセット
	aCanvas.onclick = clickHandler;
	// 描画コンテキス卜の取得
	ctx = aCanvas.getContext("2d");
	// リソース画像を読み込む
	$("info").innerHTML =  " 少々お待ちください";
	images = new Image();
	images.src = RESOURCE_FILE;
	images.onload = initGame;
};
// ゲームのバラメー夕を初期化
function initGame() {
	document.getElementById('top_title').innerHTML = play_msg;
	$("info").innerHTML = "-";
	stage = cloneArray(DEF_STAGE);
	turn = 0;
	sel_x = sel_y = sel_no = -1; // 未選択
	drawScreen();
}
// --- マウスイベント ---
function clickHandler(e) {
	var pt = getClientPos(e);
	var x = Math.floor(pt.x / CW);
	var y = Math.floor(pt.y / CW);
	clickstage(x, y);
}
// --- マウス座標を変換する --- 
function getClientPos(e) {
	var res = { x:0, y:0 };
	var rect = e.target.getBoundingClientRect();
	res.x = e.clientX - rect.left;
	res.y = e.clientY - rect.top;
	return res;
}
// --- 駒の移動処理 ---
// (x,y)をクリックした時の処理
function clickstage(x, y) {
	var no = stage[y][x];
	console.log("click=" + no);
	// クリックしたのが枠なら何もしない
	// 初回クリックが隙間なら何もしない
	// クリア後は何もしない
	if ((no == XX) || (sel_no <= 0 && no == __) ||
		 (sel_no == DT && sel_x == 2 && sel_y == 4)) return;
	// 選択状態で隙間を選択した場合
	if (no == __ && sel_no > 0) {
		checkMove(x, y);
		return;
	}
	// 選択座標を記録する
	sel_x = x; sel_y = y; sel_no = no;
	// 選択座標を補正。娘の駒なら必ず左上になる
	if (CH_SIZE[no][0] > 0) {
		if (stage[y][x-1] == no) sel_x--;
	}
	if (CH_SIZE[no][1] > 0) {
		if (stage[y-1][x] == no) sel_y--;
	}
	$("info").innerHTML = "移動方向をクリックしてください。";
	drawScreen();
}
// 移動可能か判定
function checkMove(x, y) {
	// 移動先が隙間かを確かめる
	// ただし右か下に移勤する場合を考慮
	var sel_w = CH_SIZE[sel_no][0];
	var sel_h = CH_SIZE[sel_no][1];
	if (sel_w >= 2 && sel_x < x) x -= sel_w-1;
	if (sel_h >= 2 && sel_y < y) y -= sel_h-1;
	var res = loop2(sel_h, sel_w, function(e) {
		var nx = e.x + x;
		var ny = e.y + y;
		var v = stage[ny][nx];
		if (v != __ && v != sel_no) {
			e.stop = true;
		}
	});
	if (!res) return;
	// 駒が隣り合っているかを判定する
	// console.log("test045  sel_x="+sel_x+" sel_y="+sel_y+"  x="+x+" y="+y);
	if (sel_x==x) {	//縦への移動
		res = (Math.abs(y-sel_y)<=1) ||
		      ((Math.abs(y-sel_y)==2) && (stage[sel_y+1][sel_x]==__))||
		      ((Math.abs(y-sel_y)==2) && (stage[sel_y-1][sel_x]==__))||
		      (sel_h==2 && (stage[sel_y+2][sel_x]==__))||
		      (sel_h==2 && (stage[sel_y-2][sel_x]==__));
	}
	if (sel_y==y) {	//横への移動
		res = (Math.abs(x-sel_x)<=1) ||
		      ((Math.abs(x-sel_x)==2) && (stage[sel_y][sel_x+1]==__))||
		      ((Math.abs(x-sel_x)==2) && (stage[sel_y][sel_x-1]==__))||
		      (sel_w==2 && stage[sel_y][sel_x+2]==__)||
		      (sel_w==2 && stage[sel_y][sel_x-2]==__);
	}
	if ((Math.abs(x-sel_x)==1) && (Math.abs(y-sel_y)==1)) {	//斜めへの移動
		res = (stage[sel_y+(y-sel_y)][sel_x]==__)||
		      (stage[sel_y][sel_x+(x-sel_x)]==__);
	}
	if ((Math.abs(x-sel_x)>=3) ||
	    (Math.abs(y-sel_y)>=3) ||
	    ((Math.abs(x-sel_x)==2) && (Math.abs(y-sel_y)==1)) ||
	    ((Math.abs(x-sel_x)==1) && (Math.abs(y-sel_y)==2)) ||
	    ((sel_h==2) && ((Math.abs(x-sel_x)>=1) && (Math.abs(y-sel_y)>=1))) ||
	    ((sel_w==2) && ((Math.abs(x-sel_x)>=1) && (Math.abs(y-sel_y)>=1)))) res = false;
	if (!res) return;
	console.log("canMove");
	// 既存の場所を隙間にする
	loop2(sel_h, sel_w, function(e) {
		stage[sel_y+e.y][sel_x+e.x] = __;
	});
	// 新しい位置に駒を移動
	loop2(sel_h, sel_w, function(e) {
		stage[y+e.y][x+e.x] = sel_no;
	});
	// 選択継続
	sel_x = x; sel_y = y;
	turn++;
	$("info").innerHTML = "第" + turn + "手 移動しました。";
	drawScreen();
	// クリア判定
	if (sel_no == DT && sel_x == 2 && sel_y == 4) {
		setTimeout(gameClear, 1);
	}
}
function gameClear() {
	document.getElementById('top_title').innerHTML = clear_msg;
	$("info").innerHTML = "ゲームクリア! (" + turn + "手)";
	drawScreen();
	// t_out秒後にゲームを再開する
	let count = 0;
	const countUp = () => {
		$("info").innerHTML = "ゲームクリア! (" + turn + "手) " + (t_out - count) +"";
		const timeoutId = setTimeout(countUp,1000);
		count++;
		if (count > t_out){
			clearTimeout(timeoutId);
			initGame();
		}
	}
	countUp();
}
// --- 画面の描画 ---
function drawScreen() {
	// 箱を描画
	ctx.drawImage(images,
		CH_POS[XX] * CW, 0, CANVAS_W, CANVAS_H,
		0, 0, CANVAS_W, CANVAS_H);
	// 隙間と各駒を描画
	var tmp = []; // 描画済みを記録するための変数
	loop2(ROWS,COLS, function(e) {
		row = e.y;
		col = e.x;
		var no = stage[row] [col];
		// 枠あるいは既に描画済みならスキップ
		if (tmp[row*COLS+col]||no==XX) return;
		var w = CH_SIZE[no][0];
		var h = CH_SIZE[no][1];
		var p = CH_POS[no];
		var tx = col * CW;	// 描画先x
		var ty = row * CW;	// 描画先y
		var tw = w * CW;	// 描画サイズ幅
		var th = h * CW;	// 描画高さ
		ctx.drawImage (images ,
			p * CW, 0, tw, th,
			tx, ty, tw, th
		);
		// 描画済みであることをセット
		loop2(h, w, function(f) {
			var nx = f.x + col;
			var ny = f.y + row;
			tmp[ny*COLS+nx] = no;
		});
		//ゲームクリアなら
		if (sel_no == DT && sel_x == 2 && sel_y == 4) {
			// ハイライト不要
		}else{	//ゲームクリアしてないなら
			// 選択範囲をハイライ卜
			if (sel_no == no) {
				ctx.fillStyle = "rgba(255,100,100,0.5)";
				ctx.fillRect(tx, ty, tw, th);
			}
		}
	});
}
// --- 便利関数 ---
// 配列のク口ーンを作成
function cloneArray(a) {
	var b = [];
	for (var i = 0; i < a.length; i++) {
		if (typeof(a[i]) == "object") {
			b[i] = cloneArray(a[i]);
		} else {
			b[i] = a[i];
		}
	}
	return b;
}
// 2重のforルーブをラップした関数
function loop2(rows, cols, callback) {
	var e = {x:0, y:0, stop:false};
	for (var y = 0; y < rows; y++) {
		e.y = y;
		for (var x = 0; x < cols; x++) {
			e.x = x;
			callback(e);
			if (e.stop) return false;
		}
	}
	return true;
}
// DOM要素を返す
function $(id) {
	return document.getElementById(id);
}