Typescript小练习之贪吃蛇

前言

贪吃蛇小练习

1 搭建项目结构

设置配置文件

下载依赖

 配置less

下载依赖

修改配置文件(webpack.config.js)

引入less文件

下载兼容css版本的依赖

修改配置文件(webpack.config.js)

2 编写代码

编写代码框架(html)

编写样式(less)

编写代码逻辑(ts)

 编写食物(Food)模块

编写记分牌(ScorePanel)模块

编写蛇(Snake)模块

编写键盘控制(GameControl)模块

总结


前言

 用typescript做一个贪吃蛇的小游戏

贪吃蛇小练习

1 搭建项目结构

设置配置文件

将前面配置好的tsconfig.json,webpack.config.js,package.json文件(文末)复制粘贴到新文件中并根据项目做简单修改

 

下载依赖

npm -i

 配置less

下载依赖

npm i -D less less-loader css-loader style-loader

修改配置文件(webpack.config.js)

引入less文件

下载兼容css版本的依赖

npm i -D postcss postcss-loader postcss-preset-env

修改配置文件(webpack.config.js)

 至此,项目结构搭建完成

2 编写代码

输入npm start开启监听便于实时更新代码

编写代码框架(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="main">
        <!-- 设置游戏的舞台 -->
        <div id="stage">
            <!-- 设置蛇 -->
            <div id="snake">
                <!-- snake内部的div,表示蛇的各部分 -->
                <div>

                </div>
            </div>
            <!--设置食物-->
            <div id="food">
                <!-- 添加4个小div,设置食物的样式 -->
                <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>
    <!-- <script src="../dist/bundle.js"></script>  -->
</body>
</html>

​

​

​

index.ts文件引入less样式

编写样式(less)

//设置变量
@bg-color:yellow;

//清除默认样式
*{
    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 blue;
    //设置圆角
    border-radius: 20px;

    //开启弹性盒模型
    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: red;
                border: 1px solid @bg-color;
                //开启绝对定位 使蛇可以移动
                position: absolute;
            }
        }
        
        //设置食物
        #food{
            width: 10px;
            height: 10px;
            position: absolute;
            left:40px;
            top:100px;
            //background-color: red;
            display: flex;
            //设置横轴为主轴,wrap表示自动换行
            flex-flow: row wrap;
            //设置主轴和侧轴的空白空间分配到元素之间
            justify-content: space-between;
            align-content: space-between;

            &>div{
                width: 5px;
                height: 5px;
                background-color: green;
                border: 1px solid yellow;
                //使4gediv旋转45度
                transform: rotate(45deg);
            }
        }
    }

    //记分牌
    #score-panel{
        width: 300px;
        display: flex;
        //设置主轴的对齐方式
        justify-content: space-between;
    }
}

编写代码逻辑(ts)

按不同功能分成多个模块,便于代码的编写及修改。

 将Food.ts,ScorePanel.ts,Snake.ts模块导入到GameControl.ts模块,由该模块来统一整合。

index.ts文件引入各模块

 

 编写食物(Food)模块

//定义类
class Food{
    //定义一个属性表示食物所对应的元素
    element:HTMLElement;
    constructor(){
        //获取页面中的food的元素并将其赋值给element
        this.element=document.getElementById("food")!;//!表示元素不可能为空
    }
    //定义一个获取食物X轴坐标的方法
    get X(){
        return this.element.offsetLeft;
    }
    //定义一个获取食物Y轴坐标的方法
    get Y(){
        return this.element.offsetTop;
    }
    //修改食物的位置
    change(){
        //生成一个随机的位置
        //食物的位置最小是0,最大是290   300-10=290
        //蛇移动一次就是一格,一格的大小就是10,所以就要求食物的坐标必须是10的倍数
        //Math.round表示四舍五入 
        //Math.floor(Math.random()*30)*10;
        //Math.floor表示向下取整
        let top=Math.round(Math.random()*29)*10;//既包括0,又包括290,而且都是10的倍数
        let left=Math.round(Math.random()*29)*10;
        this.element.style.left=left+'px';
        this.element.style.top=top+'px';
    }
}

//测试代码
// const food=new Food()
// console.log(food.X,food.Y);
// food.change();
// console.log(food.X,food.Y);

export default Food;

编写记分牌(ScorePanel)模块

