js实现动漫拼图2.0版

比较与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();// 更新uiupdateUI();// 游戏开始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();}// 更新UIfunction 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>

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/652158.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【工作技术栈】基于注解的redis分布式锁(支持SPEL细粒度+redisson可重入功能)

这里写目录标题 前言基于注解的reids分布式锁感悟 前言 刚开始我们使用的redis工具是自己写的&#xff0c;因为觉得redisson没必要&#xff08;其实是没有人想因为自己不懂redisson导致线上问题吧。。。毕竟公共组件&#xff09; 这个就是目前我们用的&#xff0c;手写简易lua…

python222网站实战(SpringBoot+SpringSecurity+MybatisPlus+thymeleaf+layui)-菜单管理实现

锋哥原创的SpringbootLayui python222网站实战&#xff1a; python222网站实战课程视频教程&#xff08;SpringBootPython爬虫实战&#xff09; ( 火爆连载更新中... )_哔哩哔哩_bilibilipython222网站实战课程视频教程&#xff08;SpringBootPython爬虫实战&#xff09; ( 火…

cmake-find_package链接第三方库

文章目录 基本调用形式和模块模式使用方式 之前我们是使用了绝对路径来链接OpenCV第三方库&#xff0c;但是现在很多库一般会自己写一些cmake文件提供给用户&#xff0c;用户可以直接使用其中的内置变量即可。使用的命令就是find_package。 基本调用形式和模块模式 find_packa…

【RTP】webrtc 学习2: webrtc对h264的rtp打包

切片只是拷贝帧的split的各个部分到新的rtp 包的封装中。并没有在rtp包本身标记是否为关键帧FU-A 切片 输入的H.264 数据进行split :SplitNalu SplitNalu : 按照最大1200字节进行切分 切分后会返回一个数组 对于FU-A :split的数据总大小是 去掉一个字节的nalu header size …

CROSS JOIN

CROSS JOIN 是 SQL 中用于执行笛卡尔积&#xff08;Cartesian product&#xff09;的一种连接操作。它会将左表的每一行与右表的每一行进行组合&#xff0c;生成的结果集的行数等于左表的行数乘以右表的行数。 举个例子&#xff0c;如果表 A 有 m 行&#xff0c;表 B 有 n 行&…

qt的main函数(程序启动入口)

函数入口的参数 这就是Qt中最简单的一个main函数&#xff1a; int main(int argc, char *argv[]) {QApplication a(argc, argv);Widget w;w.show();return a.exec(); } 其中int argc, char *argv[]参数是很有用的。 使用.\release\程序名.exe 模型名.model 模型文件所在的地…

实战 | OpenCV+OCR实现弧形文字识别实例(详细步骤 + 源码)

导 读 本文主要介绍基于OpenCV+OCR实现弧形文字识别实例,并给详细步骤和代码。源码在文末。 背景介绍 测试图如下,目标是正确识别图中的字符。图片来源: https://www.51halcon.com/forum.php?mod=viewthread&tid=6712 同样,论坛中已经给出了Halcon实现代码,…

1948-2022年金融许可信息明细数据

1948-2022年金融许可信息明细数据 1、时间&#xff1a;1948-2022年 2、来源&#xff1a;银监会&#xff08;银监会许可证发布系统&#xff09; 3、指标&#xff1a;来源表、机构编码、机构名称、所属银行、机构类型、业务范围、机构住所、地理坐标、行政区划代码、所属区县、…

【计算机网络】深入掌握计算机网络的核心要点(面试专用)

写在前面 前言四层模型网络地址管理Linux下设置ipARP请求包总结 前言 计算机网络是指将分散的计算机设备通过通信线路连接起来&#xff0c;形成一个统一的网络。为了使得各个计算机之间能够相互通信&#xff0c;需要遵循一定的协议和规范。OSI参考模型和TCP/IP参考模型是计算机…

(南京观海微电子)——OLED驱动与调试

一、OLED DDIC分类 OLED DDIC的技术方向可以分为3类&#xff1a;带Ram【内存】的IC、Ram-less IC和TDDI【显示&触控集成的IC】 1、带Ram的OLED DDIC OLED DDIC有两个Ram&#xff0c;分别是Demura Ram和Display Ram。 1、带Ram的OLED DDIC 1-1&#xff09;Demura Ram&a…

