贪吃蛇基于TypeScript

 一、实现效果

 

源码阿里云盘地址:https://www.aliyundrive.com/s/UkdDRX6DNjA 

二、文件目录

三、布局文件及样式

布局文件

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>贪吃蛇</title>
</head>
<body>
<!--游戏主容器-->
<div id="main">
    <!--设置游戏的舞台-->
    <div id="stage">
        <!-- 设置蛇-->
        <div id="snake">
            <!-- snake内部的div 表示蛇的各个部分-->
            <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>

 样式

//清除默认样式
* {
  margin: 0;
  padding: 0;
  //  改变何止模型计算方式
  box-sizing: border-box;
}

body {
  font: bold 20px "Courier";
}

//设置变量
@bg-color: #b7d4a8;
//解决滚动条的问题:#main -> margin: calc((100vh - 420px) / 2) auto;
//设置主窗口样式
#main {
  margin: calc((100vh - 420px) / 2) auto;
  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;
        //margin: 1px;
        border:2px solid @bg-color;
        //开启绝对定位
        position: absolute;

      }
    }

    & > #food {
      width: 10px;
      height: 10px;
      //background-color: red;
      //border-color: 1px solid @bg-color;
      //开启绝对定位
      position: absolute;
      left: 40px;
      top: 100px;
      //开启弹性盒
      display: flex;
      //设置主轴为横轴 wrap自动换行
      flex-flow: row wrap;
      //设置主轴和侧轴的空白空间分配到元素之间
      justify-content: space-between;
      align-content: space-between;

      & > div {
        width: 4px;
        height: 4px;
        background-color: black;
        //元素旋转45°
        transform: rotate(45deg);
      }
    }
  }

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

 三、主要程序文件

index.ts 

import "./style/index.less"
import GameControl  from "./moduls/GameControl";
new GameControl();
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,1);
        this.init()
    }

    // 游戏控制的初始化方法,调用后游戏开始
    init() {
        //     绑定键盘按键按下的事件
        document.addEventListener("keydown", this.keydownHandler.bind(this))
        //     调用run方法
        this.run();
    }

//     创建一个键盘按下的响应函数
    /*
    ArrowUp Up
    ArrowDown Down
    ArrowLeft Left
    ArrowRight Right
     */
    keydownHandler(event: KeyboardEvent) {
        // 检查event.key的值,是否合法(用户是否按了正确的按键)
        if (this.snake.bodies.length > 1) {
            switch (event.key) {
                case "ArrowUp":
                case "Up":
                    if (this.direction === "ArrowDown" || this.direction === "Down") {
                        return;
                    }
                    break;
                case "ArrowDown":
                case "Down":
                    if (this.direction === "ArrowUp" || this.direction === "Up") {
                        return;
                    }
                    break;
                case "ArrowLeft":
                case "Left":
                    if (this.direction === "ArrowRight" || this.direction === "Right") {
                        return;
                    }
                    break;
                case "ArrowRight":
                case "Right":
                    if (this.direction === "ArrowLeft" || this.direction === "Left") {
                        return;
                    }
                    break;
            }
        }

        // 修改direction属性
        this.direction = event.key;
    }

//     创建一个控制蛇移动的方法
    run() {
        /*
        *根据方向(this.direction)来改变蛇的位置
        *向上 top 减少
        * 向下top 增加
        * 向左left 减少
        * 向右left 增加
         */
        //     获取蛇的现在位置
        let X = this.snake.X;
        let Y = this.snake.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;
            case "R":
            case "r":
                this.isLive = true;
        }
        //判断是否迟到食物
        this.checkEat(X, Y);
        //     修改蛇的坐标
        try {
            this.snake.X = X;
            this.snake.Y = Y;
        } catch (e: any) {
            alert(e.message);
            this.isLive = false;
        }
        //开启一个定时器
        this.isLive && setTimeout(this.run.bind(this), 300 - (this.scorePanel.level - 1) * 30)
    }

    // 检查蛇是否迟到食物
    checkEat(X: number, Y: number) {
        if (this.food.X === X && this.food.Y === Y) {
            // 重置食物
            this.food.change();
            //     增加分数
            this.scorePanel.addScore();
            //     蛇增加一节
            this.snake.addBody();
        }
    }
}

export default GameControl;
Snake.ts 
class Snake {
//     容器
    element: HTMLElement;
//     表示蛇头的元素
    head: HTMLElement;
    // 蛇的身体(包含头部)
    bodies: HTMLCollectionOf<HTMLElement>;

    constructor() {
        this.element = document.getElementById("snake")!;
        // querySelector 获取第一个元素
        this.head = document.querySelector('#snake>div') as HTMLElement;
        // 获取snake下的所有div
        this.bodies = this.element.getElementsByTagName("div");
    }

//     获取坐标
    get X() {
        return this.head.offsetLeft;
    }

    //     定义一个获取食物Y轴坐标的方法
    get Y() {
        return this.head.offsetTop;
    }

//     设置坐标
    set X(value) {
        // 如果新值和旧值相同,则直接返回不再修改
        if (this.X == value) {
            return;
        }
        // 判断合法值
        if (value < 0 || value > 290) {
            //     抛出异常
            throw new Error("撞墙了!!");
        }
        // 移动身体;
        this.moveBody();
        this.head.style.left = value + "px";
        // 碰撞检查
        this.checkHeadBody()
    }

