【JavaScript】貪吃蛇

在線試玩:GITHUB

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Snake</title>
</head>
<style>
    .button {
        background: #50bfff;
        border-radius: 5px;
        border: 1px solid #50bfff;
        color: #fff;
        cursor: pointer;
        padding: 5px 10px;
        font-size: 14px;
    }

    #stage {
        /* background: #c9c9c9; */
        border: 1px solid #c9c9c9;
        position: absolute;
    }

    #gameover {
        width: 350px;
        height: 100px;
        font-weight: bold;
        font-size: 56px;
        line-height: 100px;
        top: 125px;
        position: absolute;
    }

    #level {
        padding: 5px;
        margin: 5px;
        border: 1px solid #c9c9c9;
        border-radius: 5px;
    }

    #buttons,
    #operators {
        position: absolute;
        top: 360px;
    }

    #operators {
        right: calc(50% - 175px);
    }

    #stage,
    #gameover,
    #stage,
    #buttons {
        left: calc(50% - 175px);
    }

    .table-container {
        margin: 0;
    }
</style>

<body>
    <div id="stage"></div>
    <div id="gameover"></div>
    <section id="buttons">
        <button class="button" onclick="Start(speed)">開始</button>
        <button class="button" onclick="Pause()">暫停</button>
        <button class="button" onclick="GameInit()">重玩</button>
        <section id="level"></section>
    </section>
    <aside id="operators">
        <span>
            電腦操作:<br />
            鍵盤方向箭頭控制方向
        </span>
        <!-- <table>
            <tr>
                <td></td>
                <td><button class="button" οnclick="snake.setDirection(38)">上</button></td>
            </tr>
            <tr>
                <td><button class="button" οnclick="snake.setDirection(37)">左</button></td>
                <td><button class="button" οnclick="snake.setDirection(40)">下</button></td>
                <td><button class="button" οnclick="snake.setDirection(39)">右</button></td>
            </tr>
        </table> -->
    </aside>

    <script>
        const WIDTH = 350;
        const HEIGHT = 350;
        // const BACKGROUND_COLOR = "#C9C9C9";
        const SNAKE_COLOR = "#50bfff";
        const FOOD_COLOR = "#ff4949";
        const CELL_NUM_X = 25;
        const CELL_NUM_Y = 25;
        const SNAKE_LENGTH_INITIAL = 3;
        const LEVEL_PACE = 4; // 增加n長度就升級
        const SPEED_INIT = 200; // 初始速度ms
        const SPEED_PACE = 20; // 每關加速ms
        const LEVEL_MAX = 9; // 最大關卡

        // 舞臺
        function Stage(frame) {
            this.width = WIDTH;
            this.height = HEIGHT;
            // this.backgroundColor = BACKGROUND_COLOR;

            this._stage = frame;
            this._stage.style.width = this.width + "px";
            this._stage.style.height = this.height + "px";
        }

        // 方塊
        function Cell(x, y, c, t) {
            // Position(x,y) Color Text
            this.width = WIDTH / CELL_NUM_X;
            this.height = HEIGHT / CELL_NUM_Y;
            this.position = "absolute";
            this.color = c || 'white';
            this.x = x || 0;
            this.y = y || 0;
            this.text = t || ""
            this._cell = null;

            this.clear = function () {
                if (this._cell !== null) {
                    this._cell.parentNode.removeChild(this._cell);
                    this._cell = null;
                }
            }
            this.show = function (_stage) {
                // 第一次需要先創建元素
                if (this._cell === null) {
                    this._cell = document.createElement("div");
                    this._cell.style.width = this.width + "px";
                    this._cell.style.height = this.height + "px";
                    this._cell.style.backgroundColor = this.color;
                    this._cell.style.position = this.position;
                    this._cell.style.border = "1px solid black";
                    this._cell.innerHTML = this.text;
                    this._cell.style.fontSize = "20px";
                    // this._cell.style.fontWeight = "bold";
                    this._cell.style.lineHeight = "20px";

                    _stage.appendChild(this._cell);
                }
                // 設置元素位置
                this._cell.style.left = this.x * this.width + "px";
                this._cell.style.top = this.y * this.height + "px";
            }
        }

        // 食物
        function Food() {
            Cell.call(this, 0, 0, FOOD_COLOR);
            let that = this;
            // let superShow = this.show; // 方法2 借用父類方法作局部變量
            this.show = function (_stage) {
                // 設置元素位置
                this.x = Math.floor(Math.random() * CELL_NUM_X);
                this.y = Math.floor(Math.random() * CELL_NUM_Y);
                // 調用父類方法,實現重寫(擴展父類方法)
                new Cell().show.call(that, _stage); // 方法1
                // superShow.call(that, _stage); // 方法2
            }
        }
        Food.prototype = Object.create(Cell.prototype);
        Food.prototype.constructor = Food;

        // 蛇
        function Snake() {
            this.direction = 'horizontal';
            this.pace = 1;
            this.body = [new Cell(3, 0, SNAKE_COLOR), new Cell(2, 0, SNAKE_COLOR), new Cell(1, 0, SNAKE_COLOR)];

            this.init = function () {
                this.clear();
                this.direction = 'horizontal';
                this.pace = 1;
                this.body = [new Cell(3, 0, SNAKE_COLOR), new Cell(2, 0, SNAKE_COLOR), new Cell(1, 0, SNAKE_COLOR)];
            }

            this.clear = function () {
                for (let i = 0; i < this.body.length; i++) {
                    this.body[i].clear();
                }
            }

            this.setDirection = function (arrow, _stage, food) {
                if (this.direction === 'horizontal') {
                    switch (arrow) {
                        case 38:
                            this.pace = -1; // up
                            this.direction = 'vertical';
                            break;
                        case 40:
                            this.pace = 1; // down
                            this.direction = 'vertical';
                            break;
                    }
                } else if (this.direction === 'vertical') {
                    switch (arrow) {
                        case 37:
                            this.pace = -1; // left
                            this.direction = 'horizontal';
                            break;
                        case 39:
                            this.pace = 1; // right
                            this.direction = 'horizontal';
                            break;
                    }
                }
                // 實現按鍵加速,並修正原地掉頭bug
                this.move(_stage, food);
            }

            this.show = function (_stage) {
                for (let i = 0; i < this.body.length; i++) {
                    this.body[i].show(_stage);
                }
            }

            this.move = function (_stage, food) {
                let last = this.body.length - 1;
                // 先挪身子
                for (let i = last; i > 0; i--) {
                    this.body[i].x = this.body[i - 1].x;
                    this.body[i].y = this.body[i - 1].y;
                    this.body[i].show(_stage);
                }
                // 根據方向挪頭
                if (this.direction === 'horizontal') {
                    this.body[0].x += this.pace;
                } else if (this.direction === 'vertical') {
                    this.body[0].y += this.pace;
                }
                this.body[0].show(_stage);

                // *********************成長屬性******************//
                // 吃食物
                if (this.body[0].x === food.x && this.body[0].y === food.y) {
                    this.body.push(new Cell(this.body[last].x, this.body[last].y, SNAKE_COLOR));
                    food.show(_stage);
                }
                // 關卡升級
                updateLevel(this);

                // *********************死亡屬性*******************//
                // 撞牆
                if (this.body[0].x < 0 || this.body[0].x > CELL_NUM_X - 1 || this.body[0].y < 0 || this.body[0].y > CELL_NUM_Y - 1) {
                    GameTips("GAME OVER");
                }
                // 撞自己
                for (let i = 1; i < last; i++) {
                    if (this.body[i].x === this.body[0].x && this.body[i].y === this.body[0].y) {
                        GameTips("GAME OVER");
                    }
                }
            }
        }

        // DOM element
        let frame = document.getElementById("stage");
        let gameOver = document.getElementById("gameover");
        // 遊戲邏輯
        var stage = new Stage(frame);
        var food = new Food();
        var snake = new Snake();
        GameInit();

        var timer; // 蛇蛇移動定時器
        var speed = SPEED_INIT; // 速度
        var gameOverFlag = false; // 結束標誌

        // 遊戲開始
        function Start(_speed) {
            if (!gameOverFlag) {
                timer || (timer = window.setInterval('snake.move(stage._stage, food)', _speed));
                document.onkeydown = handleKeydown;
            }
        }
        // 遊戲暫停
        function Pause() {
            if (!gameOverFlag) {
                window.clearInterval(timer);
                timer = undefined;
                document.onkeydown = undefined;
            }
        }
        // 遊戲結束
        function GameTips(tips) {
            Pause();
            gameOver.innerHTML = tips;
            gameOverFlag = true;
        }
        // 重玩
        function GameInit() {
            Pause();
            gameOver.innerHTML = "";
            snake.init();
            snake.show(stage._stage);
            food.show(stage._stage);
            updateLevel(snake);
            gameOverFlag = false;
        }
        // 更新關卡
        function updateLevel(_snake) {
            let level = Math.floor(_snake.body.length / LEVEL_PACE);
            document.getElementById("level").innerHTML = "關卡:" + level + "<br/>" + "得分:" + (_snake.body.length - SNAKE_LENGTH_INITIAL);
            if (level === LEVEL_MAX) {
                // victory
                GameTips("VERY GOOD");
            } else {
                // 速度
                speed = SPEED_INIT - level * SPEED_PACE;
            }
        }

        // 監聽方向箭頭
        function handleKeydown() {
            if (window.event) {
                arrow = window.event.keyCode;
            } else {
                arrow = event.keyCode;
            }
            snake.setDirection(arrow, stage._stage, food)
        }

    </script>
</body>

</html>
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章