通过前端技术实现——图片切片拼图游戏

一、需要实现的功能

一张完整的图片切割成多个九宫格小块,在打乱排列顺序后,通过拖动小块的位置,重新排列它们以恢复原始图片即成功。这个游戏测试你的观察能力、空间认知能力和逻辑思维能力。它不仅有助于放松大脑,还提供了娱乐和挑战。

二、需求功能细化

1、图片切割

游戏开始时,一张图片会被切割成多个小块,并以随机顺序排列在游戏区域内。

2、打乱拼图

点击"打乱拼图"按钮,可以重新随机排列拼图块的位置。

3、拖拽移动

通过鼠标点击小块并拖拽,可以移动小块的位置。

4、完成拼图

玩家的目标是将拼图块按照正确的顺序重新排列,恢复原始图片。

游戏规则:
游戏开始时,一张图片被切割成多个小块并随机排列在游戏区域内。
使用鼠标点击一个小块,然后拖拽它的位置,将其移动到空白位置或与其他小块交换位置。
继续移动其他小块,逐渐调整它们的位置,以恢复原始图片的完整性。
如果感到困惑或遇到难题,可以点击"打乱拼图"按钮重新排列小块,重新开始。
当所有小块都恰当地拼合在一起,恢复成原始图片时,游戏胜利。
在完成拼图后,会弹出恭喜信息的提示框,可以选择重新开始游戏或继续挑战。

三、H5页面设计

我们创建一个命名为puzzle.html的文件通过编辑器(我用的EditPlus)打开。使用<!DOCTYPE>声明HTML文档类型,确保浏览器以正确的方式渲染网页内容。同时,设置UTF-8编码,以确保浏览器能够正确地解析和显示中文字符。

以下是拼图游戏的基本DOM结构。玩家将在游戏板上看到拼图块,并通过点击按钮来打乱拼图块的位置。你可以根据这个基本结构来进一步实现拼图游戏的逻辑和功能。

<body>
  <h1>图片切片拼图游戏</h1>
  <div id="puzzle-container">
    <div id="puzzle-board"></div>
  </div>
  <button id="shuffle-btn">打乱拼图</button>
</body>

<h1>图片切换拼图游戏</h1>:标题,使用h1标签,用于显示游戏的名称或标题,让玩家知道这是一个拼图游戏。

<div id="puzzle-container">:这是一个容器,用于包含拼图游戏的主要内容。拼图块将显示在这个容器内。

<div id="puzzle-board"></div>:用于显示拼图块的游戏板。在游戏开始时,拼图块会被放置在这个游戏板上。

<button id="shuffle-btn">打乱拼图</button>:这是一个按钮,当点击它时,会触发打乱拼图的功能。点击这个按钮后,拼图块的位置将被重新随机排列,增加游戏的挑战性。

四、CSS样式设置

上面html已经准备好了,下面我们简单的来配置一下样式吧。

<style>
  #puzzle-container {
    width: 300px;
    height: 300px;
    margin: 0 auto;
    border: 2px solid #ccc;
    position: relative;
    overflow: hidden;
  }
  #puzzle-board {
    width: 300px;
    height: 300px;
    position: absolute;
  }
  .puzzle-piece {
    width: 100px;
    height: 100px;
    position: absolute;
    background-size: 300px 300px;
    border: 2px solid #fff;
    transition: all 0.3s ease-in-out;
  }
  button {
    display: block;
    margin: 20px auto;
  }
</style>

#puzzle-container:这是拼图游戏容器的样式。它设置了容器的宽度和高度为300px,并使用margin: 0 auto使其水平居中对齐。还设置了边框样式和相对定位,并使用overflow: hidden属性隐藏超出容器大小的内容。

#puzzle-board:这是拼图游戏板的样式。它设置了游戏板的宽度和高度为300px,并使用position: absolute将其相对于父容器进行定位。

.puzzle-piece:这是拼图块的样式。它设置了拼图块的宽度和高度为100px,并使用position: absolute将其相对于父容器进行定位。background-size: 300px 300px用于调整拼图块背景图的大小以适应游戏板。还设置了边框样式和过渡效果,使拼图块在移动时具有平滑的过渡效果。

button:这是按钮的样式。display: block使按钮以块级元素显示,margin: 20px auto用于将按钮居中对齐。

