贪吃蛇练习
使用TypeScript + Vue3+ Vite实现贪吃蛇的例子;
效果展示:
每次刷新,食物会随机刷新在屏幕的位置,当分数到10后,等级会加1,随后移动的速度会增加,10级为最高难度。
创建项目
npm init vite-app snakeGame
项目依赖
npm i -D less less-loader ts-loader typescript
{"name": "snakeGame","version": "0.0.0","scripts": {"dev": "vite","build": "vite build"},"dependencies": {"vue": "^3.0.4"},"devDependencies": {"@vue/compiler-sfc": "^3.0.4","less": "^4.1.2","less-loader": "^10.2.0","ts-loader": "^9.2.6","typescript": "^4.5.5","vite": "^1.0.0-rc.13"}}// package.json
项目目录结构
App.vue
<template><Game></Game></template><script>import Game from './components/Game.vue'export default {name: 'App',components:{Game}}</script>
index.css
*{margin: 0;padding: 0;box-sizing: border-box;}
main.js
import {createApp } from 'vue'import App from './App.vue'import './index.css'createApp(App).mount('#app')
components
Game.vue
<template><div id="main"><div id="state"><div id="snake"><div id="body"></div></div><div id="food"><div></div><div></div><div></div><div></div></div></div><div id="scope"><span id="score">SCORE:<span id="score2">0</span></span><span id="level">LEVEL:<span id="level2">0</span></span></div></div></template><script lang="ts">import useGame from '../modules/GameControl'import {defineComponent, reactive} from 'vue'interface student {name:string,age:number,gender:string}export default defineComponent({mounted(){// class Food {// // 设置初始属性// element:HTMLElement// // 获取食物类// constructor(){// this.element = document.getElementById('food') // }//// 定义getter方法// get x (){// return this.element.offsetLeft// }// get y (){// return this.element.offsetTop// }// change (){// this.element.style.top = Math.round(Math.random() * 290)+'px'// this.element.style.left = Math.round(Math.random() * 290)+'px'// }//}// // 测试代码// // const snake = new Food()// // console.log(snake);// // console.log(snake.y);// // snake.change() 更换初始点位置useGame()},setup(){return {}}})</script><style scoped lang="less">@bg:#b7d4a8;#main{width: 360px;height: 420px;margin: 100px auto;border: 10px solid #000;border-radius: 40px;background-color: @bg;display: flex;align-items: center;justify-content: space-around;flex-direction: column;#state{width: 304px;height: 304px;border: 2px solid #000;position: relative;#snake{&>#body{width: 10px;height: 10px;border: 1px solid @bg;background-color: #000;position: absolute;}}#food{width: 10px;height: 10px;display: flex;flex-flow: wrap;justify-content: space-between;align-content: space-between;position: absolute;top: 100px;left: 200px;div{width: 4px;height: 4px;background-color: #000;}}}#scope{width: 304px;font:20px bold Courier;display: flex;justify-content: space-between;align-items: center;#score{}#level{}}}</style>
modules
Food.ts
// 定义食物类Foodclass Food{// 定义的一个属性表示食物所对应的元素element: HTMLElement;constructor(){// 获取页面种的food元素并将其赋值给elementthis.element = document.getElementById('food')!;}// 定义一个获取食物X轴坐标的方法get X(){return this.element.offsetLeft;}// 定义一个获取食物Y轴坐标的方法get Y(){return this.element.offsetTop;}// 修改食物位置change(){// 生成一个随机的位置// 食物的位置最小是0, 最大是290// 蛇移动一次就是一格,一格大小就是10,所以要求 食物的坐标必须是整10// Math.round(Math.random() * 290);let top = Math.round(Math.random() * 29) * 10;let left = Math.round(Math.random() * 29) * 10;// Math.floor(Math.random() * 30) * 10;//向下取整this.element.style.left = top + 'px';this.element.style.top = left + 'px';}}// 测试代码// const food = new Food();// console.log(food.X, food.Y);// food.change();// console.log(food.X, food.Y);export default Food;
GameControl.ts
// 引入其他的类import Snake from "./Snake";import Food from "./Food";import ScorePanel from "./ScorePanel";// 游戏控制器,控制其他的所有类function useGame(){class GameControl {// 定义三个属性// 蛇snake: Snake;// 食物food: Food;// 记分牌scorePanel: ScorePanel;// 创建一个属性来存储蛇的移动方向(也就是按键的方向)direction: string = '';// 创建一个属性用来记录游戏是否结束isLive = true;constructor() {this.snake = new Snake();this.food = new Food();this.scorePanel = new ScorePanel(10, 2);this.init();}// 游戏的初始化方法,调用后游戏即开始init() {// 绑定键盘按下的事件document.addEventListener('keydown', this.keydownHandler.bind(this));// 涉及到this和bind知识// 调用run()方法,使蛇移动this.run();}/* ArrowUp UpArrowDown DownArrowRight RightArrowLeft Left*/// 创建一个键盘按下的响应函数keydownHandler(event: KeyboardEvent) {// console.log(this);// 需要检查event.key的值是否合法(用户是否按了正确的按键)// 修改direction属性this.direction = event.key// console.log(event.key);}// 创建一个控制蛇移动的方法run() {/* 根据方向(this.direction)来使蛇的位置改变向上 top 减少向下 top 增加向左 left 减少向右 left 增加 */// 获取蛇现在的坐标let X = this.snake.X;let Y = this.snake.Y;// 根据按键方向修改X值和Y值switch (this.direction) {case "ArrowUp":case "Up":// 向上移动 top 减少Y -= 10; break;case "ArrowDown":case "Down":// 向下移动 top 增加Y += 10;break;case "ArrowLeft":case "Left":// 向左移动 left 减少X -= 10;break;case "ArrowRight":case "Right":// 向右移动 left 增加X += 10;break;}// 检查蛇是否吃到了食物this.checkEat(X, Y);// if (this.checkEat(X, Y)) {//console.log('吃到食物了~~');//// 食物的位置进行重置//this.food.change();//// 分数增加//this.scorePanel.addScore();//// 蛇要增加一节//this.snake.addBody();// }// 修改蛇的X和Y值try {this.snake.X = X;this.snake.Y = Y;} catch (e) {// 进入到catch, 说明出现了异常,游戏结束,弹出一个提示信息alert(e.message+ 'GAME OVER!');// 将isLive设置为falsethis.isLive = false;}// 开启一个定时调用clearTimeout();this.isLive && setTimeout(this.run.bind(this), 300 - (this.scorePanel.level - 1) * 30);}// 定义一个方法,用来检查蛇是否吃到食物checkEat(X: number, Y: number){if (X === this.food.X && Y === this.food.Y) {console.log('吃到食物了~~');// 食物的位置进行重置this.food.change();// 分数增加this.scorePanel.addScore();// 蛇要增加一节this.snake.addBody();} }}const gm = new GameControl()gm.init()}export default useGame;
ScorePanel.ts
// 定义表示记分牌的类class ScorePanel{// score和level用来记录分数和等级score = 0;level = 1;// 分数和等级所在的元素,在构造函数种进行初始化scoreEle: HTMLElement;levelEle: HTMLElement;// 设置一个变量限制等级maxLevel: number;// 设置一个变量表示多少分时升级upScore: number;constructor(maxLevel: number = 10, upScore: number = 10){this.scoreEle = document.getElementById('score')!;this.levelEle = document.getElementById('level')!;this.maxLevel = maxLevel;this.upScore = upScore;}// 设置加分的方法addScore(){// 使分数自增// this.score++;// this.scoreEle.innerHTML = this.score + '';this.scoreEle.innerHTML = ++this.score + '';// 判断分数是多少if (this.score % this.upScore === 0) {this.levelUp();}}// 提升等级的方法levelUp(){if (this.level < this.maxLevel) {this.levelEle.innerHTML = ++this.level + '';}}}// 测试代码// const scorePanel = new ScorePanel(100, 2);// scorePanel.addScore();// scorePanel.addScore();// scorePanel.addScore();// for (let i = 0; i < 200; i++) {//scorePanel.addScore();// }export default ScorePanel;
Snake.ts
class Snake{// 表示蛇头的元素head: HTMLElement;// 蛇的身体(包括蛇头)bodies: HTMLCollection;// 获取蛇的容器element: HTMLElement;constructor(){this.element = this.element = document.getElementById('snake')!;this.head = document.querySelector('#snake > div') as HTMLElement;// document.querySelectorAll('#snake > div');// nodeListthis.bodies = this.element.getElementsByTagName('div');}// 获取蛇的坐标(蛇头坐标)get X(){return this.head.offsetLeft;}// 获取蛇的Y轴坐标get Y(){return this.head.offsetTop}// 设置蛇头的坐标set X(value:number){// 如果新值和旧值相同,则直接返回不再修改if (this.X === value) {return;}// X值的合法范围0-290之间if (value <0 || value > 290 ) {// 进入判断说明蛇撞墙了throw new Error("蛇撞墙了~~");}// 修改x时,是在修改水平坐标,蛇在左右移动,蛇在向左移动时,不能向右掉头,反之亦然if (this.bodies[1] && (this.bodies[1] as HTMLElement).offsetLeft === value) {// console.log('水平方向发生了掉头');// 如果发生了掉头,让蛇向方向继续移动if (value > this.X) {// 如果新值value大于旧值X, 则说明蛇在向右走,此时发生掉头,应该使蛇继续向左走value = this.X - 10;}else {// 向左走value = this.X + 10;}}// 移动身体this.moveBody();this.head.style.left = value + 'px';// 检查有没有撞自己this.checkHeadBody();}set Y(value: number){// 如果新值和旧值相同,则直接返回不再修改if (this.Y === value) {return;}// Y值的合法范围0-290之间if (value <0 || value > 290 ) {// 进入判断说明蛇撞墙了throw new Error("蛇撞墙了~~");}// 修改Y时,是在修改水平坐标,蛇在上下移动,蛇在向上移动时,不能向下掉头,反之亦然if (this.bodies[1] && (this.bodies[1] as HTMLElement).offsetTop === value) {// console.log('垂直方向发生了掉头');// 如果发生了掉头,让蛇向方向继续移动if (value > this.Y) {// 如果新值value大于旧值Y, 则说明蛇在向右走,此时发生掉头,应该使蛇继续向左走value = this.Y - 10;}else {// 向左走value = this.Y + 10;}}// 移动身体this.moveBody();this.head.style.top = value + 'px';// 检查有没有撞自己this.checkHeadBody();}// 蛇增加身体的方法addBody(){// 向element中添加一个divthis.element.insertAdjacentHTML("beforeend", "<div></div>")//添加到结束标签前}// 添加一个蛇身体移动的方法moveBody(){/* 从后往前改将后面的身体设置为前面身体的位置举例子:第4节 = 第3节的位置第3节 = 第2节的位置第2节 = 蛇头的位置*///遍历获取所有的身体for(let i = this.bodies.length-1; i > 0; i--){// 获取前面身体的位置let X = (this.bodies[i-1] as HTMLElement).offsetLeft;let Y = (this.bodies[i-1] as HTMLElement).offsetTop;// 将这个值设置到当前身体上(this.bodies[i] as HTMLElement).style.left = X + 'px';(this.bodies[i] as HTMLElement).style.top = Y + 'px';}}// 检查蛇头是否撞到身体的方法checkHeadBody(){// 获取所有的身体,检查是否和蛇头的坐标发生重叠for (let i = 1; i < this.bodies.length; i++) {let bd = this.bodies[i] as HTMLElement;if (this.X === bd.offsetLeft && this.Y === bd.offsetTop) {// 进入判断说明蛇头撞到了身体,游戏结束throw new Error("撞到自己了~~");}}}}export default Snake;