STM32 简易智能家居嵌入式系统设计蓝图

声明 本文为物联网产品设计蓝图,不包括程序设计。 文章目录 声明前言一、项目需求1. 1 数据采集1.2 执行器控制1.3 人机交互1.4 功能1.5 场景联动1.6 数据分析二、项目评估2.1 软硬件2.1.1 硬件2.1.2 软件2.2 设备通讯方式及网络协议三、技术预研3.1 MQTT平台评估3.1.1 方案一…

一张图文深入了解信息量概念

通信原理第10页最后一段&#xff1a; 概率论告诉我们&#xff0c;事件的不确定程度可以用其出现的概率来描述。因此&#xff0c;消息中包含的信息量与消息发生的概率密切相关。消息出现的概率越小&#xff0c;则消息中包含的信息量就越大。 这句话怎么理解呢&#xff1f; 比如…

安利6款免费又高清的视频转GIF方法,值得收藏

前言 平时我们在聊天的时候会发的很多有趣表情包&#xff0c;其实有些就是视频里面的画面&#xff0c;觉得好玩有趣就被网友转换成了GIF&#xff0c;聊天的时候就可以用这些表情包来代表当时的心情。 如何将视频转成GIF动图&#xff1f;对于还不知道怎么将视频转成GIF的朋友&a…

【C语言】(8)宏定义

1. 简介 宏定义(#define)是C语言预处理指令的一种&#xff0c;用于为程序中的值或代码片段创建别名。宏定义可以使代码更加简洁、易于维护&#xff0c;并且可以提高代码重用性。 2. 基本用法 定义常量 宏可以用来定义常量&#xff0c;代替硬编码的数字或字符串&#xff0c;…

MyBatis --- 常用注解

目录 前言 1. Mapper 2. Select 3. Insert 4. Update 5. Delete 6. Results 注意事项&#xff1a; 前言 MyBatis是一款强大的持久层框架&#xff0c;通过注解的方式&#xff0c;可以更便捷地进行数据库操作。本文将介绍MyBatis中常用的注解以及在使用这些注解时需要注意…

uniapp微信小程序-秋云u-charts层级过高

一、先说问题 想在这个每个圆环上面定位一些百分比或者定位一些东西,微信小程序端可以&#xff0c;真机调试不行&#xff0c;打算提高层级不行 二、解决 点击底下开启2d就可以&#xff0c; 也就是在你的图表上加上 :canvas2d"true"

Vue 插槽讲解

什么是插槽&#xff1f; Slot 通俗的理解就是“占坑”&#xff0c;在组件模板中占好了位置&#xff0c;当使用该组件标签时候&#xff0c;组件标签里面的内容就会自动填坑&#xff08;替换组件模板中slot位置&#xff09;并且可以作为承载分发内容的出口。 简单的来说为了更加…

Adobe ColdFusion 任意文件读取漏洞复现(CVE-2023-26361)

0x01 产品简介 Adobe ColdFusion是美国奥多比(Adobe)公司的一套快速应用程序开发平台。该平台包括集成开发环境和脚本语言。 0x02 漏洞概述 Adobe ColdFusion平台 filemanager.cfc接口存在任意文件读取漏洞,攻击者可通过该漏洞读取系统重要文件(如数据库配置文件、系统配…

流畅的Python(六)-使用一等函数实现设计模式

一、核心要义 《设计模式&#xff1a;可复用面向对象软件的基础》一书中有23个设计模式&#xff0c;其中有16个在动态语言中"不见了或者简化了"。作为动态语言之一的Python, 我们可以利用一等函数简化其中的某些设计模式&#xff0c;本章主要介绍如何使用一等函数重…

c++ QT 信号的个人理解 信号就是独立文件调用的一种“协议”

一. 简介 就我个人来理解&#xff0c;信号槽机制与Windows下消息机制类似&#xff0c;消息机制是基于回调函数&#xff0c;Qt中用信号与槽来代替函数指针&#xff0c;使程序更安全简洁。 信号和槽机制是 Qt 的核心机制&#xff0c;可以让编程人员将互不相关的对象绑定在一起&a…