说明
尚硅谷TypeScript教程(李立超老师TS新课)学习笔记。
贪吃蛇源码笔记:https://github.com/kaimo313/typescript-demo/tree/main/greedy-snake
项目搭建
我们以demo3的项目为基础,可以复制一份过来
在这个基础上添加less相关的处理
npm i -D less less-loader css-loader style-loader
然后添加postcss处理兼容性问题
npm i -D postcss postcss-loader postcss-preset-env
最后配置webpack
{
test: /\.less$/,
use: [
"style-loader",
"css-loader",
{
loader: "postcss-loader",
options: {
postcssOptions: {
plugins: [
[
"postcss-preset-env",
{
browsers: "last 2 versions"
}
]
]
}
}
},
"less-loader"
]
}
能将我们写的less文件编译成css即可:
body {
background-color: #eee;
display: flex;
}
项目界面
界面效果如下:
html的代码
<!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>贪吃蛇</title>
</head>
<body>
<div id="app">
<div id="stage">
<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>
样式代码
@bg-color: #ebebeb;
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font: 20px bold "Courier";
}
#app {
display: flex;
flex-flow: column;
align-items: center;
justify-content: space-around;
width: 360px;
height: 420px;
background-color: @bg-color;
margin: 100px auto;
border-radius: 6px;
box-shadow: 0 2px 10px 0 rgba(0, 0, 0, 0.4);
#stage {
width: 304px;
height: 304px;
border: 2px solid #c9c9c9;
position: relative;
#snake {
&>div {
width: 10px;
height: 10px;
background-color: blueviolet;
border: 1px solid @bg-color;
position: absolute;
}
}
#food {
width: 10px;
height: 10px;
position: absolute;
top: 100px;
left: 40px;
display: flex;
flex-flow: row wrap;
justify-content: space-between;
align-content: space-between;
&>div {
width: 4px;
height: 4px;
background-color: green;
transform: rotate(45deg);
}
}
}
#score-panel {
display: flex;
justify-content: space-between;
width: 304px;
}
}
完成Food类
class Food{
element: HTMLElement;
constructor() {
this.element = document.getElementById("food")!;
}
get X() {
return this.element.offsetLeft;
}
get Y() {
return this.element.offsetTop;
}
change() {
let left = Math.round(Math.random() * 29) * 10;
let top = Math.round(Math.random() * 29) * 10;
this.element.style.left = `${left}px`;
this.element.style.top = `${top}px`;
}
}
export default Food;
每次调用change都可以修改到食物的位置,让其在stage里随机显示
完成ScorePanel类
class ScorePanel{
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.scoreEle.innerHTML = `${++this.score}`;
if(this.score % this.upScore === 0) {
this.levelUp();
}
}
levelUp() {
if(this.level < this.maxLevel) {
this.levelEle.innerHTML = `${++this.level}`;
}
}
}
export default ScorePanel;
完成Snake类
class Snake{
head: HTMLElement;
bodies: HTMLCollection;
element: HTMLElement;
constructor() {
this.element = document.querySelector("#snake")!;
this.head = document.querySelector("#snake > div") as HTMLElement;
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) {
console.log("水平方向发生了调头");
value = value > this.X ? this.X - 10 : 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) {
console.log("垂直方向发生了调头");
value = value > this.Y ? this.Y - 10 : this.Y + 10;
}
this.moveBody();
this.head.style.top = `${value}px`;
this.checkHeadBody();
}
addBody() {
this.element.insertAdjacentElement("beforeend", document.createElement("div"));
}
moveBody() {
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;
GameControl键盘事件以及使蛇移动
import Food from "./Food";
import Snake from "./Snake";
import ScorePanel from "./ScorePanel";
class GameControl {
food: Food;
snake: Snake;
scorePanel: ScorePanel;
direction: String = "";
isLive = true;
constructor() {
this.food = new Food();
this.snake = new Snake();
this.scorePanel = new ScorePanel(10, 1);
this.init();
}
init() {
document.addEventListener("keydown", this.keydownHandler.bind(this));
this.run();
}
keydownHandler(event: KeyboardEvent) {
console.log(event);
this.direction = event.key;
}
run() {
let X = this.snake.X;
let Y = this.snake.Y;
switch(this.direction){
case "ArrowUp":
case "Up":
Y -= 10;
break;
case "ArrowRight":
case "Right":
X += 10;
break;
case "ArrowDown":
case "Down":
Y += 10;
break;
case "ArrowLeft":
case "Left":
X -= 10;
break;
}
this.checkEat(X, Y);
try {
this.snake.X = X;
this.snake.Y = Y;
} catch (e) {
alert(e);
this.isLive = false;
}
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();
}
}
}
export default GameControl;
实例化GameControl
最后引入控制器实例化即可。
import "./style/index.less";
import GameControl from "./modules/GameControl";
new GameControl();
蛇撞墙和吃食检测效果
目录结构
整体的目录结构如下:
|