比较与1.0版,2.0版就更像与华容道类似的拼图游戏,从头到尾都只能控制白色块移动,而且打乱拼图和求助的实现与1.0都不相同
文章目录
- 1 实现效果
- 2 实现思路
- 2.1 打乱拼图
- 2.2 求助功能
- 2.3 判赢
- 3 代码实现
js实现动漫拼图1.0版
https://blog.csdn.net/m0_58730471/article/details/135891948?spm=1001.2014.3001.5501
1 实现效果
拼图2.0
2 实现思路
思路相同的就不在重复赘述,直说我感觉比较难的点或不同的点。
2.1 打乱拼图
这里的打乱不同于1.0版本的,1.0版本的打乱是随机产生x,y两个索引值,然后将该索引值对应元素与x0,y0对应的位置元素交换, 但是有可能产生无解的情况(就直接跨好几格,直接两者交换那种),一步一步就无法还原,而1.0版是可以手动选择原点的,所以就变成了有解。
而这里2.0版本,原点只有一个,无法手动更改,只能一步一步走,那么如果仍是随机产生x,y两个索引值,就可能产生无解的情况所以这里改为随机产生0-3,代表四个方向,让白块在随机的方向上走动n次(n<difficulty,因为有可能随机产生的某个方向走不了已经到边界了),这样一定是有解的,至少原路后退就是一种解。 通过这种交换n次实现打乱的效果。
/*
*/
// 打乱图片
// 随机产生0-3 代表四个方向 0-左,1-上 2-右 3-下
function shuffle() {
for (let i = 0; i < difficulty; i++) {
let x = parseInt(Math.random() * 4);
if (x == 0) {
direction = 'left';
} else if (x == 1) {
direction = 'up';
} else if (x == 2) {
direction = 'right';
} else if (x == 3) {
direction = 'down';
}
move(direction);
}
}
2.2 求助功能
这里通过定义trace,规定0-3代表的方向[0-左 1-上 2-右 3-下],在打乱图片和玩家移动的时候记录下所走的顺序,最后倒着走(从尾部取元素pop,根据值,做反向操作),就可以实现求助(这里的求助并不完美)
在move里面去记录顺序(trace里面添值)
// 求助按钮
// 通过trace的记录情况,进行后退,就可以还原最终的拼图
function helpBake() {
// 调用trace去重函数
distinctTrace();
// 从trace数组中取出最后一个元素
let lastDir = trace.pop();
// 判断方向,反方向移动(回退)
if (lastDir == 0) {
direction = 'right';
} else if (lastDir == 1) {
direction = 'down';
} else if (lastDir == 2) {
direction = 'left';
} else if (lastDir == 3) {
direction = 'up';
}
// 调用移动函数(移动函数中,每正确交换一次,都会trace.push()这次记录)
move(direction);
// trace.push()在这里是无用的,所以需要在pop一次
trace.pop();
}
问题:
trace里面记录的数据都是有用的吗?
会不会产生左右,左左右右,上下,上上下下等原地踏步的情况,答案是肯定的(至少在打乱图片时,随机产生的索引就会有这种情况),所以我们还需要对trace数组进行一个无效步骤去除的操作
// 但是,上面直接通过后退trace,里面存放的元素可能会导致多余步骤:
/* 0 向左,1向上,2向右,3向下
比如:trace[2 0 3 1 1 3 0 2] 模拟后退情况
就先左走,又右走(原地),又向上再向下(原地),又向下再向上(原地),又右走再左走(原地),最终后退这么多步,最后就是呆在原地
所以,需要对trace进行去重处理
*/
function distinctTrace() {
let index = findDistinctIndex();
// 有重复的,删除
while (index != -1) {
trace.splice(index, 2);
index = findDistinctIndex();
}
}
// 查找相邻的矛盾无用索引
function findDistinctIndex() {
let index = -1;
// trace 是一维数组,相邻两个比较,比较次数就是数组长度-1
// 例如: arr[1 2 3 4] 四个元素,相邻的元素比较 1和2比一次,2和3比一次,3和4比一次,所以比较次数就是3次
for (let i = 0; i < trace.length - 1; i++) {
let front = trace[i];
let behind = trace[i + 1];
// 情况:(左右)(右左)(上下)(下上)都是原地,需要去重
if ((front == 0 && behind == 2) || (front == 2 && behind == 0) || (front == 1 && behind == 3) || (
front == 3 && behind == 1)) {
index = i;
break;
}
}
return index;
}
2.3 判赢
这里有所不同的是,最后的一个值不在是16,而是0(因为0.png是白色背景块,在初始化时,将16换成了0,在ui里面动态拼接时,就是白色背景块了)
// 判断是否胜利,
function judgeVictory() {
let right = true;
print.forEach((subArr, i) => {
subArr.forEach((data, j) => {
if (i == 3 && j == 3) {
if (print[i][j] != 0) {
right = false;
}
} else {
let index = i * 4 + j;
if (print[i][j] != index + 1) {
right = false;
}
}
})
})
return right;
}
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 tds = document.querySelectorAll('td');
let isStart = false;
let isRest = false;
// 记录特殊格子坐标(白色格)
let x0, y0;
// 记录移动方向
let direction = '';
// 随机交换的次数
let difficulty = 40;
// 记录已经走的路径,方便后面实现帮助功能
// 0-左 1-上 2-右 3-下
let trace = [];
// 移动步子
let step = 0;
// 更换背景拼图
let photo = 'images_1';
// 获取对应html元素
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'); //显示背景拼图的图片
let restBtn = document.getElementById('rest'); //重新开始按钮
// 初始化背景拼图(默认显示)
look.src = './images/images_1/canzhaotu.png';
let bgMusic = document.getElementById('bgMusic');
// 初始化游戏界面
updateUI();
// 监听按钮点击事件
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);
}
}
}
// 开始或重置
restBtn.onclick = function () {
// 初始化
step = 0;
bgMusic.play();
print = [
[1, 2, 3, 4],
[5, 6, 7, 8],
[9, 10, 11, 12],
[13, 14, 15, 16]
]
//初始化白格
print[3][3] = 0;
trace = [];
//记录原始位置
x0 = 3;
y0 = 3;
// 点击重置不判断输赢
isRest = true;
// 打乱图片
shuffle();
// 更新ui
updateUI();
// 游戏开始
isStart = true;
isRest = false;
// 给求助按钮添加事件
helpBtn.onclick = helpBake;
}
// 控制移动(游戏开始可以移动)
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]
]
trace = [];
step = 0;
// 随机产生1-4的索引值
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();
}
// 更新UI
function updateUI() {
print.forEach((subArr, i) => {
subArr.forEach((item, j) => {
tds[i * 4 + j].style.backgroundImage = "url(./images/" + photo + "/" + item +
".png)";
})
})
stepSpan.innerHTML = step;
if (isStart && judgeVictory() && !isRest) {
alert("恭喜你,成功通关!");
isStart = false;
setTimeout(() => {
print[3][3] = 16;
updateUI();
}, 100)
}
}
/*
这里的打乱不同于1.0版本的,
1.0版本的打乱是随机产生x,y两个索引值,然后将该索引值对应元素与x0,y0对应的位置元素交换,
但是有可能产生无解的情况(就直接跨好几格,直接两者交换那种),一步一步就无法还原,而1.0版是可以手动选择原点的,所以就变成了有解。
而这里2.0版本,原点只有一个,无法手动更改,只能一步一步走,那么如果仍是随机产生x,y两个索引值,就可能产生无解的情况
所以这里改为随机产生0-3,代表四个方向,让白块在随机的方向上走动n次(n<difficulty,因为有可能随机产生的某个方向走不了已经到边界了),
这样一定是有解的,至少原路后退就是一种解。
*/
// 打乱图片
// 随机产生0-3 代表四个方向 0-左,1-上 2-右 3-下
function shuffle() {
for (let i = 0; i < difficulty; i++) {
let x = parseInt(Math.random() * 4);
if (x == 0) {
direction = 'left';
} else if (x == 1) {
direction = 'up';
} else if (x == 2) {
direction = 'right';
} else if (x == 3) {
direction = 'down';
}
move(direction);
}
}
// 移动交换图片
function move(direction) {
let x, y;
let dir = -1;
if (direction == 'left') {
if (y0 - 1 < 0) {
console.log("左边到边界了");
return;
} else {
x = x0;
y = y0 - 1;
dir = 0;
}
} else if (direction == 'right') {
if (y0 + 1 > 3) {
console.log("右边到边界了");
return;
} else {
x = x0;
y = y0 + 1;
dir = 2;
}
} else if (direction == 'up') {
if (x0 - 1 < 0) {
console.log("上边到边界了");
return;
} else {
x = x0 - 1;
y = y0;
dir = 1;
}
} else if (direction == 'down') {
if (x0 + 1 > 3) {
console.log("下边到边界了");
return;
} else {
x = x0 + 1;
y = y0;
dir = 3;
}
}
// 这里就是记录走的情况(包括了打乱的顺序和玩家点击走的顺序)
trace.push(dir);
// 不是打乱顺序时,开始记录步数
if (!isRest) {
step++;
}
let temp = print[x][y];
print[x][y] = print[x0][y0];
print[x0][y0] = temp;
// 更新坐标位置
x0 = x;
y0 = y;
updateUI();
}
// 判断是否胜利,
function judgeVictory() {
let right = true;
print.forEach((subArr, i) => {
subArr.forEach((data, j) => {
if (i == 3 && j == 3) {
if (print[i][j] != 0) {
right = false;
}
} else {
let index = i * 4 + j;
if (print[i][j] != index + 1) {
right = false;
}
}
})
})
return right;
}
// 求助按钮
// 通过trace的记录情况,进行后退,就可以还原最终的拼图
function helpBake() {
// 调用trace去重函数
distinctTrace();
// 从trace数组中取出最后一个元素
let lastDir = trace.pop();
// 判断方向,反方向移动(回退)
if (lastDir == 0) {
direction = 'right';
} else if (lastDir == 1) {
direction = 'down';
} else if (lastDir == 2) {
direction = 'left';
} else if (lastDir == 3) {
direction = 'up';
}
// 调用移动函数(移动函数中,没正确交换一次,都会trace.push()这次记录)
move(direction);
// trace.push()在这里是无用的,所以需要在pop一次
trace.pop();
}
// 但是,上面直接通过后退trace,里面存放的元素可能会导致多余步骤:
/* 0 向左,1向上,2向右,3向下
比如:trace[2 0 3 1 1 3 0 2] 模拟后退情况
就先左走,又右走(原地),又向上再向下(原地),又向下再向上(原地),又右走再左走(原地),最终后退这么多步,最后就是呆在原地
所以,需要对trace进行去重处理
*/
function distinctTrace() {
let index = findDistinctIndex();
// 有重复的,删除
while (index != -1) {
trace.splice(index, 2);
index = findDistinctIndex();
}
}
// 查找相邻的矛盾无用索引
function findDistinctIndex() {
let index = -1;
// trace 是一维数组,相邻两个比较,比较次数就是数组长度-1
// 例如: arr[1 2 3 4] 四个元素,相邻的元素比较 1和2比一次,2和3比一次,3和4比一次,所以比较次数就是3次
for (let i = 0; i < trace.length - 1; i++) {
let front = trace[i];
let behind = trace[i + 1];
// 情况:(左右)(右左)(上下)(下上)都是原地,需要去重
if ((front == 0 && behind == 2) || (front == 2 && behind == 0) || (front == 1 && behind == 3) || (
front == 3 && behind == 1)) {
index = i;
break;
}
}
return index;
}
</script>
</body>
</html>