文章目录

  • 1 游戏整体框架和流程
  • 2 UI设计
    • 2.1 顶部空白格
    • 2.2 底部16字
  • 3 功能设计
    • 3.1 四字洗牌并展示
    • 3.2 拖拽
      • 3.2.1 元素移动的坐标要素
      • 3.2.2 元素结束移动的判断条件
    • 3.3 成功判定
  • 4 项目整体代码

1 游戏整体框架和流程

最终效果图如图:

功能点:

  1. 顶部4个格子存放拖放的字,底部16个格子存放待选字
  2. 字依靠拖动放入空白中
  3. 正确、错误给出提示,并重置

开发计划:
UI设计:顶部4个空白、底部16个字
功能设计:提供拖拽跟随鼠标、提供松开鼠标回弹、拖动到空白格吸附、4字判断、重置

2 UI设计

2.1 顶部空白格

框架:顶部最外层用一个div,每个格子为一个div
要实现每个格子间隔开,简单的办法就是flex布局,或者设置固定宽度并设置margin(margin不在宽度中所以还要计算,比较麻烦)
这里采用flex并设置固定宽度25%,为了留白需要设置padding并在内部添加一个div,在该div上设置背景色
此处采用border-box方式,使border和padding不影响宽度

  <style>body {margin: 0;}.blank-cell{display: flex;}.blank-cell .blank-item{width: 25%;height: 25vw;padding: .5rem;box-sizing: border-box;}.blank-cell .blank-item .wrapper{width: 100%;height: 100%;/* margin: 15px; padding和border在border-box下不会影响宽度,但是margin在任何情况下都会影响宽度 */border: .1rem solid black;box-sizing: border-box;}</style><div class="container"><div class="blank-cell"><div class="blank-item"><div class="wrapper"></div></div><div class="blank-item"><div class="wrapper"></div></div><div class="blank-item"><div class="wrapper"></div></div><div class="blank-item"><div class="wrapper"></div></div></div></div>

2.2 底部16字

很明显,底部的16字肯定采用循环方式进行生成的,使用innerHtml赋值拼接html即可,网页UI整体结构如下:
此处做了一个分辨率适配:document.documentElement.style.fontSize = document.documentElement.clientWidth / 39 + 'px';
适配原理:默认情况下:1rem=document.documentElement.style.fontSize=14px只需要改变后者即可改变1rem大小

此处分辨率为390*844,适配后1rem=10px,使用rem可以比较好的做移动端适配

<!DOCTYPE html>
<html lang="en">
<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>Document</title><script>document.documentElement.style.fontSize = document.documentElement.clientWidth / 39 + 'px';</script><style>body {margin: 0;}.blank-cell,.cell {display: flex;}.cell {flex-wrap: wrap;margin-top: 5rem;}.blank-cell .blank-item,.cell .item {width: 25%;height: 25vw;padding: .5rem;box-sizing: border-box;}.blank-cell .blank-item .wrapper,.cell .item .wrapper {width: 100%;height: 100%;/* margin: 15px; padding和border在border-box下不会影响宽度,但是margin在任何情况下都会影响宽度 */border: .1rem solid black;box-sizing: border-box;}.cell .item .wrapper {border: none;background-color: orange;font-size: 3rem;justify-content: center;align-items: center;display: flex;}</style>
</head>
<body><div class="container"><div class="blank-cell"><div class="blank-item"><div class="wrapper"></div></div><div class="blank-item"><div class="wrapper"></div></div><div class="blank-item"><div class="wrapper"></div></div><div class="blank-item"><div class="wrapper"></div></div></div><div class="cell"></div></div>
</body>
</html>

生成底部16字:

    function renderCellItem(text, index) {return `<div class="item"><div class="wrapper" data-index="${index}">${text}</div></div>`}

3 功能设计

3.1 四字洗牌并展示