//定义表示记分牌的类
class ScorePanel{
    //score和level用来记录分数和等级
    score=0;
    level=1;
    //分数和等级所在的元素,在构造函数中进行初始化
    scoreEle:HTMLElement;
    levelEle:HTMLElement;
    //设置变量限制等级
    maxLevel:number;
    //设置一个变量表示多少分时升级
    upScore:number;
    constructor(maxLevel:number=10,upScore:number=10){//不传默认10,穿了多少就多少
        this.scoreEle=document.getElementById("score")!;
        this.levelEle=document.getElementById("level")!;
        this.maxLevel=maxLevel;
        this.upScore=upScore;
    }

    //设置一个加分的方法
    addScore(){
        //使分数自增
        //this.score++;
        this.scoreEle.innerHTML=++this.score+'';//装换成字符串,innerHTML接收字符串
        //判断分数是多少
        if(this.score%this.upScore===0){
            this.levelUp();
        }
    }
    //提升等级的方法
    levelUp(){
        if(this.level<=this.maxLevel){
            this.levelEle.innerHTML=++this.level+'';
        }
    }
}

//测试代码
//const scorePanel=new ScorePanel(1,0);
// scorePanel.addScore();
// scorePanel.addScore();
// scorePanel.addScore();
// for(let i=0;i<9;i++)
// {
//     scorePanel.addScore();
// }

export default ScorePanel;//改为默认模块暴露出去

编写蛇(Snake)模块

class Snake{
    //表示蛇头的元素
    head:HTMLElement;
    //蛇的身体(包括蛇头)
    bodies:HTMLCollection;
    //获取蛇的容器
    element:HTMLElement;
    constructor(){
        this.element=document.getElementById("snake")!;
        this.head=document.querySelector("#snake>div")!as HTMLElement;
        this.bodies=this.element.getElementsByTagName("div");
    }
    //获取蛇的坐标(蛇头坐标)
    get X(){
        return this.head.offsetLeft;
    }
    //获取蛇的Y轴坐标
    get Y(){
        return this.head.offsetTop;
    }

