JavaScriptで15パズル

JavaScriptで15パズルを作成してみた。
ゲームライブラリとして使用したのはenchant.js
画像は適当に作った。

15パズル

各種宣言

enchant();
var BOARD_WIDTH = 547;
var BOARD_HEIGHT = 547;
var PIECE_HEIGHT = 128;
var PIECE_WIDTH = 128;
var BOARD_START_X = 28;
var BOARD_START_Y = 28;
var PIECE_MOVE_VALUE_X = 122;
var PIECE_MOVE_VALUE_Y = 122;
var DEFAULT_FONT = "";
var imageSets = [
				"images/board.png",
				"images/piece1.png",
				"images/piece2.png",
				"images/piece3.png",
				"images/piece4.png",
				"images/piece5.png",
				"images/piece6.png",
				"images/piece7.png",
				"images/piece8.png",
				"images/piece9.png",
				"images/piece10.png",
				"images/piece11.png",
				"images/piece12.png",
				"images/piece13.png",
				"images/piece14.png",
				"images/piece15.png"
			];
var core = null;
var rootScene = null;
var pieces = new Array(15);
var spacePosition = 16;				// 空いているポジション
var pieceMoving = false;			// ピースが移動中か判別
var gameFinishFlag = true;			// ゲーム終了フラグ
var startTime = null;				// スタートタイム
var movingCount = 0;				// 動かしたカウント

フレーム更新時の処理

メインの処理。「rootScene.addEventListener(“enterframe”, function() {})」
1秒間に指定回数このイベントが走る(デフォルト30)。ここではラベルの表示、クリア判定を行っている。

rootScene.addEventListener("enterframe", function() {
	// タイム
	if (startTime != null) {
		nowTime = (new Date()).getTime();
		time = Math.floor((nowTime - startTime) / 1000);
		timeLabel.text = "タイム:" + time;
	}

	// 手数カウント
	movingCountLabel.text = "手数:" + movingCount;

	// クリア判定
	if (gameFinishFlag == false) {
		if (pieceMoving == false) {
			if (isClear()) {
				if (confirm("クリア!。もう一度やりますか?") == true) {
					deletePieces();
					startGame();
				} else {
					gameFinishFlag = true;
				}
			}
		}
	}
});

ピースクラス

ピースクラス、enchant.jsのSpriteクラスを継承して、移動時の処理などをしている。

var Piece = Class.create(Sprite, {
	initialize: function(image, number, position) {
		Sprite.call(this, PIECE_HEIGHT, PIECE_WIDTH);
		this.number = number;
		this.position = position;
		this.image = core.assets[image];
		this.start_x = 0;
		this.start_y = 0;
	},
	ontouchstart: function(e) {
		this.start_x = e.x;
		this.start_y = e.y;
	},
	ontouchend: function(e) {
		// 他のピースが移動中なら終了
		if (pieceMoving == true) {
			return;
		}

		// 距離取得
		var up = Math.floor(this.start_y - e.y);
		var down = Math.floor(e.y - this.start_y);
		var left = Math.floor(this.start_x - e.x);
		var right = Math.floor(e.x - this.start_x);

		// 最大値取得
		var max = Math.max(Math.max(Math.max(up, down), left), right);

		// 移動
		switch (max) {
		case up:
			// 上
			if (this.position - 4 != spacePosition) {
				break;
			}
			pieceMoving = true;
			this.tl.moveBy(0, -PIECE_MOVE_VALUE_Y, 5);
			this.tl.delay(5).then(function() {pieceMoving = false;});
			spacePosition = this.position;
			this.position = this.position - 4;
			movingCount++;
			break;
		case down:
			// 下
			if (this.position + 4 != spacePosition) {
				break;
			}
			pieceMoving = true;
			this.tl.moveBy(0, PIECE_MOVE_VALUE_Y, 5);
			this.tl.delay(5).then(function() {pieceMoving = false;});
			spacePosition = this.position;
			this.position = this.position + 4;
			movingCount++;
			break;
		case left:
			// 左
			if (this.position - 1 != spacePosition) {
				break;
			}
			pieceMoving = true;
			this.tl.moveBy(-PIECE_MOVE_VALUE_X, 0, 5);
			this.tl.delay(5).then(function() {pieceMoving = false;});
			spacePosition = this.position;
			this.position = this.position - 1;
			movingCount++;
			break;
		case right:
			// 右
			if (this.position + 1 != spacePosition) {
				break;
			}
			pieceMoving = true;
			this.tl.moveBy(PIECE_MOVE_VALUE_X, 0, 5);
			this.tl.delay(5).then(function() {pieceMoving = false;});
			spacePosition = this.position;
			this.position = this.position + 1;
			movingCount++;
			break;
		default:
		}
	},
	remove: function() {
		rootScene.removeChild(this);
		delete this;
	},
	isCorrectPosition: function() {
		return this.position == this.number;
	}
});

不可能問題

15パズルには絶対に解けない配置があって下図の形になる。最後の14と15が交換できない。
名称未設定
これを回避するにはシャッフルしたときに、解くのが不可能な配置かどうか判別する必要がある。
以下が判別するためのコード。詳細は省略するけどこの関数で判別できる。

function isPossiblePuzzle(ary) {
	var aryTmp = new Array(ary[0], ary[1], ary[2], ary[3],
						ary[4], ary[5], ary[6], ary[7],
						ary[8], ary[9], ary[10], ary[11],
						ary[12], ary[13], ary[14], ary[15]
						);
	var tmp = 0;
	var changingCount = 0;

	// 数値交換回数を求める
	for (var i = 0; i < aryTmp.length - 1; i++) {
		for (var j = i + 1; j < aryTmp.length; j++) {
			if (i + 1 == aryTmp[j]) {
				tmp = aryTmp[i];
				aryTmp[i] = aryTmp[j];
				aryTmp[j] = tmp;
				changingCount++;
				break;
			}
		}
	}

	// 交換回数が偶数なら解ける、奇数なら解けない
	if (changingCount % 2 == 0) {
		return true;
	} else {
		return false;
	}
}

プログラム

作成したプログラムはGitHubに置いてあります。