基于TypeScript的贪食蛇项目
介绍
基于TypeScript的贪食蛇项目 此项目学习于-哔哩哔哩尚硅谷TypeScript教程(李立超老师TS新课)
软件架构
TypeScript
node版本 v18.17.0
安装教程
- npm install – 安装依赖
- npm run build – 打包
- npm run start – 运行
项目演示截图
项目源码
https://gitee.com/shen_jinlong/snake-ts.git
目录结构
代码
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>贪食蛇</title>
</head>
<body>
<!-- 游戏容器 -->
<div id="main">
<!--游戏屏幕-->
<div id="screen">
<div id="snake">
<div></div>
</div>
<!-- 设置食物 -->
<div id="food">
<!-- 设置食物样式 -->
<div></div>
<div></div>
<div></div>
<div></div>
</div>
</div>
<!--记分牌-->
<div id="score-panel">
<div>
SCORE:<span id="score">0</span>
</div>
<div>
level:<span id="level">1</span>
</div>
</div>
</div>
</body>
</html>
css代码 index.less
// 设置变量
@bg-color: #b7d4a8;
// 清除默认样式
* {
margin: 0;
padding: 0;
// 改变盒子模型计算方式
box-sizing: border-box;
}
body {
font: bold 20px "Courier";
}
// 设置主窗口的样式
#main {
width: 360px;
height: 420px;
background-color: @bg-color;
margin: 100px auto;
border: 10px solid black;
border-radius: 40px;
// 开启弹性盒模型
display: flex;
// 设置主轴方向
flex-flow: column;
// 设置侧轴的对齐方式
align-items: center;
// 设置主轴的对齐方式
justify-content: space-around;
// 操作屏幕
#screen {
// 开启相对定位
position: relative;
margin-top: 20px;
width: 304px;
height: 304px;
border: 2px solid black;
#snake {
&>div {
// 开启绝对定位
position: absolute;
width: 10px;
height: 10px;
border: 1px solid @bg-color;
background-color: #000;
}
}
// 设置食物
&>#food {
position: absolute;
left: 40px;
top: 100px;
display: flex;
flex-flow: row wrap;
justify-content: space-between;
align-content: space-between;
width: 10px;
height: 10px;
&>div {
width: 4.5px;
height: 4.5px;
background-color: black;
transform: rotate(45deg);
}
}
}
// 记分牌
#score-panel {
width: 300px;
display: flex;
// 设置主轴的对齐方式
justify-content: space-around;
}
}
食物类
/** 定义食物类 */
class Food {
/* 属性 */
element: HTMLElement; // 食物元素实例
constructor() {
// 获取dom元素
this.element = document.getElementById('food')!;
}
/* 方法 */
/** 定义一个获取食物X轴坐标的方法 */
get X() {
return this.element.offsetLeft;
}
/** 定义一个获取食物Y轴坐标的方法 */
get Y() {
return this.element.offsetTop;
}
/** 生成随机位置 */
change() {
/**
* 生成随机位置
* 1. 取值范围(0~290)&& 10的倍数(配合蛇的运动)
*/
let elementLeft = Math.round(Math.random() * 29) * 10;
let elementTop = Math.round(Math.random() * 29) * 10;
// Math.floor(Math.random() * 30) * 10; // 向下取整
this.element.style.left = elementLeft + 'px';
this.element.style.top = elementTop + 'px';
}
}
export default Food;
记分牌类
/** 记分牌类 */
class ScorePanel {
/* 属性 */
score = 0;
level = 1;
scoreElement: HTMLElement;
levelElement: HTMLElement;
maxLevel: number; // 最大等级
upScore: number; // 升级需要的分数
constructor(maxLevel: number = 10, upScore: number = 10) {
this.scoreElement = document.getElementById('score')!;
this.levelElement = document.getElementById('level')!;
this.maxLevel = maxLevel;
this.upScore = upScore;
}
/** 方法 */
/** 得分 */
addScore() {
this.scoreElement.innerHTML = ++this.score + '';
if (this.score % this.upScore === 0) {
this.upLevel();
// this.upScore = this.upScore * 2;
}
}
/** 升级 */
upLevel() {
if (this.level < this.maxLevel) {
this.levelElement.innerHTML = ++this.level + '';
}
}
}
export default ScorePanel;
蛇类
class Snake {
head: HTMLElement;
// 蛇的身体(包括头部)
bodies: HTMLCollection;
// 获取蛇的容器
element: HTMLElement;
constructor() {
this.element = document.getElementById('snake')!;
this.head = document.querySelector('#snake > div')!;
this.bodies = this.element.getElementsByTagName('div');
}
/** 获取蛇头坐标 */
get X() {
return this.head.offsetLeft;
}
/** 获取蛇的轴坐标 */
get Y() {
return this.head.offsetTop;
}
/** 设置蛇头的坐标 */
set X(value: number) {
if (this.X === value) {
return;
}
// 撞墙检测
if (value < 0 || value > 290) {
// 撞墙啦
throw new Error('游戏结束!')
}
// 设置不能反方向移动
if (this.bodies[1] && (this.bodies[1] as HTMLElement).offsetLeft === value) {
value += 10;
if (value > this.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;
}
// 撞墙检测
if (value < 0 || value > 290) {
// 撞墙啦
throw new Error('游戏结束!')
}
// 设置不能反方向移动
if (this.bodies[1] && (this.bodies[1] as HTMLElement).offsetTop === value) {
value += 10;
if (value > this.Y) {
value = this.Y - 10;
} else {
value = this.Y + 10;
}
}
// 蛇移动
this.moveBody();
this.head.style.top = value + 'px';
this.checkHeadBody();
}
/** 蛇增加身体 */
addBody() {
this.element.insertAdjacentHTML("beforeend", "<div></div>");
}
/** 蛇的移动 */
moveBody() {
/**
* 后一节的身体等于前一节的身体位置
* eg:
* 第四节 = 第三节位置
* 第三节 = 第二节位置
* 第二节 = 第一节位置
* 第一节自己走了
*/
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;
控制类
// 引入其他的类
import Snake from "./Snake";
import Food from "./Food";
import ScorePanel from "./ScorePanel";
/** 游戏控制器,控制其他所有类 */
class GameControl {
// 定义三个属性
snake: Snake; // 蛇
food: Food; // 食物
scorelPanel: ScorePanel; // 记分牌
direction: string = 'ArrowRight'; // 蛇的移动方向
isLive: boolean = true; // 记录游戏是否结束
constructor() {
this.snake = new Snake();
this.food = new Food();
this.scorelPanel = new ScorePanel();
}
/** 游戏的初始化方法 */
init() {
// 绑定键盘按下的事件
// document.addEventListener('keydown', this.keydownHandler.bind(this));
// document.addEventListener('keydown', (event) => this.keydownHandler(event));
document.addEventListener('keydown', this.keydownHandler);
// 调用蛇移动
this.run();
}
/** 创建一个键盘按下的响应函数 */
keydownHandler = (event: KeyboardEvent) => {
this.direction = event.key;
}
// keydownHandler(event: KeyboardEvent) {
// this.direction = event.key;
// }
/** 创建一个控制蛇移动的方法 */
run() {
/**
* 根据方向(this.direction)来使蛇的位置改变
* 向上——top 减少
* 向下——top 增加
* 向左——left 减少
* 向右——left 增加
*/
// 获取蛇的坐标
let X = this.snake.X;
let Y = this.snake.Y;
switch (this.direction) {
case "ArrowUp":
// 向上移动top减少
Y -= 10;
break;
case "ArrowDown":
// 向下移动top增加
Y += 10;
break;
case "ArrowLeft":
// 向左移动left减少
X -= 10;
break;
case "ArrowRight":
// 向右移动left增加
X += 10;
break;
}
// 吃到食物
this.checkEat(this.snake.X, this.snake.Y);
// 修改蛇的位置
try {
this.snake.X = X;
this.snake.Y = Y;
} catch (e: any) {
// 进入到catch,出现异常,游戏结束,弹出一个提示
alert(e.message);
this.isLive = false;
}
// 开启定时调用
this.isLive && setTimeout(this.run.bind(this), 300 - (this.scorelPanel.level - 1) * 30);
// setTimeout(() => {
// this.run();
// }, 300 - (this.scorelPanel.level - 1) * 30);
}
// 检测蛇是否吃到食物
checkEat(X: number, Y: number) {
if (X === this.food.X && Y === this.food.Y) {
// 更新食物位置
this.food.change();
// 分数增加
this.scorelPanel.addScore();
// 增长蛇
this.snake.addBody();
}
}
}
export default GameControl;