欢迎来到飞鸟慕鱼博客,开始您的技术之旅!
当前位置: 首页知识笔记正文

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;            }        }    }}
三、截图展示


四、总结

实现贪吃蛇小游戏使用的技术有为了使用而使用的嫌疑使用还有些不太熟练望大家多多理解如有建议欢迎多多评论或私信指教。

标签:
声明:无特别说明,转载请标明本文来源!