以上是拼图游戏的基本CSS样式。通过这些样式,游戏容器、游戏板和拼图块将按照指定的样式进行布局和显示。你可以根据这个CSS代码继续完善拼图游戏的样式和外观。

五、代码逻辑,游戏实现

上面我们搭建了好了页面,下面呢我们就通过js代码,实现我们游戏的功能吧。

<script>
  document.addEventListener('DOMContentLoaded', () => {
    const puzzleContainer = document.getElementById('puzzle-container');
    const puzzleBoard = document.getElementById('puzzle-board');
    const shuffleButton = document.getElementById('shuffle-btn');
    const imageSrc = 'https://www.webppp.com/upload/icecream_animation_cover.png'; // 替换为你的图片地址
    const rows = 3;
    const cols = 3;
    const pieces = [];
    let emptyPiece;
    let isShuffling = false;
    // 创建拼图块
    function createPuzzlePieces() {
      const pieceWidth = puzzleContainer.offsetWidth / cols;
      const pieceHeight = puzzleContainer.offsetHeight / rows;
      for (let row = 0; row < rows; row++) {
        for (let col = 0; col < cols; col++) {
          const piece = document.createElement('div');
          piece.className = 'puzzle-piece';
          piece.style.width = `${pieceWidth}px`;
          piece.style.height = `${pieceHeight}px`;
          piece.style.backgroundImage = `url(${imageSrc})`;
          piece.style.backgroundPosition = `${-col * pieceWidth}px ${-row * pieceHeight}px`;
          piece.style.top = `${row * pieceHeight}px`;
          piece.style.left = `${col * pieceWidth}px`;
          piece.dataset.row = row;
          piece.dataset.col = col;
          if (row === rows - 1 && col === cols - 1) {
            emptyPiece = piece;
            piece.classList.add('empty');
          } else {
            piece.addEventListener('click', () => {
              if (!isShuffling) {
                movePiece(piece);
              }
            });
          }
          puzzleBoard.appendChild(piece);
          pieces.push(piece);
        }
      }
    }
    // 移动拼图块
    function movePiece(piece) {
      const emptyPieceRow = parseInt(emptyPiece.dataset.row);
      const emptyPieceCol = parseInt(emptyPiece.dataset.col);
      const pieceRow = parseInt(piece.dataset.row);
      const pieceCol = parseInt(piece.dataset.col);
      if (
        (pieceRow === emptyPieceRow && Math.abs(pieceCol - emptyPieceCol) === 1) ||
        (pieceCol === emptyPieceCol && Math.abs(pieceRow - emptyPieceRow) === 1)
      ) {
        const tempRow = emptyPieceRow;
        const tempCol = emptyPieceCol;
        emptyPiece.style.top = `${pieceRow * piece.offsetHeight}px`;
        emptyPiece.style.left = `${pieceCol * piece.offsetWidth}px`;
        emptyPiece.dataset.row = pieceRow;
        emptyPiece.dataset.col = pieceCol;
        piece.style.top = `${tempRow * piece.offsetHeight}px`;
        piece.style.left = `${tempCol * piece.offsetWidth}px`;
        piece.dataset.row = tempRow;
        piece.dataset.col = tempCol;
      }
      checkWin();
    }
    let isWin = false; // 添加标志位
    // 检查是否完成拼图
    function checkWin() {
      let isPuzzleComplete = true;
      for (let i = 0; i < pieces.length; i++) {
        const piece = pieces[i];
        const row = parseInt(piece.dataset.row);
        const col = parseInt(piece.dataset.col);
        if (row !== Math.floor(i / cols) || col !== i % cols) {
          isPuzzleComplete = false;
          break;
        }
      }
      if (isPuzzleComplete && !isWin && !isShuffling) { // 添加条件判断
        isWin = true; // 设置标志位为true
        setTimeout(() => {
          alert('恭喜!你完成了拼图!');
          shuffleButton.disabled = false;
          isWin = false; // 重置标志位为false
        }, 200);
      }
    }
    // 打乱拼图
    function shufflePuzzle() {
      isShuffling = true;
      shuffleButton.disabled = true;
      isWin = false; // 重置标志位为false
      const shuffleCount = 100;
      let count = 0;
      const intervalId = setInterval(() => {
        const randomIndex = Math.floor(Math.random() * pieces.length);
        const randomPiece = pieces[randomIndex];
        movePiece(randomPiece);
        count++;
        if (count >= shuffleCount) {
          clearInterval(intervalId);
          isShuffling = false;
          shuffleButton.disabled = false;
        }
      }, 10);
    }
    createPuzzlePieces();
    shuffleButton.addEventListener('click', shufflePuzzle);
  });
</script>

现在解释一下关键部分的代码。

(1)document.addEventListener('DOMContentLoaded', () => { ... });

表示在文档加载完成后执行的函数,确保在操作DOM之前元素已经存在。

(2)const puzzleContainer = document.getElementById('puzzle-container');

获取 id 为"puzzle-container"的元素,并将其存储在 puzzleContainer 变量中。这个容器元素是整个拼图游戏的外层容器。

(3)const puzzleBoard = document.getElementById('puzzle-board');

获取id为"puzzle-board"的元素,并将其存储在 puzzleBoard 变量中。这个元素是用来放置拼图块的容器。

(4)const shuffleButton = document.getElementById('shuffle-btn');

获取id为"shuffle-btn"的元素,并将其存储在 shuffleButton 变量中。这是一个按钮元素,用于触发打乱拼图的操作。

(5)const imageSrc = '';

一个存储图片地址的变量,用于设置拼图块的背景图片。

(6)const rows = 3; 和 const cols = 3;

定义拼图的行数和列数。

(7)const pieces = [];

创建了一个空数组,用于存储拼图块的DOM元素。

(8)let emptyPiece;

一个用于存储空拼图块的变量。

(9)let isShuffling = false;

一个标志位变量,用于判断是否正在打乱拼图。

(10)function createPuzzlePieces() { ... }

一个创建拼图块的函数。它根据行数和列数循环创建拼图块的DOM元素,并设置它们的样式、背景图片和位置。同时,为非空拼图块添加了点击事件监听器。

(11)function movePiece(piece) { ... }

一个移动拼图块的函数。它根据点击的拼图块和空拼图块的位置关系,交换它们的位置,并更新它们的样式和数据集。

(12)function checkWin() { ... }

一个检查是否完成拼图的函数。它遍历所有拼图块,根据其当前位置与正确位置进行比较,判断是否完成拼图。如果拼图完成,则显示恭喜的提示,并重新启用打乱按钮。

(13)function shufflePuzzle() { ... }

一个打乱拼图的函数。它使用随机算法将拼图块进行打乱,将空拼图和其他拼图块进行交换,并计数打乱次数。在每次交换拼图块后,检查是否完成拼图,如果拼图已经完成,则显示完成提示,并重新启用打乱按钮。

(14)createPuzzlePieces();

调用 createPuzzlePieces() 函数,开始创建拼图块。

(15)shuffleButton.addEventListener('click', shufflePuzzle);

为打乱按钮添加了点击事件监听器,点击按钮将触发 shufflePuzzle() 函数,开始打乱拼图。

这些代码一起实现了一个基于DOM操作的拼图游戏。通过点击拼图块和打乱按钮,可以移动拼图块并打乱拼图,当拼图完成时会显示完成提示。

六、完整源代码

<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>图片切片拼图游戏</title>
  <style>
    #puzzle-container {
      width: 300px;
      height: 300px;
      margin: 0 auto;
      border: 2px solid #ccc;
      position: relative;
      overflow: hidden;
    }
    #puzzle-board {
      width: 300px;
      height: 300px;
      position: absolute;
    }
    .puzzle-piece {
      width: 100px;
      height: 100px;
      position: absolute;
      background-size: 300px 300px;
      border: 2px solid #fff;
      transition: all 0.3s ease-in-out;
    }
    button {
      display: block;
      margin: 20px auto;
    }
  </style>
</head>
<body>
<h1>图片切片拼图游戏</h1>
<div id="puzzle-container">
  <div id="puzzle-board"></div>
