javascript实现贪吃蛇游戏,贪吃蛇小游戏js代码解析
墨初 知识笔记 137阅读
提示文章写完后目录可以自动生成如何生成可参考右边的帮助文档
文章目录 前言一、逻辑设计分析二、代码实现1.TS interface2.javascript3.页面样式Sass 三、截图展示四、总结

前言
主要技术如下vue3 、vite 、ts、element-plus 、 tsx、sass、xxx.module.scss

数据定义定义
index : 总下标colNum : 列数colIdx : 列下标rowNum : 行数rowIdx : 行下标isSnake: 是否属于模型内容isHead : 是否属于模型头部isTail : 是否属于模型尾部
生成网格
1、生成整体网格数据单位2、生成所有行数据3、生成所有列数据
初始化snake
1、生成默认snake长度2、生成默认snake位置3、定义snake首尾
速度
1、定义初始速度2、定义加速度模型
生成目标
1、随机生成2、新增eat目标单元 * 生成时排除snake模型部分随机生成位置
snake移动逻辑
1、移动UI方案 * 网格背景变化 * 变化速度取移动速度间隔值2、移动方向方案 * 记录移动方向 默认向右 * 当前方向向右 向上取当前snake头部行数加一与行下标取下一次位移目标位置为头部数据 改变前进方向为向上 撞自己身体判断 向右操作无效 向右撞墙判断 撞自己身体判断 后期加入向右加速度keydown加速度keyup取消加速度 向下取当前snake头部行数减一与行下标取下一次位移目标位置为头部数据 改变前进方向为向下 撞自己身体判断 向左操作无效 * 当前方向向左 向上取当前snake头部行数加一与行下标取下一次位移目标位置为头部数据 改变前进方向为向上 撞自己身体判断 向右操作无效 向下取当前snake头部行数减一与行下标取下一次位移目标位置为头部数据 改变前进方向为向下 撞自己身体判断 向左操作无效 向左撞墙判断 撞自己身体判断 后期加入向左加速度keydown加速度keyup取消加速度 * 当前方向向上 向上操作无效 向上撞墙判断 撞自己身体判断 后期加入向上加速度keydown加速度keyup取消加速度 向右取当前snake头部列数加一与列下标取下一次位移目标位置为头部数据 改变前进方向为向右 撞自己身体判断 向下操作无效 向左操作取当前snake头部列数减一与列下标取下一次位移目标位置为头部数据 改变前进方向为向左 撞自己身体判断 * 当前方向向下 向上操作无效 向右取当前snake头部列数加一与列下标取下一次位移目标位置为头部数据 改变前进方向为向右 撞自己身体判断 向下操作无效 向下撞墙判断 撞自己身体判断 后期加入向上加速度keydown加速度keyup取消加速度 向左操作取当前snake头部列数减一与列下标取下一次位移目标位置为头部数据 改变前进方向为向左 撞自己身体判断
游戏结束判断
1、撞墙死亡2、撞自己身体死亡3、身体占满网格游戏通关
分数计算
1、身总长度减去初始身体长度
二、代码实现 1.TS interface 代码如下示例
declare namespace Ad{ namespace Game{ namespace Snake { interface BaseItem { /** 下标 */ index:number, /** 列数 */ colNum:number, /** 列坐标 */ colIdx:number, /** 行数 */ rowNum:number, /** 行坐标 */ rowIdx:number, /** 是否属于目标得分点 */ newSpot:boolean, /** 是否属于模型内容 */ isSnake:boolean, /** 是否属于模型头部 */ isHead:boolean, /** 是否属于模型尾部 */ isTail:boolean } } }}
2.javascript 代码如下
import { Ref, computed, defineComponent, reactive, ref } from vue;import SnakeScss from ./greedySnake.module.scssimport ../game.scssimport { ElMessageBox } from element-plusimport { secondsToDate } from /utils/utils;export default defineComponent({ setup(props, ctx) { /** 行-基点数 */ const row 29 /** 列-基点数 */ const column 29 /** 时间 */ let timer ref(0) /** 当前游戏状态 */ let curState ref(false) /** 贪吃蛇数据模型 */ let snakeData:Ref<Array<Ad.Game.Snake.BaseItem>> ref([]) /** 时间记录器 */ let timeStamp: undefined | NodeJS.Timer; /** 速度移动记录器 */ let timeStampMove: undefined | NodeJS.Timer; /** 前进方向 */ let direction ref(right) /** 能用做得分点的框 */ let canCreatStops:Ref<Array<number>> ref([]) /** 得分 */ let score computed(() > snakeData.value.length - 5 ) /** 移动速度 */ let speed computed(() > { const baseSpeed 500 return baseSpeed - score.value * ((500 - 40) / gridData.length) }) const snakeMove () > { let nextItem:Ad.Game.Snake.BaseItem | null null const curLastSpot snakeData.value[snakeData.value.length-1] switch(direction.value){ case top: nextItem gridData[curLastSpot.index - row] break; case right: nextItem gridData[curLastSpot.index 1] if(nextItem.rowNum ! curLastSpot.rowNum) nextItem null break; case bottom: nextItem gridData[curLastSpot.index row] break; case left: nextItem gridData[curLastSpot.index - 1] if(nextItem.rowNum ! curLastSpot.rowNum) nextItem null break; default: console.error(方向错误:, direction.value) } if(nextItem?.isSnake || !nextItem){ gameOver() } else { updateSnake(nextItem) } } const windowKeyDown (ev:KeyboardEvent) > { const { keyCode } ev ev.preventDefault() switch(keyCode){ case 37: //左键 if(direction.value right) return else direction.value left break; case 38://上键 if(direction.value bottom) return else direction.value top break; case 39://右键 if(direction.value left) return else direction.value right break; case 40://下键 if(direction.value top) return else direction.value bottom break; default: console.log(keyCode:, keyCode) } snakeMove() } /** 设置运动时间更新机制 */ const setSpeedTime () > { if(!curState.value) return if(timeStampMove)clearTimeout(timeStampMove) timeStampMove setTimeout(() > { snakeMove() setSpeedTime() }, speed.value) } /** 开始游戏 */ const startGame () > { window.addEventListener(keydown, windowKeyDown) initNewSpot() curState.value true setSpeedTime() timeStamp setInterval(() > timer.value 1, 1000) } /** 重置 */ const resetGame () > { window.removeEventListener(keydown, windowKeyDown) clearInterval(timeStamp) clearTimeout(timeStampMove) curState.value false } /** 游戏结束 */ const gameOver () > { resetGame() ElMessageBox.confirm(本次存活时间${secondsToDate(timer.value, HH*mm*ss, true )}本次得分${score.value}再接再厉。,游戏结束,{ confirmButtonText: OK, showCancelButton:false, showClose:false, closeOnClickModal:false, closeOnPressEscape:false, type: warning, }).then(res > { timer.value 0 snakeData.value [] direction.value right initGrid() }) } /** 得分、计时、操作面板生成 */ const renderHeader () > { return <div classheader> <div classheader-item> <div>时间</div> <div classnumber-value>{ secondsToDate(timer.value, HH:mm:ss, true) || 00:00:00 }</div> </div> <div classheader-item> <div>得分</div> <div classnumber-value>{ score.value || 0 }</div> </div> <div classheader-item> { curState.value ? <div classbtn onClick{ gameOver }>结束游戏</div> : <div classbtn onClick{ startGame }>开始游戏</div> } </div> </div> } /** 网格数据 */ let gridData:Array<Ad.Game.Snake.BaseItem> reactive([]) const renderGrid () > { return <div class{SnakeScss[snake-grid]}> { gridData.map(gridItem > renderGridItem(gridItem)) } </div> } const renderGridItem (gridItem:Ad.Game.Snake.BaseItem) > { const activeClass gridItem.isSnake ? SnakeScss[grid-item_active] : let directionClass:{[key:string]:string} { top:snake-title_top, right:snake-title_right, bottom:snake-title_bottom, left:snake-title_left } const headClass gridItem.isHead ? SnakeScss[directionClass[direction.value]] : const newSpotClass gridItem.newSpot ? SnakeScss[grid-item_new-spot] : return <div class{[SnakeScss[grid-item], activeClass, headClass, newSpotClass] }> { gridItem.isHead && [ <div class{SnakeScss[eye]}></div>, <div class{SnakeScss[eye]}></div> ] } </div> } /** 初始化网格 */ const initGrid () > { gridData [] const allGridNums row * column for (let index 0; index < allGridNums; index) { let currentRow Math.ceil((index 1) / row) let currentColumn (index1) % row let currentGrid:Ad.Game.Snake.BaseItem { index:index, rowNum:currentRow, rowIdx:currentColumn, colNum:currentColumn, colIdx:currentRow, newSpot:false, isSnake:false, isHead:false, isTail:false } gridData.push(currentGrid) } initSnakePosition() } /** 初始化贪吃蛇数据模型 */ const initSnakePosition () > { const rowMidDrop Math.ceil(row / 2) const columnMidDrop Math.ceil(column / 2) const mindSpotIdx ((rowMidDrop - 1) * row) (columnMidDrop - 1) const snakeDefaultSpotIdxs [mindSpotIdx-2, mindSpotIdx-1, mindSpotIdx, mindSpotIdx1, mindSpotIdx2] for (let index 0; index < snakeDefaultSpotIdxs.length; index) { gridData[snakeDefaultSpotIdxs[index]].isSnake true if(index 0){ gridData[snakeDefaultSpotIdxs[index]].isTail true } if(index snakeDefaultSpotIdxs.length - 1){ gridData[snakeDefaultSpotIdxs[index]].isHead true } snakeData.value.push(gridData[snakeDefaultSpotIdxs[index]]) } } /** * 更新贪吃蛇位置和长度 * param nextItem 下一个目标位置 */ const updateSnake (nextItem:Ad.Game.Snake.BaseItem) > { const preIdx snakeData.value[snakeData.value.length - 1].index gridData[preIdx].isHead false snakeData.value.push(nextItem) if(snakeData.value.length gridData.length){ resetGame() ElMessageBox.confirm(本次存活时间${secondsToDate(timer.value, HH*mm*ss, true )}本次得分${score.value}太厉害了游戏通关。,通关,{ confirmButtonText: OK, showCancelButton:false, showClose:false, closeOnClickModal:false, closeOnPressEscape:false, type: success, }).then(res > { timer.value 0 snakeData.value [] direction.value right initGrid() }) return } gridData[nextItem.index].isHead true gridData[nextItem.index].isSnake true if(!nextItem.newSpot) { const delItme:Ad.Game.Snake.BaseItem snakeData.value.shift() as Ad.Game.Snake.BaseItem gridData[delItme.index].isSnake false } else { gridData[snakeData.value[snakeData.value.length - 1].index].newSpot false initNewSpot() } } /** 生成新的目标得分点 */ const initNewSpot () > { const snakeDataIdxs snakeData.value.map(item > item.index) canCreatStops.value gridData.filter(item > !snakeDataIdxs.includes(item.index)).map(item > item.index) const idx Number((Math.random() * canCreatStops.value.length - 1).toFixed(0)) gridData[canCreatStops.value[idx]].newSpot true } initGrid() return () > <div classgame-content> { renderHeader() } { renderGrid() } </div> }})
3.页面样式Sass greedySnake.module.scss
.snake-grid{ display: flex; flex-wrap: wrap; border: 1px solid #eee; border-radius: 4px; margin: 0 auto; margin-top: 20px; width: 580px; .grid-item{ width: 20px; height: 20px; .eye{ width: 5px; height: 5px; background-color: #fff; border-radius: 50%; margin-left: 10px; margin-top: 3px; } } .grid-item:nth-child(2n){ background-color:rgb(248, 248, 248) } .grid-item:nth-child(2n-1){ background-color:rgb(255, 255, 255) } .grid-item_active{ background-color: rgb(0, 0, 0) !important; } .snake-title_top{ border-top-right-radius: 50%; border-top-left-radius: 50%; } .snake-title_right{ border-top-right-radius: 50%; border-bottom-right-radius: 50%; } .snake-title_bottom{ border-bottom-right-radius: 50%; border-bottom-left-radius: 50%; } .snake-title_left{ border-top-left-radius: 50%; border-bottom-left-radius: 50%; } .grid-item_new-spot{ background-color: rgb(0, 0, 0) !important; }}
game.scss
.game-content{ width: 900px; margin: 0 auto; height: 700px; margin-top: 40px; box-shadow: 0px 1px 10px 4px #ccc; border-radius: 10px; user-select: none; .header{ display: flex; justify-content: space-around; height: 60px; border-bottom: 1px solid #eee; margin: 0 20px; align-items: center; .header-item{ display: flex; align-items: center; font-weight: bold; font-size: 16px; .number-value{ font-size: 18px; } .btn{ width: 120px; border-radius: 10px; height: 36px; text-align: center; line-height: 36px; box-shadow: 0px 1px 10px 0px #ccc; cursor: pointer; } } }}
三、截图展示 实现贪吃蛇小游戏使用的技术有为了使用而使用的嫌疑使用还有些不太熟练望大家多多理解如有建议欢迎多多评论或私信指教。
标签: