上一篇文章我们刚学习了TS的相关基础知识,因此我们来进行检验一下学习成果。我们来使用TypeScript完成一个贪吃蛇小项目。对应的文件如下:
对应的配置文件如下(该配置文件的设置可以看我上一篇的文章有较为详细的介绍,这部分的配置文件在之前的基础上,引入了css的插件,来实现对css的处理。):
package.json
{
"name": "chapter02",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"build": "webpack",
"start": "webpack serve --open"
},
"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-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"
}
}
tsconfig.json
{
"compilerOptions": {
"target": "ES2015",
"module": "ES2015",
"strict": true
}
}
webpack.config.js
const path=require('path');
const HtmlWebpackPlugin = require("html-webpack-plugin");
const { CleanWebpackPlugin } = require("clean-webpack-plugin");
module.exports={
mode:'development',
entry:"./src/index.ts",
output:{
path:path.resolve(__dirname,'dist'),
filename:"bundle.js",
environment:{
arrowFunction:false,
const:false
}
},
module:{
rules:[
{
test:/\.ts$/,
use:[
{
loader:"babel-loader",
options:{
presets:[
[
"@babel/preset-env",
{
targets:{
"chrome":"88"
},
"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:"这是一个自定义的title"
template:"./src/index.html"
}),
],
resolve:{
extensions:[".ts",'.js']
}
}
再下载依赖,相关命令如下:npm i
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="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">0span>
div>
<div>
level:<span id="level">1span>
div>
div>
div>
body>
html>
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;
//游戏舞台
#stage{
width: 304px;
height: 304px;
border: 2px solid black;
position: relative;
// 设置蛇的样式
#snake{
&>div{
width: 10px;
height: 10px;
background-color:#000;
border: 1px solid @bg-color;
position: absolute;
}
}
// 设置食物
#food{
width: 10px;
height: 10px;
position: absolute;
left: 40px;
top:100px;
display: flex;
flex-flow: row wrap;
justify-content: space-between;
align-content: space-between;
&>div{
width: 4px;
height: 4px;
background-color: black;
transform: rotate(45deg);
}
}
}
//计分牌
#score-panel{
width: 300px;
display: flex;
// 设置主轴的对齐方式
justify-content: space-between;
}
}
Food.ts
// 定义食物类
class Food{
element:HTMLElement;
constructor(){
this.element=document.getElementById("food")!;
}
// 定义一个获取食物X轴坐标的方法
get X(){
return this.element.offsetLeft;
}
// 定义一个获取食物Y轴坐标的方法
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.left=left+'px';
this.element.style.top=top+'px';
}
}
export default Food;
GameControl.ts
import Snake from "./Snake";
import Food from "./Food";
import ScorePanel from "./ScorePanel";
// 游戏控制器
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));
// 调用run方法,使蛇移动
this.run();
}
// 创建一个键盘按下的响应函数
keydownHandler(event:KeyboardEvent){
// 需要检查event.key的值是否合法(用户是否按了正确的按键)
// 修改directipn属性
this.direction=event.key;
}
// 创建一个控制蛇移动的方法
run(){
// 获取蛇现在的坐标
let X=this.snake.X;
let Y=this.snake.Y;
// 根据按键方向来计算x和y值
switch(this.direction){
case "ArrowUp":
case "Up":
Y -=10;
break;
case "ArrowDown":
case "Down":
Y +=10;
break;
case "ArrowLeft":
case "Left":
X -=10;
break;
case "ArrowRight":
case "Right":
X +=10;
break;
}
// 检查蛇是否吃到了食物
this.checkEat(X,Y);
// 修改蛇的X和Y值
try{
this.snake.X=X;
this.snake.Y=Y;
}catch(e){
alert((e as Error).message+'GAME OVER!');
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.food.change();
this.scorePanel.addScore();
this.snake.addBody();
}
}
}
export default GameControl;
ScorePanel.ts
// 定义表示计分盘的类
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.leveUp();
}
}
// 提升等级方法
leveUp(){
if(this.level<this.maxLevel){
this.levelEle.innerHTML=++this.level+'';
}
}
}
export default ScorePanel;
Snake.ts
class Snake{
head:HTMLElement;
bodies:HTMLCollection;
// 获取蛇的容器
element:HTMLElement;
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;
}
// 获取蛇的Y轴坐标
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){
// 如果发生了掉头,让蛇向反方向继续移动
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){
// 如果发生了掉头,让蛇向反方向继续移动
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","")
}
// 添加一个蛇身体移动的方法
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 db=this.bodies[i] as HTMLElement;
if(this.X===db.offsetLeft && this.Y===db.offsetTop){
throw new Error("撞到自己了```");
}
}
}
}
export default Snake;
index.ts
import "./style/index.less";
import GameControl from "./moduls/GameControl";
new GameControl();