</div>
<button id="shuffle-btn">打乱拼图</button>
</body>
<script>
  document.addEventListener('DOMContentLoaded', () => {
    const puzzleContainer = document.getElementById('puzzle-container');
    const puzzleBoard = document.getElementById('puzzle-board');
    const shuffleButton = document.getElementById('shuffle-btn');
    const imageSrc = 'https://www.webppp.com/upload/icecream_animation_cover.png';
    const rows = 3;
    const cols = 3;
    const pieces = [];
    let emptyPiece;
    let isShuffling = false;
    // 创建拼图块
    function createPuzzlePieces() {
      const pieceWidth = puzzleContainer.offsetWidth / cols;
      const pieceHeight = puzzleContainer.offsetHeight / rows;
      for (let row = 0; row < rows; row++) {
        for (let col = 0; col < cols; col++) {
          const piece = document.createElement('div');
          piece.className = 'puzzle-piece';
          piece.style.width = `${pieceWidth}px`;
          piece.style.height = `${pieceHeight}px`;
          piece.style.backgroundImage = `url(${imageSrc})`;
          piece.style.backgroundPosition = `${-col * pieceWidth}px ${-row * pieceHeight}px`;
          piece.style.top = `${row * pieceHeight}px`;
          piece.style.left = `${col * pieceWidth}px`;
          piece.dataset.row = row;
          piece.dataset.col = col;
          if (row === rows - 1 && col === cols - 1) {
            emptyPiece = piece;
            piece.classList.add('empty');
          } else {
            piece.addEventListener('click', () => {
              if (!isShuffling) {
                movePiece(piece);
              }
            });
          }
          puzzleBoard.appendChild(piece);
          pieces.push(piece);
        }
      }
    }
    // 移动拼图块
    function movePiece(piece) {
      const emptyPieceRow = parseInt(emptyPiece.dataset.row);
      const emptyPieceCol = parseInt(emptyPiece.dataset.col);
      const pieceRow = parseInt(piece.dataset.row);
      const pieceCol = parseInt(piece.dataset.col);
      if (
        (pieceRow === emptyPieceRow && Math.abs(pieceCol - emptyPieceCol) === 1) ||
        (pieceCol === emptyPieceCol && Math.abs(pieceRow - emptyPieceRow) === 1)
      ) {
        const tempRow = emptyPieceRow;
        const tempCol = emptyPieceCol;
        emptyPiece.style.top = `${pieceRow * piece.offsetHeight}px`;
        emptyPiece.style.left = `${pieceCol * piece.offsetWidth}px`;
        emptyPiece.dataset.row = pieceRow;
        emptyPiece.dataset.col = pieceCol;
        piece.style.top = `${tempRow * piece.offsetHeight}px`;
        piece.style.left = `${tempCol * piece.offsetWidth}px`;
        piece.dataset.row = tempRow;
        piece.dataset.col = tempCol;
      }
      checkWin();
    }
    let isWin = false; // 添加标志位
    // 检查是否完成拼图
    function checkWin() {
      let isPuzzleComplete = true;
      for (let i = 0; i < pieces.length; i++) {
        const piece = pieces[i];
        const row = parseInt(piece.dataset.row);
        const col = parseInt(piece.dataset.col);
        if (row !== Math.floor(i / cols) || col !== i % cols) {
          isPuzzleComplete = false;
          break;
        }
      }
      if (isPuzzleComplete && !isWin && !isShuffling) { // 添加条件判断
        isWin = true; // 设置标志位为true
        setTimeout(() => {
          alert('恭喜!你完成了拼图!');
          shuffleButton.disabled = false;
          isWin = false; // 重置标志位为false
        }, 200);
      }
    }
    // 打乱拼图
    function shufflePuzzle() {
      isShuffling = true;
      shuffleButton.disabled = true;
      isWin = false; // 重置标志位为false
      const shuffleCount = 100;
      let count = 0;
      const intervalId = setInterval(() => {
        const randomIndex = Math.floor(Math.random() * pieces.length);
        const randomPiece = pieces[randomIndex];
        movePiece(randomPiece);
        count++;
        if (count >= shuffleCount) {
          clearInterval(intervalId);
          isShuffling = false;
          shuffleButton.disabled = false;
        }
      }, 10);
    }
    createPuzzlePieces();
    shuffleButton.addEventListener('click', shufflePuzzle);
  });
</script>
</html>

以上是图片切片拼图游戏的完整代码,可以直接复制过去,或者点击游戏demo连接访问。

版权声明:本文为老张的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。

原文链接:https://www.webppp.com/view/puzzle.html