最近在学习TypeScript
的时候,跟着B站课写的一个小脚本,十分有趣!李立超老师确实深入简出,收获颇丰!
下面是编译后程序,可以使用W、A、S、D控制移动蛇身体,速度会逐渐加快,避免碰壁(网页刷新后可重新开始)
下面是关键的源码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
> tree
│ package-lock.json
│ package.json
│ tsconfig.json
│ webpack.config.js
├───dist
│ bundle.js
│ index.html
├───node_modules
└───src
│ index.html
│ index.ts
├───modules
│ Food.ts
│ GameControl.ts
│ ScorePanel.ts
│ Snake.ts
└───style
index.less
|
1
2
3
4
5
6
7
8
|
{
"compilerOptions": {
"module": "ES2015",
"target": "ES2015",
"strict": true,
"noEmitOnError": true
}
}
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
|
{
"name": "greedysnake",
"version": "1.0.0",
"description": "",
"game": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"build": "webpack",
"start": "webpack server --open chrome.exe"
},
"keywords": [],
"author": "",
"license": "ISC",
"devDependencies": {
"@babel/core": "^7.22.9",
"@babel/preset-env": "^7.22.9",
"babel-loader": "^9.1.3",
"clean-webpack-plugin": "^4.0.0",
"core-js": "^3.32.0",
"css-loader": "^6.8.1",
"html-webpack-plugin": "^5.5.3",
"less": "^4.1.3",
"less-loader": "^11.1.3",
"postcss": "^8.4.27",
"postcss-loader": "^7.3.3",
"postcss-preset-env": "^9.1.0",
"style-loader": "^3.3.3",
"ts-loader": "^9.4.4",
"typescript": "^5.1.6",
"webpack": "^5.88.2",
"webpack-cli": "^5.1.4",
"webpack-dev-server": "^4.15.1"
}
}
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
|
const path = require('path');
const HTMLWebpackPlugin = require('html-webpack-plugin');
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
module.exports = {
entry: "./src/index.ts",
output: {
path: path.resolve(__dirname, 'dist'),
filename: "bundle.js"
},
mode:'development', //production
module:{
rules: [
{
test: /\.ts$/,
use: [
{
loader: 'babel-loader',
options: {
presets:[
[
"@babel/preset-env",
{
targets: {
"chrome": "115"
},
"corejs":"3",
"useBuiltIns": "usage"
}
]
]
}
},
'ts-loader'
],
exclude: /node-modules/
},
{
test: /\.less$/,
use: [
"style-loader",
"css-loader",
{
loader: "postcss-loader",
options: {
postcssOptions: {
plugins: [
[
"postcss-preset-env",
{
browsers: "last 2 versions"
}
]
]
}
}
},
"less-loader"
]
}
]
},
plugins:[
new CleanWebpackPlugin(),
new HTMLWebpackPlugin({
title: "GreedySnake",
template: "./src/index.html"
}),
],
resolve: {
extensions: ['.ts', '.js']
}
}
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
|
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<title>贪吃蛇</title>
</head>
<body>
<div id="game">
<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>
|
1
2
3
4
|
import './style/index.less'
import GameControl from './modules/GameControl'
const gc = new GameControl();
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
class Food{
element: HTMLElement;
constructor(){
this.element = document.getElementById("food")!;
}
get X(){
return this.element.offsetLeft;
}
get Y(){
return this.element.offsetTop;
}
change(){
let top = Math.round(Math.random()*29) * 10;
let left = Math.round(Math.random()*29) * 10;
this.element.style.top = top+'px';
this.element.style.left = left+'px';
}
}
export default Food;
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
|
class Snake{
element: HTMLElement;
head: HTMLElement;
bodies: HTMLCollection;
constructor(){
this.element = document.getElementById("snake")!;
this.head = document.querySelector('#snake > div') as HTMLElement;
this.bodies = document.getElementById("snake")!.getElementsByTagName('div');
}
get X(){
return this.head.offsetLeft;
}
get Y(){
return this.head.offsetTop;
}
set X(value){
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("水平方向掉头");
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){
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("水平方向掉头");
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(){
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 body = this.bodies[i] as HTMLElement;
if (this.X === body.offsetLeft && this.Y === body.offsetTop) {
throw new Error("撞倒自己了");
}
}
}
}
export default Snake;
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
|
class ScorePanel{
score = 0;
level = 1;
maxLevel:number;
upScore:number;
scoreEle: HTMLElement;
levelEle: HTMLElement;
constructor(maxLevel:number, upScore:number){
this.maxLevel = maxLevel;
this.upScore = upScore;
this.scoreEle = document.getElementById("score")!;
this.levelEle = document.getElementById("level")!;
}
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;
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
|
import Snake from "./Snake";
import Food from "./Food";
import ScorePanel from './ScorePanel';
class GameControl{
snake: Snake;
food: Food;
scorePanel: ScorePanel;
direction: string = '';
isLive: boolean = true;
constructor(){
this.snake = new Snake();
this.food = new Food();
this.scorePanel = new ScorePanel(10, 3);
this.init();
}
init(){
document.addEventListener('keydown', this.keydownHandler.bind(this));
this.run();
}
keydownHandler(event: KeyboardEvent){
// console.log(event.key);
this.direction = event.key;
}
run(){
let X = this.snake.X;
let Y = this.snake.Y;
switch(this.direction){
case "ArrowUp":
case "Up":
case "w":
Y -= 10;
break;
case "ArrowDown":
case "Down":
case "s":
Y += 10;
break;
case "ArrowLeft":
case "Left":
case "a":
X -= 10;
break;
case "ArrowRight":
case "Right":
case "d":
X += 10;
break;
}
this.checkEat(X, Y);
try {
this.snake.X = X;
this.snake.Y = Y;
} catch (error) {
// error.message
alert("游戏结束");
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){
this.scorePanel.addScore();
this.snake.addBody();
this.food.change();
// console.log(this.snake.bodies);
}
}
}
export default GameControl;
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
|
@bg-color: #b7d4a8;
*{
margin: 0;
padding: 0;
box-sizing: border-box;
}
body{
font: bold 20px "Courier";
}
#game{
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;
#stage{
width: 304px;
height: 304px;
border: 2px solid black;
position: relative;
#snake{
&>div{
height: 10px;
width: 10px;
background-color: black;
border: 1px solid @bg-color;
position: absolute;
}
}
#food{
width: 10px;
height: 10px;
// background-color: red;
position: absolute;
display: flex;
flex-flow: row wrap;
justify-content: space-between;
align-content: space-between;
left: 40px;
top: 40px;
&>div{
height: 4px;
width: 4px;
background: black;
transform: rotate(45deg);
}
}
}
#score-panel{
width: 300px;
display: flex;
justify-content: space-between;
}
}
|
这里分享关键代码以及编译后的文件
编译后代码下载
dist目录下文件,点击html即可运行