原始数据:const idioms = ['一发入魂', '三羊开泰', '二狗上树', '五福临门']
将原始数据打乱成一个一个字并拼接html,放在dom中

    cellDom = document.querySelector('.cell');function splitIdioms() {return idioms.reduce((pre, cur) => pre + cur).split("");}function shuffle() {return splitIdioms().sort(randomSorter)}function randomSorter() {return Math.random() > 0.5 ? 1 : -1;}function renderCell(words) {let index = 0;const cellInnerHtml = words.reduce((pre, cur) => {return pre + renderCellItem(cur, index++);}, "")cellDom.innerHTML = cellInnerHtml;}renderCell(shuffle());

3.2 拖拽

需要用到的事件:touchstarttouchmovetouchend
触碰开始,需要获取元素坐标信息
移动中,需要获取鼠标坐标信息并实时更新元素位置,使用fixedlefttop控制位置,还需要设置width等来控制宽度
松开手,需要对当前位置进行判定,如处于可吸附区则吸附,如果不是则回到原来位置,并改为static
添加事件:

function bindEvent() {const wrapperList = document.querySelectorAll('.cell .wrapper');wrapperList.forEach(item => {item.addEventListener('touchstart', touchStart, false);item.addEventListener('touchmove', touchMove, false);item.addEventListener('touchend', touchEnd, false);})
}

3.2.1 元素移动的坐标要素

元素移动控制的是left和top,鼠标移动获取到的是鼠标坐标,所以在start时需要获取到鼠标坐标到元素左上角的距离,再移动时减去即可

    function touchStart(e) {const rect = this.getBoundingClientRect();mouseOffsetX = e.touches[0].clientX - rect.left;mouseOffsetY = e.touches[0].clientY - rect.top;this.style.position = 'fixed'; // 让其可以自由移动,以视窗为坐标this.style.left = rect.left + 'px';this.style.top = rect.top + 'px';this.style.width = rect.width + 'px';this.style.height = rect.height + 'px';}function touchMove(e) {e.preventDefault();this.style.left = (e.touches[0].clientX - mouseOffsetX) + 'px';this.style.top = (e.touches[0].clientY - mouseOffsetY) + 'px';}

移动时一定要取消默认事件,否则屏幕会有一段距离的滑动

3.2.2 元素结束移动的判断条件

移动结束时需要回归或吸附,此时需要保存好原始坐标的信息空白坐标信息和吸附区域的信息:

cellRect = getRect(document.querySelectorAll('.cell .wrapper')); // 原始坐标信息
blankRect = getRect(document.querySelectorAll('.blank-item .wrapper')) // 空白坐标信息
checkRange = getCheckRange(blankRect) // 吸附区域信息function getRect(eList) {return Array.prototype.slice.call(eList).map(item => {let itemRect = item.getBoundingClientRect()return { left: itemRect.left, top: itemRect.top, width: itemRect.width, height: itemRect.height }})
}
function getCheckRange(range) {return range.map(item => ({ x: [item.left, item.left + item.width], y: [item.top, item.top + item.height] }))
}

在拖拽结束时进行判断:当被拖拽元素的中心位于吸附区域内时进行吸附

function touchEnd(e) {// 获取拖动元素中心点坐标值const el = e.target;const index = this.dataset.index;const elRect = el.getBoundingClientRect();const [x, y] = [elRect.left + elRect.width / 2, elRect.top + elRect.height / 2] // 获取被拖拽元素中心for (let i = 0; i < checkRange.length; i++) {const item = checkRange[i];if (x >= item.x[0] && x <= item.x[1] && y >= item.y[0] && y <= item.y[1]) {this.style.left = blankRect[i].left + 'px';this.style.top = blankRect[i].top + 'px';return;}}const item = cellRect[parseInt(index)];this.style.left = item.left + 'px';this.style.top = item.top + 'px';this.style.position = 'static'; // 这个很重要!不改的话滑动页面字会跟着动
}

3.3 成功判定

当空白格全满且顺序与原始数据某一项相同,则成功,否则失败!

