15パズル【画像】
Web
2025年3月5日
みなさんこんにちは。
ケミストのWeb担当みやのです。
「Web」記事では、これまでに得たWebに関する知識を記録として残していきたいと思います。
今回は画像を使った15パズルを作ってみました。
こんな感じになればクリアです。さあレッツプレイ!
おめでとう!完成です!
画像を用意する
正方形の画像を用意します。

はやぶさカッコイイですね!いつか本物に乗ってみたいです!
これを十七分割 16等分します。
















Photoshopの「スライス」機能を使うと一瞬でできます。
16個の画像のファイル名をそれぞれimage1.jpg、image2.jpg・・・image16.jpgとしておきます。
HTML
<div id="fifteen">
<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" id="tile-9"></div>
<div class="tile" id="tile-10"></div>
<div class="tile" id="tile-11"></div>
<div class="tile" id="tile-12"></div>
<div class="tile" id="tile-13"></div>
<div class="tile" id="tile-14"></div>
<div class="tile empty" id="tile-15"></div>
</div>
<button id="shuffle" onclick="shuffleTiles();">シャッフル</button>
<h4 id="victory-message">おめでとう!完成です!</h4>
この前とほとんど同じです。
今回は色が変わる機能はつけないので、onclickに直接shuffleTilesを設定しました。
CSS
CSSは以下の通りです。
#fifteen {
display: grid;
grid-template-columns: repeat(4, 1fr);
grid-template-rows: repeat(4, 1fr);
gap: 0px;
width: 100%;
max-width: 400px;
aspect-ratio: 1 / 1;
margin: 20px auto;
}
.tile {
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
transition: transform 0.2s ease;
color: white;
font-size: 3rem;
font-weight: 700;
text-shadow: 2px 2px 0 black;
border: 2px solid white;
background-size: cover;
background-position: center;
width: 100%;
height: 100%;
}
.empty {
background-color: transparent;
cursor: default;
}
#victory-message {
margin-top: 20px;
display: none;
}
gapは0にしてタイルをぴっちり敷き詰めて、2pxの白いborderを設定します。
画像の上に表示する数字の文字色は白にして、黒い影をつけました。
JavaScript
スクリプトは以下の通りです。
const tiles = $('.tile');
const victoryMessage = $('#victory-message');
let emptyIndex = 15;
let isAnimating = false;
function shuffleTiles() {
const order = Array.from({ length: 16 }, (_, i) => i);
let emptyRow = 3, emptyCol = 3;
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 * 4 + emptyCol], order[newRow * 4 + newCol]] = [order[newRow * 4 + newCol], order[emptyRow * 4 + emptyCol]];
emptyRow = newRow;
emptyCol = newCol;
}
}
order.forEach((value, index) => {
const tile = tiles.eq(index);
if (value < 15) {
tile.text(value + 1).removeClass('empty');
const imageUrl = `img/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) * 4 + col] : null;
case 'down': return row < 3 ? [row + 1, col, (row + 1) * 4 + col] : null;
case 'left': return col > 0 ? [row, col - 1, row * 4 + col - 1] : null;
case 'right': return col < 3 ? [row, col + 1, row * 4 + col + 1] : null;
default: return null;
}
}
function slideTile(index) {
if (isAnimating) return;
const [emptyRow, emptyCol] = [Math.floor(emptyIndex / 4), emptyIndex % 4];
const [tileRow, tileCol] = [Math.floor(index / 4), index % 4];
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(img/image16.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 < 15; 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();
});
パズル完成時に枠線と数字を消して、空のタイルに16枚目の画像を配置する処理をしています。
まとめ

ジグソーパズルが作りたくなりました。