    //     定义一个获取食物Y轴坐标的方法
    set Y(value) {
        // 如果新值和旧值相同,则直接返回不再修改
        if (this.Y == value) {
            return;
        }
        // 判断合法值
        if (value < 0 || value > 290) {
            //     抛出异常
            throw new Error("撞墙了!!");
        }
        // 移动身体;
        this.moveBody();
        this.head.style.top = value + "px";
        // 碰撞检查
        this.checkHeadBody()
    }

    checkHeadBody() {
        let length = this.bodies.length;
        if (length < 5) {
            return
        }
        for (let i = 1; i < length; i++) {
            let temEle = this.bodies[i];
            if (this.X === temEle.offsetLeft && this.Y == temEle.offsetTop) {
                throw new Error("撞自己了!")
            }
        }
        // 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;
        //     if (this.X === X && this.Y === Y) {
        //         throw new Error("撞自己了!")
        //     }
        // }
    }

//     增加蛇的身体方法
    addBody() {
        // 向element中添加一个div
        this.element.insertAdjacentHTML("beforeend", "<div></div>");
        (this.bodies[this.bodies.length - 1] as HTMLElement).style.top = this.Y + "px";
        (this.bodies[this.bodies.length - 1] as HTMLElement).style.left = this.X + "px";
    }

//     添加一个蛇身体移动的方法
    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";
        }
    }
}

export default Snake;
Food.ts 
// 定义实物类Food
class Food {
//     定义一个属性表示实物所对应的元素
    element: HTMLElement;

    constructor() {
        //获取页面food的元素  ! 不会为空
        this.element = document.getElementById("food")!;
    }

//     定义一个获取食物X轴坐标的方法
    get X() {
        return this.element.offsetLeft;
    }

//     定义一个获取食物Y轴坐标的方法
    get Y() {
        return this.element.offsetTop;
    }

//     修改食物位置
    change() {
        // 生成一个随机位置,最小值0,最大值290,步长为10
        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;

 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.score++;
        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;

五、配置文件

webpack.config.js

//引入一个包
const path = require('path');
//     引入html插件
const HTMLWebpackPlugin = require('html-webpack-plugin');
// 引入clean插件
const {CleanWebpackPlugin} = require('clean-webpack-plugin')
// webpack中所有配置信息写在module.exports
module.exports = {
    // 开发配置
    mode: 'development',

// 指定入口文件
    entry: "./src/index.ts",
//  指定打包文件所在目录
    output: {
        // 指定打包文件所在目录
        // path: "./dist"
        path: path.resolve(__dirname, 'dist'),
        // 打包后文件的名称
        filename: "bundle.js",
        //     不适用webpack箭头函数
        environment: {
            arrowFunction: false,
            const: false
        }

    },
// 指定webpack打包时要使用模块
    module: {
        //     指定要加载的规则
        rules: [
            {
                // test指定规则生成文件
                test: /\.ts$/,
                // 要使用的loader,加载顺序,数组倒序
                use: [
                    {
                        //     指定加载器
                        loader: "babel-loader",
                        // 设置babel
                        options: {
                            // 设置预定义的环境
                            presets: [[
                                "@babel/preset-env",
                                //     配置信息
                                {
                                    // 要兼容目标浏览器
                                    targets: {
                                        "chrome": "88"
                                    },
                                    // 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: {
                                plugin: [
                                    "postcss-preset-env",
                                    {
                                        browsers: "last 2 versions"
                                    }
                                ]
                            }
                        }
                    },
                    "less-loader"
                ]
            }
        ]
    },
    // 配置webpack插件
    plugins: [
        // html插件
        new HTMLWebpackPlugin({
            // 定义网页标题
            // title: "title"
            // 定义生成模板
            template: "./src/index.html"
        }),
        //     clean插件
        new CleanWebpackPlugin()

    ],
    // 设置引用模块
    resolve: {
        extensions: ['.ts', '.js']
    }
}

tsconfig.json 

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

package.json 

{
  "name": "snake",
  "version": "1.0.0",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "build": "webpack",
    "start": "webpack serve --open"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "description": "",
  "devDependencies": {
    "@babel/core": "^7.20.5",
    "@babel/preset-env": "^7.20.2",
    "babel-loader": "^9.1.0",
    "clean-webpack-plugin": "^4.0.0",
    "core-js": "^3.26.1",
    "css-loader": "^6.7.2",
    "html-webpack-plugin": "^5.5.0",
    "less": "^4.1.3",
    "less-loader": "^11.1.0",
    "postcss": "^8.4.19",
    "postcss-loader": "^7.0.2",
    "postcss-preset-env": "^7.8.3",
    "style-loader": "^3.3.1",
    "ts-loader": "^9.4.2",
    "typescript": "^4.9.4",
    "webpack": "^5.75.0",
    "webpack-cli": "^5.0.1",
    "webpack-dev-server": "^4.11.1"
  }
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值