コアダンプの数だけ強くなれるよ

見習いエンジニアの備忘log

Javascriptで8パズル

前回の画像データを分割+シャッフルして表示する処理に機能を追加してパズルゲームにしてみた。

www.segmentation-fault.xyz

完成品

canvasで図形を描く

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>