TypeScript 实现贪吃蛇游戏

目录

  • TypeScript 实现贪吃蛇
    • 介绍
    • 项目搭建
    • 页面以及样式设计
    • 类以及对应方法的设计
    • 实现效果

TypeScript 实现贪吃蛇

介绍

上一篇文章我们刚学习了TS的相关基础知识,因此我们来进行检验一下学习成果。我们来使用TypeScript完成一个贪吃蛇小项目。对应的文件如下:

TypeScript 实现贪吃蛇游戏_第1张图片

项目搭建

对应的配置文件如下(该配置文件的设置可以看我上一篇的文章有较为详细的介绍,这部分的配置文件在之前的基础上,引入了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();

实现效果

TypeScript 实现贪吃蛇游戏_第2张图片
TypeScript 实现贪吃蛇游戏_第3张图片
TypeScript 实现贪吃蛇游戏_第4张图片
TypeScript 实现贪吃蛇游戏_第5张图片

你可能感兴趣的:(前端,typescript,游戏,javascript)