亚洲乱码中文字幕综合,中国熟女仑乱hd,亚洲精品乱拍国产一区二区三区,一本大道卡一卡二卡三乱码全集资源,又粗又黄又硬又爽的免费视频

JavaScript TypeScript實(shí)現(xiàn)貪吃蛇游戲完整詳細(xì)流程

 更新時(shí)間:2022年09月14日 15:49:48   作者:前端小白在前進(jìn)  
這篇文章主要介紹了JavaScript TypeScript實(shí)現(xiàn)貪吃蛇游戲流程,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)吧

項(xiàng)目背景及簡(jiǎn)介

typescript系列到這篇文章正式進(jìn)入尾聲了,我們通過(guò)以上學(xué)習(xí)ts的知識(shí),想要熟悉的掌握必須要寫(xiě)一個(gè)小demo綜合運(yùn)用所學(xué)的知識(shí),這個(gè)項(xiàng)目的目的就是綜合ts所學(xué)知識(shí),實(shí)現(xiàn)面向?qū)ο蟮膶?shí)際開(kāi)發(fā)!

項(xiàng)目地址 : https://gitee.com/liuze_quan/ts-greedy-snake

多模塊需求分析

場(chǎng)景模塊需求

  1. 具有長(zhǎng)和寬的容器,容器內(nèi)分成蛇移動(dòng)和記分牌兩個(gè)板塊
  2. 蛇移動(dòng)的場(chǎng)景設(shè)置邊界線,邊界線一旦觸碰直接結(jié)束游戲
  3. 記分牌記錄蛇吃到食物的分?jǐn)?shù)以及等級(jí)
  4. 蛇吃到食物分?jǐn)?shù)漲一分,每漲一定分?jǐn)?shù)等級(jí)提高一級(jí)
  5. 等級(jí)設(shè)有上限
  6. 等級(jí)和蛇移動(dòng)速度有關(guān)

食物類模塊需求

  1. 在游戲開(kāi)始時(shí)候食物生成在隨機(jī)位置
  2. 當(dāng)蛇吃掉食物后食物會(huì)再次隨機(jī)出現(xiàn)(出現(xiàn)的位置不能與蛇身重合)

記分牌模塊需求

  1. 設(shè)置限制等級(jí)
  2. 可以提升等級(jí)
  3. 可以增加獲取的分?jǐn)?shù)

蛇類模塊需求

  1. 游戲開(kāi)始的時(shí)候只有一個(gè)方塊(蛇頭),隨后每吃掉一個(gè)食物則增加一節(jié)身體
  2. 當(dāng)游戲進(jìn)行過(guò)程中蛇頭碰到身體則結(jié)束游戲
  3. 蛇的前進(jìn)是持續(xù)的,不能停頓下來(lái),只能去改變方向

控制模塊需求

  1. 按下方向鍵開(kāi)始游戲
  2. 只能通過(guò)四個(gè)方向鍵改變蛇前進(jìn)的方向
  3. 判斷游戲是否結(jié)束,蛇是否吃到食物
  4. 控制分?jǐn)?shù)和等級(jí)是否相應(yīng)增長(zhǎng),食物是否刷新

項(xiàng)目搭建

ts轉(zhuǎn)譯為js代碼

我們需要?jiǎng)?chuàng)建tsconfig.json文件,文件代碼如下:

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

package.json包配置文件

  1. 在這個(gè)小項(xiàng)目中我們需要webpack打包工具,所以我們要對(duì)package.json文件進(jìn)行一些配置。
  2. 選擇該項(xiàng)目在集成終端中打開(kāi)并輸入代碼npm init -y進(jìn)行項(xiàng)目初始化,這個(gè)時(shí)候會(huì)在你的項(xiàng)目中生成一個(gè)初步的package.json文件,然后我們進(jìn)一步完善
  3. 在集成終端中輸入指令npm i -D webpack webpack-cli typescript ts-loader用來(lái)下載相關(guān)依賴(如果可以看見(jiàn)package.json的depDependencies中更新了你下載的依賴表示下載成功)。i表示install下載的意思,-D意思是下載的作為依賴使用
  4. 繼續(xù)輸入指令npm i -D css-loader 等依賴,這些后面都有用
  5. 請(qǐng)注意上述代碼中scripts中的"build": "webpack"鍵值對(duì),這個(gè)設(shè)置說(shuō)明我們可以用npm run build的代碼來(lái)啟用webpack打包工具
{
  "name": "part2",
  "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.18.9",
    "@babel/preset-env": "^7.18.9",
    "babel-loader": "^8.2.5",
    "clean-webpack-plugin": "^4.0.0",
    "core-js": "^3.24.0",
    "css-loader": "^6.7.1",
    "html-webpack-plugin": "^5.5.0",
    "less": "^4.1.3",
    "less-loader": "^11.0.0",
    "postcss": "^8.4.14",
    "postcss-loader": "^7.0.1",
    "postcss-preset-env": "^7.7.2",
    "style-loader": "^3.3.1",
    "ts-loader": "^9.3.1",
    "typescript": "^4.7.4",
    "webpack": "^5.74.0",
    "webpack-cli": "^4.10.0",
    "webpack-dev-server": "^4.9.3"
  }
}

webpack.config.js打包工具配置

webpack打包文件配置中,代碼注釋非常清楚,代碼如下:

//引入一個(gè)包
const path = require('path');
//引入html插件
const HTMLWebpackPlugin = require('html-webpack-plugin')
const { CleanWebpackPlugin } = require("clean-webpack-plugin");
// webpack 中所有的配置信息都寫(xiě)道嗎module.exports中
module.exports = {
    //指定入口文件
    entry: './src/index.ts',
    //指定打包文件所在的目錄
    output: {
        //指定打包文件的目錄
        path: path.resolve(__dirname,'dist'),
        //打包后文件的名字
        filename: "bundle.js",
        //告訴webpack不使用箭頭函數(shù)
        environment: {
            arrowFunction: false
        }
    },
    mode: 'development',
    //指定webpack打包時(shí)要使用的模塊
    module: {
        //指定要加載的規(guī)則
        rules: [
            {
                //test指定規(guī)則生成的文件
                test: /\.ts$/,
                //要使用的loader
                use : [
                    //配置babel
                    {
                        // 指定加載器
                        loader: "babel-loader",
                        //設(shè)置babel
                        options: {
                            //設(shè)置預(yù)定義的環(huán)境
                            presets:[
                                [
                                    //指定環(huán)境的插件
                                    "@babel/preset-env",
                                    //配置信息
                                    {
                                        //要兼容的目標(biāo)瀏覽器
                                        targets : {
                                            "chrome" : "101"
                                        },
                                        //指定core.js的版本
                                        "corejs":"3",
                                        //使用core.js的方式 usage 按需加載
                                        "useBuiltIns": "usage"
                                    }
                                ]
                            ]
                        }
                    }
                    ,
                    'ts-loader'
                ],
                //要排除的文件
                exclude: /node-modules/
            },
            //設(shè)置less文件的處理
            {
                test : /\.less$/,
                use : [
                    "style-loader",
                    "css-loader",
                    //引入postcss
                    {
                        loader: "postcss-loader",
                        options: {
                            postcssOptions : {
                                plugins: [
                                    [
                                        "postcss-preset-env",
                                        {
                                            browsers:'last 2 versions'
                                        }
                                    ]
                                ]
                            }
                        }
                    },
                    "less-loader"
                ]
            }
        ]
    },
    //配置webpack插件
    plugins: [
        new CleanWebpackPlugin(),
        new HTMLWebpackPlugin({
            // title: "自定義的title"
            template: "./src/index.html"
        }),
    ],
    //用來(lái)設(shè)置引用模塊
    resolve: {
        extensions: ['.ts', '.js']
    }
}

到這里,我們要配置的文件都已經(jīng)配置結(jié)束,接下來(lái)正式進(jìn)入項(xiàng)目的開(kāi)發(fā)

項(xiàng)目結(jié)構(gòu)搭建

首先要進(jìn)行我們的html和css樣式搭建,搭建出來(lái)項(xiàng)目的頁(yè)面!

html文件

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>貪吃蛇</title>
</head>
<body>
<!--創(chuàng)建游戲的主容器-->
<div id="main">
    <!--設(shè)置游戲的舞臺(tái)-->
    <div id="stage">
        <!--設(shè)置蛇-->
        <div id="snake">
            <!--snake內(nèi)部的div 表示蛇的各部分-->
            <div></div>
        </div>
        <!--設(shè)置食物-->
        <div id="food">
            <!--添加四個(gè)小div 來(lái)設(shè)置食物的樣式-->
            <div></div>
            <div></div>
            <div></div>
            <div></div>
        </div>
    </div>
    <!--設(shè)置游戲的積分牌-->
    <div id="score-panel">
        <div>
            SCORE:<span id="score">0</span>
        </div>
        <div>
            level:<span id="level">1</span>
        </div>
    </div>
</div>
</body>
</html>

css文件(這里使用的是less)

// 設(shè)置變量
@bg-color: #b7d4a8;
//清除默認(rèn)樣式
* {
  margin: 0;
  padding: 0;
  //改變盒子模型的計(jì)算方式
  box-sizing: border-box;
}
body{
  font: bold 20px "Courier";
}
//設(shè)置主窗口的樣式
#main{
  width: 360px;
  height: 420px;
  // 設(shè)置背景顏色
  background-color: @bg-color;
  // 設(shè)置居中
  margin: 100px auto;
  border: 10px solid black;
  // 設(shè)置圓角
  border-radius: 40px;
  // 開(kāi)啟彈性盒模型
  display: flex;
  // 設(shè)置主軸的方向
  flex-flow: column;
  // 設(shè)置側(cè)軸的對(duì)齊方式
  align-items: center;
  // 設(shè)置主軸的對(duì)齊方式
  justify-content: space-around;
  // 游戲舞臺(tái)
  #stage{
    width: 304px;
    height: 304px;
    border: 2px solid black;
    // 開(kāi)啟相對(duì)定位
    position: relative;
    // 設(shè)置蛇的樣式
    #snake{
      &>div{
        width: 10px;
        height: 10px;
        background-color: #000;
        border: 1px solid @bg-color;
        // 開(kāi)啟絕對(duì)定位
        position: absolute;
      }
    }
    // 設(shè)置食物
    #food{
      width: 10px;
      height: 10px;
      position: absolute;
      left: 40px;
      top: 100px;
      // 開(kāi)啟彈性盒
      display: flex;
      // 設(shè)置橫軸為主軸,wrap表示會(huì)自動(dòng)換行
      flex-flow: row wrap;
      // 設(shè)置主軸和側(cè)軸的空白空間分配到元素之間
      justify-content: space-between;
      align-content: space-between;
      &>div{
        width: 4px;
        height: 4px;
        background-color: black;
        // 使四個(gè)div旋轉(zhuǎn)45度
        transform: rotate(45deg);
      }
    }
  }
  // 記分牌
  #score-panel{
    width: 300px;
    display: flex;
    // 設(shè)置主軸的對(duì)齊方式
    justify-content: space-between;
  }
}

項(xiàng)目頁(yè)面

多模塊搭建

在項(xiàng)目開(kāi)發(fā)中我們不可能把所有的代碼寫(xiě)到一個(gè)文件中,所以項(xiàng)目開(kāi)發(fā)必須會(huì)靈活運(yùn)用模塊化開(kāi)發(fā)思想,把實(shí)現(xiàn)的功能細(xì)化成一個(gè)個(gè)模塊。

完成Food(食物)類

//定義食物類
class Food {
    element : HTMLElement;
    constructor() {
        //獲取頁(yè)面中的food元素并賦給element
        this.element = document.getElementById('food')!;
    }
    //獲取食物x軸坐標(biāo)的方法
    get X() {
        return this.element.offsetLeft;
    }
    //獲取食物y軸坐標(biāo)的方法
    get Y() {
        return this.element.offsetTop;
    }
    //修改食物位置的方法
    change() {
        //生成隨機(jī)位置
        //食物的最小位置是0 最大是290
        let left = Math.round(Math.random() * 29) * 10
        let top = Math.round(Math.random() * 29) * 10
        this.element.style.left = left + 'px';
        this.element.style.top = top + 'px';
    }
}
export default Food

代碼分析:

由于在配置typescript時(shí)我們?cè)O(shè)置了strict(嚴(yán)格)模式,因此

  1. this.element = document.getElementById('food')!中如果我們不加!會(huì)讓編譯器不確定我們是否會(huì)獲取到food的dom元素而發(fā)生報(bào)錯(cuò)
  2. 準(zhǔn)備了get()方法可以在控制模塊中隨時(shí)獲取food的具體定位
  3. change()方法為隨機(jī)刷新一次food的位置
  4. export default Food 代碼加在最后。為的是把food成為全局模塊暴露出去,這樣的話其他的模塊可以調(diào)用這個(gè)food模塊

完成ScorePanel(記分牌)類

//定義表示記分牌的類
class ScorePanel {
    score : number = 0;
    level : number = 1;
    scoreSpan :HTMLElement;
    levelEle : HTMLElement;
    //設(shè)置變量限制等級(jí)
    maxLevel : number;
    //設(shè)置一個(gè)變量多少分升級(jí)
    upScore : number;
    constructor(maxLevel : number = 10,Score : number = 10) {
        this.scoreSpan = document.getElementById('score')!;
        this.levelEle  = document.getElementById('level')!;
        this.maxLevel = maxLevel
        this.upScore = Score
    }
    //設(shè)置加分的方法
    AddScore() {
        this.score++;
        this.scoreSpan.innerHTML = this.score + ''
        if (this.score % this.upScore === 0 ) {
            this.AddLevel()
        }
    }
    //提升等級(jí)
    AddLevel() {
        if (this.level < this.maxLevel) {
            this.levelEle.innerHTML = ++this.level +''
        }
    }
}
export default ScorePanel

代碼分析:

在記分牌模塊主要是兩種方法AddScore()AddLevel(),分別用來(lái)控制分?jǐn)?shù)增加和等級(jí)提升,重點(diǎn)也有設(shè)置一個(gè)變量來(lái)限制等級(jí)和設(shè)置變量來(lái)判斷多少分上升一個(gè)等級(jí)

完成Snake(蛇)類

class Snake {
    //表示蛇頭的元素
   head : HTMLElement;
   bodies : HTMLCollectionOf<HTMLElement>;
   //獲取蛇的容器
    element : HTMLElement;
    constructor() {
        this.element = document.getElementById('snake')!
        this.head = document.querySelector('#snake>div') as HTMLElement;
        this.bodies = this.element.getElementsByTagName('div')
    }
    //獲取蛇的坐標(biāo)
    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('蛇撞墻了!')
        }
        //修改x時(shí),是在修改水平坐標(biāo),蛇在左右移動(dòng),蛇在向左移動(dòng)時(shí),不能向右掉頭
        if(this.bodies[1] && (this.bodies[1] as HTMLElement).offsetLeft === value) {
            //如果發(fā)生的掉頭,讓蛇向反方向繼續(xù)移動(dòng)
            if(value > this.X) {
                //如果value大于舊值X,則說(shuō)明蛇在向右走,此時(shí)應(yīng)該發(fā)生掉頭,應(yīng)該使蛇繼續(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('蛇撞墻了!')
        }
        //修改Y時(shí),是在修改水平坐標(biāo),蛇在上下移動(dòng),蛇在向上移動(dòng)時(shí),不能向下掉頭
        if(this.bodies[1] && (this.bodies[1] as HTMLElement).offsetTop === value) {
            //如果發(fā)生的掉頭,讓蛇向反方向繼續(xù)移動(dòng)
            if(value > this.Y) {
                //如果value大于舊值X,則說(shuō)明蛇在向右走,此時(shí)應(yīng)該發(fā)生掉頭,應(yīng)該使蛇繼續(xù)向左走
                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>")
    }
    //移動(dòng)身體方法
    moveBody() {
        /*
        *將后邊的身體設(shè)置為前邊身體的位置
        * 舉例子:
        * 第四節(jié) == 第三節(jié)的位置
        * 第三節(jié) == 第二節(jié)的位置
        * 第二節(jié) == 第一節(jié)的位置
        * */
        //遍歷
        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;
            //將值設(shè)置到當(dāng)前身體上
            (this.bodies[i] as HTMLElement).style.left = x +'px';
            (this.bodies[i] as HTMLElement).style.top = y +'px';
        }
    }
    //檢查蛇頭是否撞到身體
    checkHeadBody() {
        //獲取所有的身體,檢查其是否和蛇頭的坐標(biāo)發(fā)生重疊
        for(let i =1;i<this.bodies.length;i++) {
            if(this.X === this.bodies[i].offsetLeft && this.Y === this.bodies[i].offsetTop) {
                //進(jìn)入判斷說(shuō)明蛇頭撞到了身體,游戲結(jié)束
                throw new Error('撞到自己了')
            }
        }
    }
}
export default Snake

代碼分析:

首先它自身只添加了三個(gè)功能函數(shù)addbody,movebody和checkHeadBody

movebody的實(shí)現(xiàn)邏輯非常的巧妙,根據(jù)從后往前的順序來(lái)確定位置,根據(jù)前一節(jié)的位置,從而讓后邊的位置替換到前一節(jié)的位置上,從而實(shí)現(xiàn)蛇可以移動(dòng)的邏輯。

為什么get,set,判斷蛇是否死亡機(jī)制以及之后的蛇移動(dòng)的代碼一定要寫(xiě)在constructor()函數(shù)中而不是寫(xiě)在外面?

在后面還有一個(gè)控制模塊中

首先利用get()方法獲得蛇頭坐標(biāo),當(dāng)蛇頭移動(dòng)一次以后,立刻刷新后的蛇頭坐標(biāo)反饋給蛇對(duì)象

蛇這個(gè)對(duì)象更新以后constructor代碼就會(huì)執(zhí)行一遍,執(zhí)行過(guò)程中首先蛇頭的坐標(biāo)用set()函數(shù)重新設(shè)置,然后蛇的movebody函數(shù)就會(huì)執(zhí)行一次。最后對(duì)蛇進(jìn)行判斷死沒(méi)死。

這樣一次代碼就執(zhí)行完成啦。此時(shí)整條蛇都前進(jìn)了一次。然后我們通過(guò)定時(shí)器定個(gè)時(shí)間不斷讓蛇移動(dòng)就可以了。

完成GameControl(控制)類

import Food from "./food";
import Snake from "./Snake";
import ScorePanel from "./ScorePanel";
//游戲控制器,控制其他所有類
class GameControl {
    snake : Snake;
    food : Food;
    scorePanel : ScorePanel
    direction : string = '';
    //創(chuàng)建一個(gè)變量來(lái)判斷游戲是否結(jié)束
    isLive : boolean = true;
    constructor() {
        this.snake = new Snake()
        this.food = new Food()
        this.scorePanel = new ScorePanel()
        this.init()
    }
    //游戲的初始化,調(diào)用后游戲?qū)㈤_(kāi)始
    init() {
        document.addEventListener('keydown',this.keydownHandler.bind(this))
        //調(diào)用run
        this.run()
    }
    /*ArrowUp
     ArrowDown
     ArrowLeft
     ArrowRight
     */
    //創(chuàng)建一個(gè)鍵盤(pán)按下的響應(yīng)函數(shù)
    keydownHandler(event: KeyboardEvent) {
        console.log(event.key)
        this.direction = event.key
    }
    //創(chuàng)建一個(gè)控制蛇移動(dòng)的方法
    /*
    *   根據(jù)方向(this.direction)來(lái)使蛇位置發(fā)生改變
    *
    * */
    run() {
        let X = this.snake.X;
        let Y = this.snake.Y;
        //根據(jù)方向修改值
        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))
        try {
            //修改X和Y的值
            this.snake.X = X;
            this.snake.Y = Y;
        }catch (e) {
            //進(jìn)入到catch出現(xiàn)異常
            alert((e as any).message + '游戲結(jié)束了,老表!');
            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) {
            //食物的位置要進(jìn)行重置
            this.food.change()
            //分?jǐn)?shù)增加
            this.scorePanel.AddScore()
            //蛇要增加一節(jié)
            this.snake.addBody()
        }
    }
}
export  default  GameControl

代碼分析:

我們?cè)O(shè)置控制類主要目的在于整合之前的三個(gè)類,從而在這個(gè)類中調(diào)用之前聲明的類,在該類中重點(diǎn)在于初始化游戲、控制蛇的移動(dòng)、檢查蛇是否吃到食物,這個(gè)類相當(dāng)于一個(gè)總開(kāi)關(guān)。

這里還有一個(gè)重點(diǎn)在于this指向問(wèn)題,這里使用了bind()函數(shù),bind最直接的定義就是將this指向到當(dāng)前的對(duì)象。

完成index類(啟動(dòng)項(xiàng)目)

import './style/index.less'
import GameControl from './modules/GameControl'
new GameControl()

代碼分析:

大家都非常清楚,想要讓對(duì)象執(zhí)行,我們必須要進(jìn)行實(shí)例化,這里只用new一下進(jìn)行調(diào)用即可,項(xiàng)目就可以執(zhí)行了

項(xiàng)目啟動(dòng)

最后我們打開(kāi)終端輸入npm start或者npm run build,項(xiàng)目就跑起來(lái)了,它可以自動(dòng)打開(kāi)瀏覽器進(jìn)行執(zhí)行

總結(jié)

學(xué)習(xí)完了typescript,其實(shí)最主要的在于運(yùn)用它實(shí)現(xiàn)面向?qū)ο蟮拈_(kāi)發(fā),我們?cè)谌粘i_(kāi)發(fā)中基本不會(huì)用到面向?qū)ο螅退鉫s6中涉及到類、接口等等,但是在實(shí)際中很少人去使用,面向?qū)ο蟮拈_(kāi)發(fā)中其實(shí)使得項(xiàng)目變得更加的嚴(yán)謹(jǐn)和合理化,我們?cè)跁?shū)寫(xiě)代碼的時(shí)候會(huì)更加的規(guī)范,ts的類型嚴(yán)格更加的使它方便大型項(xiàng)目開(kāi)發(fā)!

到此這篇關(guān)于JavaScript TypeScript實(shí)現(xiàn)貪吃蛇游戲完整詳細(xì)流程的文章就介紹到這了,更多相關(guān)JS TypeScript貪吃蛇內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

最新評(píng)論