Javascriptで8パズル
前回の画像データを分割+シャッフルして表示する処理に機能を追加してパズルゲームにしてみた。
[rtoc_mokuji title=”” title_display=”” heading=”h3″ list_h2_type=”” list_h3_type=”” display=”” frame_design=”” animation=””]
目次
完成品
8パズル
画像データのURL:
シャッフル処理の変更
前回作ったシャッフルの処理をそのまま使うとパズルがゴール不可能なケースが発生する問題がある。この問題を回避するにはパズルのゴール可否を判定して再シャッフルする処理を追加するか、ゴール可能なシャッフル処理をするか2択になるが、前者は面倒そうだったので後者に変更することにした。
変更前
function shuffleArray(array) {
var n = array.length, t, i;
while (n) {
i = Math.floor(Math.random() * n--);
t = array[n];
array[n] = array[i];
array[i] = t;
}
return array;
}
変更後
function shuffleBlocks(array) {
var blk_id = -1;
for (var i = 0; i < BOARD.X_BLOCKS * BOARD.Y_BLOCKS; i++) {
if ((array[i][0] == (BOARD.X_BLOCKS-1)) &&
(array[i][1] == (BOARD.Y_BLOCKS-1))) {
// 空きブロックのindex
blk_id = i;
break;
}
}
var n = 0;
while (n < BOARD.SHUFFLE_CNT) {
var direct = Math.floor(n * Math.random() % BOARD.DIRECT_MAX);
// 空きブロックを可能な限り繰り返し移動する
if (0 <= blk_id + diff[direct] && blk_id + diff[direct] < BOARD.X_BLOCKS * BOARD.Y_BLOCKS) {
var tmp_blocks = array[blk_id];
array[blk_id] = array[blk_id + diff[direct]];
array[blk_id + diff[direct]] = tmp_blocks;
blk_id = blk_id + diff[direct];
}
n++;
}
return array;
}
パズルのゴールから空ブロックを適当に移動させることでシャッフルしている。
ソースコード
<html>
<head>
<meta charset="UTF-8">
<title>canvasで図形を描く</title>
<script type="text/javascript">
var blocks = [];
var canvas;
var img;
var status = 0;
var BOARD = {
'X_RANGE' : 300,
'Y_RANGE' : 300,
'X_BLOCKS' : 3,
'Y_BLOCKS' : 3,
'DIRECT_MAX' : 4,
'SHUFFLE_CNT' : 100,
};
var diff = [-BOARD.X_BLOCKS, -1, 1, BOARD.X_BLOCKS];
function shuffleBlocks(array) {
var blk_id = -1;
for (var i = 0; i < BOARD.X_BLOCKS * BOARD.Y_BLOCKS; i++) {
if ((array[i][0] == (BOARD.X_BLOCKS-1)) &&
(array[i][1] == (BOARD.Y_BLOCKS-1))) {
// 空きブロックのindex
blk_id = i;
break;
}
}
var n = 0;
while (n < BOARD.SHUFFLE_CNT) {
var direct = Math.floor(n * Math.random() % BOARD.DIRECT_MAX);
// 空きブロックを移動可能な限り適当に移動する
if (0 <= blk_id + diff[direct] && blk_id + diff[direct] < BOARD.X_BLOCKS * BOARD.Y_BLOCKS) {
var tmp_blocks = array[blk_id];
array[blk_id] = array[blk_id + diff[direct]];
array[blk_id + diff[direct]] = tmp_blocks;
blk_id = blk_id + diff[direct];
}
n++;
}
return array;
}
function moveBlock(e) {
var rect = e.target.getBoundingClientRect();
var width = BOARD.X_RANGE / BOARD.X_BLOCKS;
var height = BOARD.Y_RANGE / BOARD.Y_BLOCKS;
// クリックしたブロック(座標)を算出
var mouseX = Math.floor((e.clientX - Math.floor(rect.left)) / width);
var mouseY = Math.floor((e.clientY - Math.floor(rect.top)) / height);
// ブロック(blocks)のindexに変換
var blk_id = mouseX * BOARD.X_BLOCKS + mouseY;
// 上下左右の4方向
for (i = 0; i < BOARD.DIRECT_MAX; i++) {
if (0 <= blk_id + diff[i] && blk_id + diff[i] < BOARD.X_BLOCKS * BOARD.Y_BLOCKS) {
// 隣が空ブロックなら位置を交換
if ((blocks[blk_id + diff[i]][0] == (BOARD.X_BLOCKS-1)) &&
(blocks[blk_id + diff[i]][1] == (BOARD.Y_BLOCKS-1))) {
var tmp_blocks = blocks[blk_id];
blocks[blk_id] = blocks[blk_id + diff[i]];
blocks[blk_id + diff[i]] = tmp_blocks;
break;
}
}
}
}
function drawBlock(ctx) {
var width = BOARD.X_RANGE / BOARD.X_BLOCKS;
var height = BOARD.Y_RANGE / BOARD.Y_BLOCKS;
var width0 = img.width / BOARD.X_BLOCKS;
var height0 = img.height / BOARD.Y_BLOCKS;
var blk_cnt = BOARD.X_BLOCKS * BOARD.Y_BLOCKS;
// 一度キャンバスを白に戻す
ctx.fillStyle = "rgb(255,255,255)";
ctx.fillRect(0, 0, BOARD.X_RANGE, BOARD.Y_RANGE);
// ブロックを描画
for(var i = 0; i < BOARD.X_BLOCKS; i++){
for(var j = 0; j < BOARD.Y_BLOCKS; j++){
var x = blocks[(BOARD.X_BLOCKS * BOARD.Y_BLOCKS) - blk_cnt][0];
var y = blocks[(BOARD.X_BLOCKS * BOARD.Y_BLOCKS) - blk_cnt][1];
// 最右下のブロックは空ブロック(黒塗り)
if ((x == BOARD.X_BLOCKS - 1) && (y == BOARD.Y_BLOCKS - 1)) {
ctx.fillStyle = "rgb(0,0,0)";
ctx.fillRect(width * i, height * j, width, height);
} else {
ctx.drawImage(img, width0 * x, height0 * y,width0, height0, width * i, height * j, width, height);
}
blk_cnt--;
}
}
}
function shuffle() {
// 描画コンテキストの取得
var canvas = document.getElementById('c');
if (canvas.getContext) {
var ctx = canvas.getContext('2d');
ctx.fillStyle = "rgb(255,255,255)";
ctx.fillRect(0, 0, BOARD.X_RANGE, BOARD.Y_RANGE);
// 画像データがロード済みならシャッフルして描画
if (status != 0) {
var width = BOARD.X_RANGE / BOARD.X_BLOCKS;
var height = BOARD.Y_RANGE / BOARD.Y_BLOCKS;
var width0 = img.width / BOARD.X_BLOCKS;
var height0 = img.height / BOARD.Y_BLOCKS;
var blk_cnt = 0;
// 初期配置
for(var i = 0; i < BOARD.X_BLOCKS; i++){
for(var j = 0; j < BOARD.Y_BLOCKS; j++){
blocks[blk_cnt++] = [i,j];
}
}
// ブロックをシャッフル
//blocks = shuffleArray(blocks);
blocks = shuffleBlocks(blocks);
// ブロックの配置
for(var i = 0; i < BOARD.X_BLOCKS; i++){
for(var j = 0; j < BOARD.Y_BLOCKS; j++){
var x = blocks[(BOARD.X_BLOCKS * BOARD.Y_BLOCKS) - blk_cnt][0];
var y = blocks[(BOARD.X_BLOCKS * BOARD.Y_BLOCKS) - blk_cnt][1];
// 最右下のブロックは空ブロック(黒塗り)
if ((x == BOARD.X_BLOCKS - 1) && (y == BOARD.Y_BLOCKS - 1)) {
ctx.fillStyle = "rgb(0,0,0)";
ctx.fillRect(width * i, height * j, width, height);
} else {
ctx.drawImage(img, width0 * x, height0 * y,width0, height0, width * i, height * j, width, height);
}
blk_cnt--;
}
}
} else {
ctx.font = "20px Arial";
ctx.strokeStyle = "red";
ctx.strokeText("画像データが未ロードです", 0, 150);
}
// ブロック移動処理
canvas.onclick = function(event) {
moveBlock(event);
drawBlock(ctx);
}
}
}
function loadImage() {
status = 0;
canvas = document.getElementById('c');
if (canvas.getContext) {
var ctx = canvas.getContext('2d');
ctx.fillStyle = "rgb(255,255,255)";
ctx.fillRect(0, 0, 300, 300);
img = new Image();
img.src = document.getElementById('url').value;
img.onload = function() {
ctx.drawImage(img, 0,0, this.width, this.width, 0, 0, BOARD.X_RANGE, BOARD.Y_RANGE);
status = 1;
}
}
}
</script>
</head>
<body>
<p>8パズル</p>
<canvas id="c" style="background-color:white;" width=300 height=300></canvas>
<p>画像データのURL:
<input id="url" type="text" value="">
</p>
<input type="button" value="Load" onclick="loadImage()">
<input type="button" value="Shuffle" onclick="shuffle()">
</body>
</html>
コメント