function checkIdiomFunc() {console.log("检查中")const idiom = checkIdiom.map(c => c.text).join("");const flag = idioms.find(idm => idm === idiom)if (flag != null) {alert("成功!找到:" + idiom)} else {alert("失败!!!")}checkIdiom.map(c => {let el = c.el;let index = c.index;const _el = cellRect[parseInt(index)];el.style.left = _el.left + 'px';el.style.top = _el.top + 'px';el.style.position = 'static';})checkIdiom = [];
}

在touchend事件中调用检查函数:

function touchEnd(e) {// 获取拖动元素中心点坐标值const el = e.target;const index = this.dataset.index;const elRect = el.getBoundingClientRect();const [x, y] = [elRect.left + elRect.width / 2, elRect.top + elRect.height / 2]for (let i = 0; i < checkRange.length; i++) {const item = checkRange[i];if (x >= item.x[0] && x <= item.x[1] && y >= item.y[0] && y <= item.y[1]) {if (checkIdiom[i] != null) {break;}this.style.left = blankRect[i].left + 'px';this.style.top = blankRect[i].top + 'px';checkIdiom[i] = { text: el.innerText, el, index };if (checkIdiom.length === 4 && !checkIdiom.includes(undefined)) {setTimeout(checkIdiomFunc, 500); // 此处如果不延迟,则会导致DOM还未渲染就弹出alert,因为alert是阻塞的}return;}}for (let i = 0; i < checkIdiom.length; i++) {if (checkIdiom && checkIdiom[i].index == index) {checkIdiom[i] = undefined;break;}}const item = cellRect[parseInt(index)];this.style.left = item.left + 'px';this.style.top = item.top + 'px';this.style.position = 'static'; // 这个很重要!不改的话滑动页面字会跟着动
}

4 项目整体代码

