We Blog Weblog

8パズル【画像】

Web

2025年3月15日

みなさんこんにちは。
ケミストのWeb担当みやのです。

「Web」記事では、これまでに得たWebに関する知識を記録として残していきたいと思います。

今回は前回の「画像を使った15パズル」をもとに「8パズル」を作ってみました。やっぱり順番逆じゃね?

こんな感じになればクリアです。さあレッツプレイ!

おめでとう!完成です!

画像を用意する

正方形の画像を用意します。

こまちもカッコイイですね!いつかはやぶさとこまちが連結するところを見てみたいです!

これをみじん斬り 9等分します。

Photoshopの「スライス」機能を使うと一瞬でできる...とは知らずに、Illustratorに画像を配置して、アートボードをずらしながら書き出してました。

9個の画像のファイル名をそれぞれimage1.jpg、image2.jpg・・・image9.jpgとしておきます。

HTML

<div id="eight">
  <div class="tile" id="tile-0"></div>
  <div class="tile" id="tile-1"></div>
  <div class="tile" id="tile-2"></div>
  <div class="tile" id="tile-3"></div>
  <div class="tile" id="tile-4"></div>
  <div class="tile" id="tile-5"></div>
  <div class="tile" id="tile-6"></div>
  <div class="tile" id="tile-7"></div>
  <div class="tile" id="tile-8"></div>
  <div class="tile empty" id="tile-9"></div>
</div>
<button id="shuffle" onclick="shuffleTiles();">シャッフル</button>
<h4 id="victory-message">おめでとう!完成です!</h4>

9枚目の画像にemptyクラスを付与しておきます。

CSS

CSSは以下の通りです。

#eight {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  grid-template-rows: repeat(3, 1fr);
  gap: 0;
  width: 100%;
  max-width: 400px;
  aspect-ratio: 1 / 1;
  margin: 20px auto;
}

4×4を3×3に変えます。

JavaScript

スクリプトは以下の通りです。

const tiles = $('.tile');
const victoryMessage = $('#victory-message');
let emptyIndex = 8;
let isAnimating = false;
function shuffleTiles() {
  const order = Array.from({ length: 9 }, (_, i) => i);
  let emptyRow = 2, emptyCol = 2;
  for (let i = 0; i < 1000; i++) {
    const direction = ['up', 'down', 'left', 'right'];
    const moves = direction.map(dir => getMove(emptyRow, emptyCol, dir)).filter(move => move !== null);
    const move = moves[Math.floor(Math.random() * moves.length)];
    if (move) {
      const [newRow, newCol, index] = move;
      [order[emptyRow * 3 + emptyCol], order[newRow * 3 + newCol]] = [order[newRow * 3 + newCol], order[emptyRow * 3 + emptyCol]];
      emptyRow = newRow;
      emptyCol = newCol;
    }
  }
  order.forEach((value, index) => {
    const tile = tiles.eq(index);
    if (value < 8) {
      tile.text(value + 1).removeClass('empty');
      const imageUrl = `images/image${value + 1}.jpg`;
      tile.css('background-image', `url(${imageUrl})`);
      tile.css('border', '2px solid white');
    } else {
      tile.text('').addClass('empty');
      emptyIndex = index;
      tile.css('background-image', 'none');
      tile.css('border', 'none');
    }
  });
  victoryMessage.hide();
  stopFireworks();
}
function getMove(row, col, direction) {
  switch (direction) {
    case 'up': return row > 0 ? [row - 1, col, (row - 1) * 3 + col] : null;
    case 'down': return row < 2 ? [row + 1, col, (row + 1) * 3 + col] : null;
    case 'left': return col > 0 ? [row, col - 1, row * 3 + col - 1] : null;
    case 'right': return col < 2 ? [row, col + 1, row * 3 + col + 1] : null;
    default: return null;
  }
}
function slideTile(index) {
  if (isAnimating) return;
  const [emptyRow, emptyCol] = [Math.floor(emptyIndex / 3), emptyIndex % 3];
  const [tileRow, tileCol] = [Math.floor(index / 3), index % 3];
  const isAdjacent = (Math.abs(emptyRow - tileRow) === 1 && emptyCol === tileCol) || (Math.abs(emptyCol - tileCol) === 1 && emptyRow === tileRow);
  if (isAdjacent) {
    isAnimating = true;
    const tileToMove = tiles.eq(index);
    const emptyTile = tiles.eq(emptyIndex);
    const tileNumber = tileToMove.text();
    const tileImage = tileToMove.css('background-image');
    tileToMove.animate({ 
      left: `${(emptyCol - tileCol) * 100}%`,
      top: `${(emptyRow - tileRow) * 100}%`
    }, 200, function() {
      emptyTile.text(tileNumber).removeClass('empty').css({
        backgroundImage: tileImage,
        border: '2px solid white'
      });     
      tileToMove.text('').addClass('empty').css({
        backgroundImage: 'none',
        border: 'none'
      });
      emptyIndex = index;
      tileToMove.css({ left: 0, top: 0 });
      isAnimating = false;
      if (isWinningCondition()) {
        setTimeout(() => {
          victoryMessage.show();
          fireworks();
          tiles.each(function(index) {
            if (index === emptyIndex) {
              $(this).css('background-image', 'url(images/image9.jpg)');
            } else {
              $(this).text('').css({
                border: 'none'
              });
            }
          });
        }, 200);
      }
    });
    tileToMove.css({
      position: 'relative',
      left: 0,
      top: 0
    }).css('z-index', 1);
  }
}
function isWinningCondition() {
  for (let i = 0; i < 8; i++) {
    if (parseInt(tiles.eq(i).text()) !== i + 1) {
      return false;
    }
  }
  return true;
}
tiles.each(function(index) {
  $(this).on('click', () => slideTile(index));
});
$(document).ready(function() {
  shuffleTiles();
});

パズル完成時に枠線と数字を消して、空のタイルに9枚目の画像を配置する処理をしています。

まとめ

てっ 手抜きじゃないんだからねっ

この記事を書いた人
みやの
Web・DTP担当

Contact Us

ご意見、ご相談、料金のお見積もりなど、お気軽にお問い合わせください。

お問い合わせはこちら

TOP