拼图游戏实现思路
1. 游戏容器初始化
- 创建盒子区域:设定游戏主盒子区域的大小及基本样式,为小方块提供相对定位的容器。
2. 方块生成与管理
- 创建小方块数据模型:
- 将每个小方块看作一个独立对象(建议通过构造函数实现)。
- 使用数组集中存放所有小方块的信息。
- 精确计算并记录每个小方块的核心属性:当前坐标(
left/top)、背景图切割位置(background-position)、以及它的正确目标坐标。
- 渲染小方块 DOM:
- 根据计算好的数据生成对应的 DOM 节点,并统一添加到
game盒子中。 - 特殊处理:将最后一个方块(通常是右下角)设置为不可见(例如
display: none),作为拼图滑动所需的空白区域。
- 根据计算好的数据生成对应的 DOM 节点,并统一添加到
3. 游戏核心交互
- 洗牌(打乱顺序):
- 游戏开始时,通过生成随机数来随机选取方块进行打乱。
- 打乱的核心逻辑是交换两个方块对象的
left和top属性,并通过修改 DOM 样式实现视觉移动。
- 方块移动(交换位置):
- 边界与合法性判断:点击方块时,必须判断该方块是否与空白方块相邻。只有相邻的方块才能进行位置交换。
4. 胜负判定
- 判断游戏是否结束:
- 每次成功交换位置后,都需要触发胜负判断逻辑。
- 遍历所有方块,比对当前状态:只有当所有方块当前的坐标都与其正确的坐标完全一致时,才判定为游戏胜利。
💡 开发注意事项
坐标精度问题:在判断当前坐标与正确坐标是否相等时,由于浏览器渲染或计算可能会产生小数点的精度问题。强烈建议将坐标转换为整数后再进行判定,以避免因微小误差导致胜负判定失败。
代码实现
html
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title></title>
</head>
<body>
<div id="game" class="game"></div>
<script type="module" src="./script/index.js"></script>
</body>
</html>js
import { getRandom, isEqual } from "./utils.js";
/* 游戏配置 */
const gameConfig = {
width: 500, //宽度
height: 500, //高度
rows: 3,
cols: 3,
bgUrl: "images/lol.png",
dom: document.querySelector("#game"),
blockWidth: 0, //小方块的宽
blockHeight: 0, //小方块的高
blockNum: 0, //小方块的数量
isGameOver: false,//游戏是否结束
}
const bWidth = gameConfig.width / gameConfig.cols;
const bHeight = gameConfig.height / gameConfig.rows;
const bNum = (gameConfig.width / bWidth) * (gameConfig.height / bHeight);
gameConfig.blockWidth = bWidth;
gameConfig.blockHeight = bHeight;
gameConfig.blockNum = bNum;
console.log("gameConfig==>", gameConfig);
/* 初始化 */
const bocksArr = []; //存放方块信息集合
// 初始game容器
const initGameDom = () => {
const {
width,
height,
bgUrl
} = gameConfig;
game.style.position = 'relative';
game.style.width = width + 'px';
game.style.height = height + 'px';
game.style.border = "1px solid #ccc";
// game.style.background = `url(${bgUrl}) no-repeat`;
}
// 小方块对象构造函数
class Block {
// 构造函数
constructor(left, top, isVisible) {
this.left = left; //方块的x坐标
this.top = top; //方块的y坐标
this.bgX = -left; //小方块背景图x
this.bgY = -top; //小方块背景图y
this.correctX = left; //小方块正确坐标x
this.correctY = top; //小方块正确坐标y
this.node = null; //小方块dom节点
this.isVisible = isVisible; //是否展示
this.createBlock(); //创建小方块dom
}
// 创建小方块dom
createBlock() {
this.node = document.createElement('div');
this.node.style.boxSizing = 'border-box';
this.node.style.position = "absolute";
this.node.style.width = gameConfig.blockWidth + 'px';
this.node.style.height = gameConfig.blockHeight + 'px';
this.node.style.border = '1px solid #fff';
this.node.style.left = this.left + 'px';
this.node.style.top = this.top + 'px';
this.node.style.cursor = "pointer";
this.node.style.background = `url(${gameConfig.bgUrl}) no-repeat ${this.bgX}px ${this.bgY}px`;
this.node.style.display = this.isVisible ? 'block' : 'none';
gameConfig.dom.appendChild(this.node);
}
// 显示方块
show() {
this.node.style.left = this.left + 'px';
this.node.style.top = this.top + 'px';
}
//判断当前方块是否在正确的位置上
isCorrect() {
return isEqual(this.left, this.correctX) && isEqual(this.top, this.correctY);
}
}
// 初始化小方块
const initBlock = () => {
// 构造小方块信息
for (let i = 0; i < gameConfig.rows; i++) {
for (let j = 0; j < gameConfig.cols; j++) {
// i行 j列
const left = j * gameConfig.blockWidth;
const top = i * gameConfig.blockHeight;
const visible = !(i === gameConfig.rows - 1 && j === gameConfig.cols - 1);
const block = new Block(left, top, visible);
bocksArr.push(block);
}
}
console.log("bocksArr", bocksArr);
}
// 交换位置
const exchange = (b1, b2) => {
let temp = b1.left;
b1.left = b2.left;
b2.left = temp;
temp = b1.top;
b1.top = b2.top;
b2.top = temp;
b1.show();
b2.show();
}
// 洗牌 随机生成一个位置,交换对应的left和top
const shuffle = () => {
bocksArr.forEach((blockItem, bIndex) => {
// 最后白色方块不参与洗牌
if (bIndex === bocksArr.length - 1) {
return
}
// 当前元素和随机位置进行交换left和top
const index = getRandom(0, bocksArr.length - 2);
exchange(blockItem, bocksArr[index]);
})
console.log("bocksArr洗牌", bocksArr);
}
// 是否胜利
const isWin = () => {
const hasWrong = bocksArr.filter(blockItem => {
return !blockItem.isCorrect();
})
if (!hasWrong.length) {
gameConfig.isGameOver = true;
bocksArr.forEach((blockItem) => {
blockItem.isVisible = true;
blockItem.node.style.border = "0";
blockItem.node.style.display = "block";
})
}
}
// 绑定事件
const bindEvent = () => {
bocksArr.forEach((blockItem, index) => {
blockItem.node.onclick = () => {
// 游戏结束,不能点击
if (gameConfig.isGameOver) {
return
}
const emptyBlock = bocksArr.find(item => !item.isVisible);//找到空白块
// 只有相邻的才可以交换位置
const isNeighbor =
blockItem.left === emptyBlock.left && isEqual(Math.abs(blockItem.top - emptyBlock.top), gameConfig.blockHeight)
||
blockItem.top === emptyBlock.top && isEqual(Math.abs(blockItem.left - emptyBlock.left), gameConfig.blockWidth)
// if (!isNeighbor) {
// return
// }
exchange(blockItem, emptyBlock);
isWin();//判断是否胜利
}
})
}
// 初始化操作
const init = () => {
initGameDom(); //初始化容器
initBlock(); //初始化小方块
shuffle();//洗牌
bindEvent();//绑定事件
}
init();js
/**
* 获取随机数
*/
const getRandom = (min, max) => {
return Math.floor(Math.random() * (max - min + 1) + min)
}
/*
判断两个数是否相等
*/
const isEqual = (a, b) => {
return parseInt(a) === parseInt(b)
}
export {
getRandom,
isEqual
}