文章目录
- 1 实现效果视频
- 2 功能实现思路
- 3代码实现
1 实现效果视频
拼图1.0
2 功能实现思路
布局忽略
(小白学前端,不献丑了)
左侧拼图格
左侧4*4的拼图小格子
利用表格实现,规划好td的大小,给每个格子加上背景图片(将完整的图片裁剪为16张,命名规则为数字.png(1-16),利用二维数组存放四行四列的值从1-16,在遍历数组时,给他动态加上背景图片即可)item就是二维数组存的值,动态拼接上;photo是文件夹的名字images_1(或2,3,4)实现动态切换拼图图片
let print = [
[1, 2, 3, 4],
[5, 6, 7, 8],
[9, 10, 11, 12],
[13, 14, 15, 16]
]
function updateUI() {
print.forEach((subArr, i) => {
subArr.forEach((item, j) => {
tds[i * 4 + j].style.backgroundImage = "url(./images/" + photo + "/" + item +".png)";
if (isStart && i == x0 && j == y0) {
tds[i * 4 + j].style.border = "3px solid red";
} else {
tds[i * 4 + j].style.border = "";
}
})
})
更换拼图图片
先获取元素,给其绑定单击事件。产生随机[1-4]的索引值(我当前有image_x文件四个,命名从_1到_4所以我需要的索引是1-4),获取当前展示的拼图的_x,确保产生的随机值与上次不同,不同之后就动态拼接新的src,在调用上面实现的ui函数,实现切换背景。
changeBtn.onclick = function () {
isStart = false;
print = [
[1, 2, 3, 4],
[5, 6, 7, 8],
[9, 10, 11, 12],
[13, 14, 15, 16]
]
step=0;
let index = parseInt(Math.random() * 4) + 1;
let photo_id = look.src.split('/')[look.src.split('/').length - 2].split('_')[1];
while (index == photo_id) {
index = parseInt(Math.random() * 4) + 1;
}
photo = 'images_' + index;
look.src = "./images/" + photo + "/canzhaotu.png";
updateUI();
}
重置功能(开始游戏或者打乱拼图)
随机交换打乱顺序(1.0版)
随机索引,实现随机的交换,调用自实现ui函数动态拼接,达到背景的更换,实现打乱拼图效果。
获取对应元素,绑定点击事件,点击调用之后,完成相应的初始化,此时给求助按钮绑定点击事件,开始播放背景音乐。
let restBtn = document.getElementById('rest');
restBtn.onclick = function () {
step = 0;
isStart = true;
shuffle();
updateUI();
initEvent();
helpBtn.onclick = helpBake;
bgMusic.play();
}
// 打乱图片
function shuffle() {
print.forEach((subArr, i) => {
subArr.forEach((item, j) => {
let x = parseInt(Math.random() * 4);
let y = parseInt(Math.random() * 4);
let temp = print[i][j];
print[i][j] = print[x][y];
print[x][y] = temp;
})
})
}
initEvent函数
(给每一个td加点击事件)
1.0版本玩法不同,可以任意更换原始块(想要控制移动的块)
给每一个td也帮上点击事件,在点击时计算出对应的二维数组的i(行)和j(列)。tds是一维数组索引值从0-16,print二维数组,行索引0-3,列索引0-3。可以根据二维行列推出对应的移位tds中的索引位置,也可以反过来。
例如:
如果 是二维的第三行第三列(对应的行列索引是2,2),此时对应的td索引应该是10=2×4+2;index=i*(每一行的长度)+j;反过来就是i=向下取整[index/每行的长度],j=[index%每行的长度]
// 选择初始化位置
function initEvent() {
tds.forEach((td, index) => {
td.onclick = function () {
let x = parseInt(index / 4);
let y = parseInt(index % 4);
x0 = x;
y0 = y;
updateUI();
}
})
}
给移动按钮加点击事件
当然,也加了键盘监听事件,监听上下左右键
leftBtn.onclick = function () {
if (isStart) {
direction = 'left';
move(direction);
}
}
rightBtn.onclick = function () {
if (isStart) {
direction = 'right';
move(direction);
}
}
upBtn.onclick = function () {
if (isStart) {
direction = 'up';
move(direction);
}
}
downBtn.onclick = function () {
if (isStart) {
direction = 'down';
move(direction);
}
}
window.onkeyup = function (e) {
if (isStart) {
if (e.keyCode == 37) {
direction = 'left';
move(direction);
} else if (e.keyCode == 38) {
direction = 'up';
move(direction);
} else if (e.keyCode == 39) {
direction = 'right';
move(direction);
} else if (e.keyCode == 40) {
direction = 'down';
move(direction);
}
}
}
move函数
定义一个direction全局变量,记录方向,判断向那个方向移动,在边界位置时需要注意越界,下一步如果是越界的情况,就直接return,不进行交换了(也就是不移动),也不执行step++(此时不算步数)。step记录移动步数,而一直出现的x0,y0是为了记录原始块(正在操控移动的块,在updateUi中给其加个边框,与其他做区分)
// 移动交换图片
function move(direction) {
let x, y;
if (direction == 'left') {
if (y0 - 1 < 0) {
console.log("左边到边界了");
return;
} else {
x = x0;
y = y0 - 1;
}
} else if (direction == 'right') {
if (y0 + 1 > 3) {
console.log("右边到边界了");
return;
} else {
x = x0;
y = y0 + 1;
}
} else if (direction == 'up') {
if (x0 - 1 < 0) {
console.log("上边到边界了");
return;
} else {
x = x0 - 1;
y = y0;
}
} else if (direction == 'down') {
if (x0 + 1 > 3) {
console.log("下边到边界了");
return;
} else {
x = x0 + 1;
y = y0;
}
}
step++;
let temp = print[x][y];
print[x][y] = print[x0][y0];
print[x0][y0] = temp;
// 更新坐标位置
x0 = x;
y0 = y;
updateUI();
}
判断输赢
同时定义一个victory二维数组,记录下正确顺序,当移动到最后print数组的元素重新排序正确时,就游戏结束了。
let print = [
[1, 2, 3, 4],
[5, 6, 7, 8],
[9, 10, 11, 12],
[13, 14, 15, 16]
]
let victory = [
[1, 2, 3, 4],
[5, 6, 7, 8],
[9, 10, 11, 12],
[13, 14, 15, 16]
];
// 判断是否胜利
function judgeVictory() {
for (let i = 0; i < 4; i++) {
for (let j = 0; j < 4; j++) {
if (print[i][j] != victory[i][j]) {
return false;
}
}
}
return true;
}
重点(难点)
求助按钮(实现提示)
思路:首先我们知道二维数组正确的顺序是1-16按序的,在打乱之后(其实就是这些值被打乱了),我们要还原,【在这个版本里是可以任意更换原始块的(我们移动操作的块)】,所以应该遍历这个print二维数组。去挨个检查里面存放的值的顺序,第一个位置的值应该是1,不是1就是需要被还原,那么再去遍历找寻那个块上的值是1,找到之后,让其和第一个位置的块实现交换,但是不能直接交换,那样就是显得很突兀,直接翻山越海了,应该让其有依据的一步一步归位。那就是找到num,根据num在找到对应的块的i和j,将i,j分别赋值给x0和y0,调用ui实现找到应该归位的那个原始块。
这个时候就用到了print二维数组的行列,利用destX记录绿色块的行坐标(i),利用destY记录绿色块的列坐标(j),用x0和y0记录原始块(红色块)的行和列坐标 (规定先横向移动,在纵向移动,这样就不会影响前面已经正确归位的块【当前num值之前的块】)
比较红色块和绿色块的横坐标(若y0<destY),说明红色块在绿色快的左边,那么应该右移;
比较红色块和绿色块的横坐标(若y0>destY),说明红色块在绿色快的右边,那么应该左移;
当y0=destY时,说明二者在同一列上,此时开始上移或下移(同理)
比较红色块和绿色块的纵坐标(若x0<destX),说明红色块在绿色快的上边,那么应该下移;
比较红色块和绿色块的纵坐标(若x0>destX),说明红色块在绿色快的下边,那么应该上移;
最后,当x0=destX时,说明这个红色块已经移动到了正确位置(绿色块的地方)。
清楚定时器(用定时器实现的循环,for循环太快,体现不出归位的过程)
// 求助按钮
function helpBake() {
let destX;
let destY;
let num;
//遍历寻找那个块的位置不对,找到不对的那个,返回这个位置正确的值应该是那个num
wc: for (let i = 0; i < print.length; i++) {
for (let j = 0; j < print[i].length; j++) {
let index = i * 4 + j;
if (print[i][j] != index + 1) {
destX = i;
destY = j;
num = index + 1;
break wc;
}
}
}
let bakeX;
let bakeY;
//遍历数组,去看当前那个块身上的值是num
wc: for (let i = 0; i < print.length; i++) {
for (let j = 0; j < print[i].length; j++) {
if (print[i][j] == num) {
bakeX = i;
bakeY = j;
break wc;
}
}
}
x0 = bakeX;
y0 = bakeY;
updateUI();
// 开启自动巡航
// 先横向移动,在纵向移动(必须得这样,不然就是bug)
helpBtn.onclick = 'none';
let timeId = setInterval(() => {
// 先比较y0和destY
if (y0 < destY) {
direction = "right";
move(direction);
} else if (y0 > destY) {
direction = "left";
move(direction);
} else {
if (x0 > destX) {
direction = "up";
move(direction);
} else if (x0 < destX) {
direction = "down";
move(direction);
} else {
// 归位了
clearInterval(timeId);
if (isStart) {
helpBtn.onclick = helpBake;
}
}
}
}, 800)
}
剩余的就是各种测试,找bug,改逻辑。
3代码实现
代码下载:https://www.alipan.com/s/WrkusEaP8Uq
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
body {
background-image: url('./images/background.png');
}
.first {
text-align: center;
margin-top: 20px;
margin-bottom: 20px;
}
td {
width: 100px;
height: 100px;
background-image: url('./images/1.png');
background-size: 100% 100%;
background-repeat: no-repeat;
}
.second {
width: 60%;
margin: 0 auto;
display: flex;
}
.second_right {
margin-left: 200px;
}
.third {
margin-top: 20px;
text-align: center;
}
#step {
font-size: 30px;
color: red;
display: inline-block;
width: 80px;
box-sizing: border-box;
text-align: center;
}
.change {
width: 100px;
height: 40px;
font-size: 20px;
background-color: #da3c24;
border-radius: 10px;
color: #fedcdc;
}
#look {
width: 200px;
height: 200px;
background-repeat: no-repeat;
background-size: 100% 100%;
border: 4px solid white;
}
</style>
</head>
<body>
<audio src="./audio/bg.mp3" id="bgMusic"></audio>
<div class="first">
<button class="change">更换图片</button>
<img src="./images/title.png" alt="">
</div>
<div class="second">
<div class="second_left">
<table>
<tr>
<td></td>
<td></td>
<td></td>
<td></td>
</tr>
<tr>
<td></td>
<td></td>
<td></td>
<td></td>
</tr>
<tr>
<td></td>
<td></td>
<td></td>
<td></td>
</tr>
<tr>
<td></td>
<td></td>
<td></td>
<td></td>
</tr>
</table>
</div>
<div class="second_right">
<div style="margin-bottom: 40px;">
<img src="" id="look">
</div>
<div style="margin-bottom: 30px; font-size: 25px; color:aliceblue">
已经走了
<span id="step">0</span>
步
</div>
<div>
<div style="text-align: center;">
<img src="./images/shang.png" id="up">
</div>
<div style="text-align: center;">
<img src="./images/zuo.png" id="left">
<img src="./images/xia.png" id="down">
<img src="./images/you.png" id="right">
</div>
</div>
</div>
</div>
<div class="third">
<img src="./images/chongzhi.png" id="rest">
<img src="./images/qiuzhu.png" id="help">
<div style="color: aliceblue;">点击重置按钮(开始游戏或者重新打乱顺序)</div>
</div>
<script>
let print = [
[1, 2, 3, 4],
[5, 6, 7, 8],
[9, 10, 11, 12],
[13, 14, 15, 16]
]
let victory = [
[1, 2, 3, 4],
[5, 6, 7, 8],
[9, 10, 11, 12],
[13, 14, 15, 16]
];
let tds = document.querySelectorAll('td');
let isStart = false;
let x0 = 0;
let y0 = 0;
let direction = '';
let step = 0;
let photo = 'images_1';
let leftBtn = document.getElementById('left');
let rightBtn = document.getElementById('right');
let upBtn = document.getElementById('up');
let downBtn = document.getElementById('down');
let stepSpan = document.getElementById('step');
let helpBtn = document.getElementById('help');
let changeBtn = document.querySelector('.change');
let look = document.getElementById('look');
look.src = './images/images_1/canzhaotu.png';
let bgMusic = document.getElementById('bgMusic');
window.onkeyup = function (e) {
if (isStart) {
if (e.keyCode == 37) {
direction = 'left';
move(direction);
} else if (e.keyCode == 38) {
direction = 'up';
move(direction);
} else if (e.keyCode == 39) {
direction = 'right';
move(direction);
} else if (e.keyCode == 40) {
direction = 'down';
move(direction);
}
}
}
let restBtn = document.getElementById('rest');
restBtn.onclick = function () {
step = 0;
isStart = true;
shuffle();
updateUI();
initEvent();
helpBtn.onclick = helpBake;
bgMusic.play();
}
leftBtn.onclick = function () {
if (isStart) {
direction = 'left';
move(direction);
}
}
rightBtn.onclick = function () {
if (isStart) {
direction = 'right';
move(direction);
}
}
upBtn.onclick = function () {
if (isStart) {
direction = 'up';
move(direction);
}
}
downBtn.onclick = function () {
if (isStart) {
direction = 'down';
move(direction);
}
}
changeBtn.onclick = function () {
isStart = false;
print = [
[1, 2, 3, 4],
[5, 6, 7, 8],
[9, 10, 11, 12],
[13, 14, 15, 16]
]
step=0;
let index = parseInt(Math.random() * 4) + 1;
let photo_id = look.src.split('/')[look.src.split('/').length - 2].split('_')[1];
while (index == photo_id) {
index = parseInt(Math.random() * 4) + 1;
}
photo = 'images_' + index;
look.src = "./images/" + photo + "/canzhaotu.png";
updateUI();
}
updateUI();
// 更新UI
function updateUI() {
print.forEach((subArr, i) => {
subArr.forEach((item, j) => {
tds[i * 4 + j].style.backgroundImage = "url(./images/" + photo + "/" + item +
".png)";
if (isStart && i == x0 && j == y0) {
tds[i * 4 + j].style.border = "3px solid red";
} else {
tds[i * 4 + j].style.border = "";
}
})
})
stepSpan.innerHTML = step;
if (isStart) {
let flag = judgeVictory();
if (flag) {
alert("恭喜你,成功通关!");
isStart = false;
}
}
}
// 打乱图片
function shuffle() {
print.forEach((subArr, i) => {
subArr.forEach((item, j) => {
let x = parseInt(Math.random() * 4);
let y = parseInt(Math.random() * 4);
let temp = print[i][j];
print[i][j] = print[x][y];
print[x][y] = temp;
})
})
}
// 选择初始化位置
function initEvent() {
tds.forEach((td, index) => {
td.onclick = function () {
let x = parseInt(index / 4);
let y = parseInt(index % 4);
x0 = x;
y0 = y;
updateUI();
}
})
}
// 移动交换图片
function move(direction) {
let x, y;
if (direction == 'left') {
if (y0 - 1 < 0) {
console.log("左边到边界了");
return;
} else {
x = x0;
y = y0 - 1;
}
} else if (direction == 'right') {
if (y0 + 1 > 3) {
console.log("右边到边界了");
return;
} else {
x = x0;
y = y0 + 1;
}
} else if (direction == 'up') {
if (x0 - 1 < 0) {
console.log("上边到边界了");
return;
} else {
x = x0 - 1;
y = y0;
}
} else if (direction == 'down') {
if (x0 + 1 > 3) {
console.log("下边到边界了");
return;
} else {
x = x0 + 1;
y = y0;
}
}
step++;
let temp = print[x][y];
print[x][y] = print[x0][y0];
print[x0][y0] = temp;
// 更新坐标位置
x0 = x;
y0 = y;
updateUI();
}
// 判断是否胜利
function judgeVictory() {
for (let i = 0; i < 4; i++) {
for (let j = 0; j < 4; j++) {
if (print[i][j] != victory[i][j]) {
return false;
}
}
}
return true;
}
// 求助按钮
function helpBake() {
let destX;
let destY;
let num;
wc: for (let i = 0; i < print.length; i++) {
for (let j = 0; j < print[i].length; j++) {
let index = i * 4 + j;
if (print[i][j] != index + 1) {
destX = i;
destY = j;
num = index + 1;
break wc;
}
}
}
let bakeX;
let bakeY;
wc: for (let i = 0; i < print.length; i++) {
for (let j = 0; j < print[i].length; j++) {
if (print[i][j] == num) {
bakeX = i;
bakeY = j;
break wc;
}
}
}
x0 = bakeX;
y0 = bakeY;
updateUI();
// 开启自动巡航
// 先横向移动,在纵向移动(必须得这样,不然就是bug)
helpBtn.onclick = 'none';
let timeId = setInterval(() => {
// 先比较y0和destY
if (y0 < destY) {
direction = "right";
move(direction);
} else if (y0 > destY) {
direction = "left";
move(direction);
} else {
if (x0 > destX) {
direction = "up";
move(direction);
} else if (x0 < destX) {
direction = "down";
move(direction);
} else {
// 归位了
clearInterval(timeId);
if (isStart) {
helpBtn.onclick = helpBake;
}
}
}
}, 800)
}
</script>
</body>
</html>