<!DOCTYPE html>
<html lang="en"><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>Document</title><script>document.documentElement.style.fontSize = document.documentElement.clientWidth / 39 + 'px';</script><style>body {margin: 0;}.blank-cell,.cell {display: flex;}.cell {flex-wrap: wrap;margin-top: 5rem;}.blank-cell .blank-item,.cell .item {width: 25%;height: 25vw;padding: .5rem;box-sizing: border-box;}.blank-cell .blank-item .wrapper,.cell .item .wrapper {width: 100%;height: 100%;/* margin: 15px; padding和border在border-box下不会影响宽度,但是margin在任何情况下都会影响宽度 */border: .1rem solid black;box-sizing: border-box;}.cell .item .wrapper {border: none;background-color: orange;font-size: 3rem;justify-content: center;align-items: center;display: flex;}</style>
</head><body><div class="container"><div class="blank-cell"><div class="blank-item"><div class="wrapper"></div></div><div class="blank-item"><div class="wrapper"></div></div><div class="blank-item"><div class="wrapper"></div></div><div class="blank-item"><div class="wrapper"></div></div></div><div class="cell"></div></div><script>const idioms = ['一发入魂', '三羊开泰', '二狗上树', '五福临门'],cellDom = document.querySelector('.cell');let mouseOffsetX = 0,mouseOffsetY = 0,cellRect = [],blankRect = [],checkRange = [],checkIdiom = [];function init() {renderCell(shuffle());cellRect = getRect(document.querySelectorAll('.cell .wrapper'));blankRect = getRect(document.querySelectorAll('.blank-item .wrapper'))checkRange = getCheckRange(blankRect)bindEvent();}function getCheckRange(range) {return range.map(item => ({ x: [item.left, item.left + item.width], y: [item.top, item.top + item.height] }))}function getRect(eList) {return Array.prototype.slice.call(eList).map(item => {let itemRect = item.getBoundingClientRect()return { left: itemRect.left, top: itemRect.top, width: itemRect.width, height: itemRect.height }})}function splitIdioms() {return idioms.reduce((pre, cur) => pre + cur).split("");}function shuffle() {return splitIdioms().sort(randomSorter)}function randomSorter() {return Math.random() > 0.5 ? 1 : -1;}function renderCell(words) {let index = 0;const cellInnerHtml = words.reduce((pre, cur) => {return pre + renderCellItem(cur, index++);}, "")cellDom.innerHTML = cellInnerHtml;}function renderCellItem(text, index) {return `<div class="item"><div class="wrapper" data-index="${index}">${text}</div></div>`}function bindEvent() {const wrapperList = document.querySelectorAll('.cell .wrapper');wrapperList.forEach(item => {item.addEventListener('touchstart', touchStart, false);item.addEventListener('touchmove', touchMove, false);item.addEventListener('touchend', touchEnd, false);})}function touchStart(e) {const rect = this.getBoundingClientRect();mouseOffsetX = e.touches[0].clientX - rect.left;mouseOffsetY = e.touches[0].clientY - rect.top;this.style.position = 'fixed'; // 让其可以自由移动,以视窗为坐标this.style.left = rect.left + 'px';this.style.top = rect.top + 'px';this.style.width = rect.width + 'px';this.style.height = rect.height + 'px';}function touchMove(e) {e.preventDefault();this.style.left = (e.touches[0].clientX - mouseOffsetX) + 'px';this.style.top = (e.touches[0].clientY - mouseOffsetY) + 'px';}function touchEnd(e) {// 获取拖动元素中心点坐标值const el = e.target;const index = this.dataset.index;const elRect = el.getBoundingClientRect();const [x, y] = [elRect.left + elRect.width / 2, elRect.top + elRect.height / 2]for (let i = 0; i < checkRange.length; i++) {const item = checkRange[i];if (x >= item.x[0] && x <= item.x[1] && y >= item.y[0] && y <= item.y[1]) {if (checkIdiom[i] != null) {break;}this.style.left = blankRect[i].left + 'px';this.style.top = blankRect[i].top + 'px';checkIdiom[i] = { text: el.innerText, el, index };if (checkIdiom.length === 4 && !checkIdiom.includes(undefined)) {setTimeout(checkIdiomFunc, 500);}return;}}for (let i = 0; i < checkIdiom.length; i++) {if (checkIdiom && checkIdiom[i].index == index) {checkIdiom[i] = undefined;break;}}const item = cellRect[parseInt(index)];this.style.left = item.left + 'px';this.style.top = item.top + 'px';this.style.position = 'static'; // 这个很重要!不改的话滑动页面字会跟着动}function checkIdiomFunc() {console.log("检查中")const idiom = checkIdiom.map(c => c.text).join("");const flag = idioms.find(idm => idm === idiom)if (flag != null) {alert("成功!找到:" + idiom)} else {alert("失败!!!")}checkIdiom.map(c => {let el = c.el;let index = c.index;const _el = cellRect[parseInt(index)];el.style.left = _el.left + 'px';el.style.top = _el.top + 'px';el.style.position = 'static';})checkIdiom = [];}init();</script>
</body></html>

纯JS实现填字游戏实战总结相关推荐

  1. AI玩填字游戏,赢得全国锦标赛冠军!官方:不是人,没有奖金和名次

    杨净 发自 家里 量子位 报道 | 公众号 QbitAI 上周,一年一度的美国填字游戏锦标赛(ACPT)落下了帷幕,共有1300多名人员线上参与. 最终官方认定,一位Tyler Hinman的玩家夺得 ...

  2. LeetCode 2018. 判断单词是否能放入填字游戏内(模拟)

    文章目录 1. 题目 2. 解题 1. 题目 给你一个 m x n 的矩阵 board ,它代表一个填字游戏 当前 的状态. 填字游戏格子中包含小写英文字母(已填入的单词),表示 空格 的 ' ' 和 ...

  3. 应用JavaFX实现的填字游戏

    题目来自我们老师这次布置的一个大作业,说实话,刚开始觉得难度超纲了,毕竟javaFX我们才刚开头,基本上没讲啥,全靠自学-自己看书敲代码,学习书上案例,然后书上没有的就百度,边学边做,现学现卖,题目基 ...

  4. python编写古诗_用Python实现古诗词填字游戏(一)

    利用古诗词做填字游戏是一项很有趣的活动,通常的填字游戏都是由几横几竖构成,如下图: 显然,横竖交叉的位置就是两句诗共有的字.那么,问题来了,如何从众多诗文中找到有共同字的句子呢? 这里Mr. PosP ...

  5. 纯js单页面赛车游戏

    纯js单页面赛车游戏 这次就是给大家分享赛车的游戏: 废话不多,直接上代码: <!DOCTYPE html> <html> <head><meta chars ...

  6. opencv3+python3.5成语填字游戏(三)成语填字游戏解密算法

    本篇介绍填字游戏解密算法,本算法尚且存在一些问题,并不适合所有成语填字游戏. 源代码:https://github.com/mayue801/crossword-puzzle--idiom 1.找到横 ...

  7. 求解3*3方格填字游戏问题(DFS + 质数)

    继续来水一道题... 一. 实验目的 加深对求解一个3 * 3方格两两相邻为质数求解算法的理解; 通过本次试验掌握将算法转换为上机操作; 加深对深度优先搜索思想的理解,理解回溯原理即实现过程,并利用其 ...

  8. [2022.1.13]UPC-2021级新生个人训练赛第22场-9782 Problem G 填字游戏

    问题 G: 填字游戏 时间限制: 1.000 Sec 内存限制: 128 M 题目描述 六一儿童节就要到了,晨晨的学校进行游园活动,其中一个游戏为填字游戏,规则为:有一个R行C列的棋盘(2≤R,C≤2 ...

  9. 最强大脑----“汉字女英雄”填字游戏研究

    最强大脑----"汉字女英雄"填字游戏研究 一.引子 江苏卫视的<最强大脑>2014年1月17日星期五第三期第一个节目,是"汉字女英雄"胡小玲所表演 ...

最新文章

  1. for oracle中pivot_Oracle PIVOT 行转列方法
  2. 基于PyTorch框架的多层全连接神经网络实现MNIST手写数字分类
  3. MySQL倒序如何避免filesort_如何避免mysql查询的filesort?
  4. Adbshell相关命令
  5. 【BZOJ2151】种树,贪心+Splay乱搞
  6. 2021永州高中高考成绩查询入口,邵阳高考成绩查询入口2021
  7. OU计算机移动无法访问,配置状态尚未应用-知识库
  8. 安卓奇葩问题之:返回按键监听,使Dialog不消失
  9. 大数据时代亟需消除八大“疑云”
  10. 多元统计之因子分析模型及Python分析示例
  11. win11 即将来临 —— 你真的会管理你的Windows 10吗?
  12. Python——用面相对象写奥特曼大战小怪兽
  13. (机械师T90外接显卡GTX-1080)Win10笔记本通过M.2接口外接独立显卡+解决错误代码43
  14. 圆桌骑士 图的连通性
  15. cisco 2821 路由器的端口映射
  16. HTML5字母为30px用h几,HTML 中 1pt 等于多少 px?
  17. 地震预警信息接收服务器,震后9秒就发布信息 我省首次实现微博地震预警
  18. ASA5520远程配置 telnet,ssh
  19. Java实现——判断一个数是否是质数
  20. Python爬虫入门-scrapy爬取拉勾网

热门文章

  1. Thinking In C++中文版
  2. 现代化高校智慧校园考勤模式
  3. RANSAC随机抽样一致算法
  4. Python科研绘图第一期——线型图(Line)、条型图(Bar)、散点图(Scatter)、子图(subplot)
  5. 大智慧公式系统:条件选股之技术指标选股
  6. Rem 字体设置学习
  7. EditorGridPanel 加载问题
  8. java随机数代码解析,实例解析常用的java随机数生成办法
  9. C语言--函数习题篇
  10. 莫烦Python[基础教程]