    //设置蛇头的坐标
    set X(value:number){
        //如果新值和旧值相同,则直接返回不在修改
        if(this.X===value){
            return;
        }
        //x的值的合法范围0-290
        if(value<0||value>290){
            //进入判断说明蛇撞墙了
            throw new Error("蛇撞墙了");
        }
        //修改x时,是在修改水平坐标,蛇在左右移动,蛇在向左移动式时,不能向右掉头,反之亦然
        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:number){
         if(this.Y===value){
             return;
         }
        //y的值的合法范围0-290
         if(value<0||value>290){
             //进入判断说明蛇撞墙了
             throw new Error("蛇撞墙了");
        }
         //修改y时,是在修改垂直坐标,蛇在上下移动,蛇在向上移动式时,不能向下掉头,反之亦然
        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(){
        //向element中添加一个div
        this.element.insertAdjacentHTML("beforeend","<div></div>");
    }
    //添加一个蛇身体移动的方法
    moveBody(){
        //将后边的身体设置为前边身体的位置
        //  第4节=第3节的位置
        //  第3节=第2节的位置
        //  第2节=第1节的位置
            //遍历获取所有的身体
            for(let i=this.bodies.length-1;i>0;i--)
            {
                //获取前边身体的位置
                let X=(this.bodies[i-1]as HTMLElement).offsetLeft;//没加类型断言报错是因为bodies类型是Element,另外一个是htmlelement
                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 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();

        this.init();
    }

    //游戏的初始化方法,调用后游戏即开始
    init(){
        //绑定键盘按下的事件
        document.addEventListener('keydown',this.keydownHandle.bind(this));//加了bind使得括号里的this指向keydownHandle
        //调用run方法。使蛇移动,没开定时器时,蛇只动一次,因为只调用一次
        this.run();
    }

    /*
        ArrowUp   Up
        ArrowDown  Down
        ArrowLeft   Left
        ArrowRight   Right
    */
    //创建一个键盘按下的响应函数
    keydownHandle(event:KeyboardEvent){
        //console.log(this);没加bind之前 this指向是document
        //需要检查event.key的值是否合法(用户是否按了正确的按键)
        //修改direction属性
        this.direction=event.key;
    }

    //创建一个控制蛇移动的方法
    run(){
        //根据方向(this.direction)来使蛇的位置改变
        /*
            向上:top减少
            向下:top增加
            向左: left减少
            向右;left增加
        */
       //获取蛇现在的坐标
       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:any){
        //进入到catch,说明出现了异常,游戏结束,弹出一个提示信息
        alert(e.message+"GAME OVER");
        //将isLive设置为false
        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;

至此,项目完成,输入npm run build即可运行代码

注:所需的配置文件如下

tsconfig.json

{
  "compilerOptions": {
    "module": "es2015",
    "target": "es2015",
    "strict": true,
    "noEmitOnError": true
  }
}

package.json

{
  "name": "part2",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "build": "webpack --mode development",
    "start": "webpack serve --open --mode development "
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "@babel/core": "^7.19.3",
    "@babel/preset-env": "^7.19.4",
    "babel-loader": "^8.2.5",
    "clean-webpack-plugin": "^4.0.0",
    "core-js": "^3.25.5",
    "css-loader": "^6.7.1",
    "html-webpack-plugin": "^5.5.0",
    "less": "^4.1.3",
    "less-loader": "^11.1.0",
    "postcss": "^8.4.18",
    "postcss-loader": "^7.0.1",
    "postcss-preset-env": "^7.8.2",
    "style-loader": "^3.3.1",
    "ts-loader": "^9.4.1",
    "typescript": "^4.8.4",
    "webpack": "^5.74.0",
    "webpack-cli": "^4.10.0",
    "webpack-dev-server": "^4.11.1"
  }
}

webpack.config.js

//引入一个包
const path=require("path");
//引入html插件
const HTMLWebpackPlugin=require('html-webpack-plugin');
//引入clean插件
const {CleanWebpackPlugin}=require('clean-webpack-plugin');
const { resolve } = require("path");

//webpack中的所有的配置信息都应该写在module.exports中
module.exports={
    //指定入口文件
    entry:"./src/index.ts",

    //指定打包文件所在的目录
    output:{
        //指定打包文件的目录
        path:path.resolve(__dirname,"dist"),
        //打包后文件的文件
        filename:"bundle.js",

        //告诉webpack不使用箭头函数
        environment:{
            arrowFunction:false,
            const:false
        }
    },

    //指定webpack打包时要使用的模块
    module:{
        //指定要加载的规则
        rules:[
            {
                //test指定规则生效的文件
                test:/\.ts$/,
                //要使用的loader
                use:[
                    //配置babel
                    {
                        //指定加载器
                        loader:'babel-loader',
                        //设置babel
                        options:{
                            //设置预定义的环境
                            presets:[
                                [
                                    //指定环境的插件
                                    "@babel/preset-env",
                                    //配置信息
                                    {
                                        //要兼容的目标浏览器
                                        targets:{
                                            "edge":"106",
                                            "ie":"11",
                                            "chrome":"99"
                                        },
                                        //指定corejs的版本
                                        "corejs":"3",
                                        //使用corejs的方式 "usage"表示按需加载
                                        "useBuiltIns":"usage"
                                    }
                                ]
                            ]
                        }
                    },
                    'ts-loader',
                ],
                //要排除的文件
                exclude:/node_modules/
            },
                //设置less文件的处理
            {
                test:/\.less$/,
                use:[
                    "style-loader",
                    "css-loader",
                    //引入postcss
                    {
                        loader:"postcss-loader",
                        options:{
                            postcssOptions:{
                                plugins:[
                                    [
                                        'postcss-preset-env',
                                        {
                                            browsers:"last 2 versions"//兼容两种最新的浏览器
                                        }
                                    ]
                                ]
                            }
                        }
                    },
                    "less-loader"//loader执行顺序从下往上
                ]
            }
        ]
    },

    //配置webpack插件
    plugins:[
        new CleanWebpackPlugin(),
        new HTMLWebpackPlugin({
            //title:"这是一个自定义的title"
            template:"./src/index.html"//以他作为模板生成js文件
        }),
    ],

    //用来设置引用的模块
    resolve:{
        extensions:['.ts','.js']//以.ts ,.js结尾的文件都可以作为模块来使用
    }
}

总结

做了小练习来熟悉typescript,对typescript语法及使用有了进一步的了解,提高了编程能力。

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值