设计模式总结项目练习——贪吃蛇

前言

通过设计模式写出的 demo

可拓展性强

GitHub链接

utils.js

// 符合单一职责原则
const tool = {
     
    inherit: function (target, origin) {
     
        const Fun = function () {
     
        };
        Fun.prototype = origin.prototype;
        target.prototype = new Fun();
        // 让子类的constructor重新指向自己,若不修改则会发现constructor指向的是父类的构造函数
        target.prototype.constructor = target;
    },
    extend: function (origin) {
     
        // 返回一个构造函数
        const result = function () {
     
            // 调用父类的构造函数来创建对象
            origin.apply(this, arguments);
            return this;
        }
        // 原型继承
        this.inherit(result, origin);
        return result;
    },
    single: function (origin) {
     
        // 单例模式应用,例如 蛇头 食物等  无需创建第二个,因此使用单例模式
        const singleResult = (function () {
     
            let instance;
            return function () {
     
                if (typeof instance == 'object') {
     
                    return instance;
                }
                origin && origin.apply(this, arguments);
                instance = this;
            }
        })();
        origin && this.inherit(singleResult, origin);
        return singleResult;
    },
};

initVar.js

// 初始化定义一些变量

// dom 元素初始化
const restartBtn = document.getElementById('restart');
const startBtn = document.getElementById('start');
const speedUp = document.getElementById('speed-up');
const speedDown = document.getElementById('speed-down');
const speed = document.getElementById('speed');

// 游戏场景 宽度系数—控制每行有多少个方块  高度系数-控制一共多少行
const XLen = 30;
const YLen = 30;

// 每个方块的大小
const SquareSize = 20;

// 定义游戏地图的位置
const Base_X_Point = 100;
const Base_Y_Point = 100;

// 方向枚举
const DirectionNum = {
     
    LEFT: {
     
        x: -1,
        y: 0,
        rotate: 180, //蛇头在不同的方向中 应该进行旋转
    },
    RIGHT: {
     
        x: 1,
        y: 0,
        rotate: 0,
    },
    UP: {
     
        x: 0,
        y: -1,
        rotate: -90,
    },
    DOWN: {
     
        x: 0,
        y: 1,
        rotate: 90,
    },
};

// 定义蛇的移动事件间隔
let Level = 1;
let Interval = 300;

// 定义方块
function Square(x, y, width, height, dom) {
     
    this.x = x;
    this.y = y;
    this.width = width;
    this.height = height;
    this.viewContent = dom || document.createElement('div');
}

Square.prototype.touch = function () {
     
};

// 单例模式下的 方块状态更新
Square.prototype.update = function (x, y) {
     
    this.x = x;
    this.y = y;
    this.viewContent.style.left = x * SquareSize + 'px';
    this.viewContent.style.top = y * SquareSize + 'px';
}

// 初始化构造函数
const Floor = tool.extend(Square);
const Stone = tool.extend(Square);
const Food = tool.single(Square);
const SnakeHead = tool.single(Square);
const SnakeBody = tool.extend(Square);
const Ground = tool.single(Square);
const Snake = tool.single();
const Game = tool.single();

// 蛇的状态枚举
const StrategyEnum = {
     
    MOVE: 'move',
    EAT: 'eat',
    DIE: 'die',
};

SquareFactory.js

function SquareFactory() {
     

}

SquareFactory.create = function (type, x, y, className) {
     
    if (typeof SquareFactory.prototype[type] == 'undefined') {
     
        throw new Error('no this type');
    }
    if (SquareFactory.prototype[type].prototype.__proto__ !== SquareFactory.prototype) {
     
        SquareFactory.prototype[type].prototype = new SquareFactory();
    }
    return SquareFactory.prototype[type](x, y, className);
};

SquareFactory.prototype.init = function (square, className, message) {
     
    square.viewContent.style.position = 'absolute';
    square.viewContent.style.width = square.width + 'px';
    square.viewContent.style.height = square.height + 'px';
    square.type = className;
    square.viewContent.className = className;
    square.viewContent.style.left = square.x * SquareSize + 'px';
    square.viewContent.style.top = square.y * SquareSize + 'px';
    square.touch = function () {
     
        return message;
    };
};

SquareFactory.prototype.Floor = function (x, y, className) {
     
    const oFloor = new Floor(x, y, SquareSize, SquareSize);
    this.init(oFloor, className, StrategyEnum.MOVE);
    return oFloor;
}

SquareFactory.prototype.Stone = function (x, y, className) {
     
    const oStone = new Stone(x, y, SquareSize, SquareSize);
    this.init(oStone, className, StrategyEnum.DIE);
    return oStone;
}

SquareFactory.prototype.Food = function (x, y, className) {
     
    const oFood = new Food(x, y, SquareSize, SquareSize);
    this.init(oFood, className, StrategyEnum.EAT);
    oFood.update(x, y);
    return oFood;
}

SquareFactory.prototype.SnakeHead = function (x, y, className) {
     
    const oSnakeHead = new SnakeHead(x, y, SquareSize, SquareSize);
    this.init(oSnakeHead, className, StrategyEnum.DIE);
    oSnakeHead.update(x, y);
    return oSnakeHead;
}

SquareFactory.prototype.SnakeBody = function (x, y, className) {
     
    const oSnakeBody = new SnakeBody(x, y, SquareSize, SquareSize);
    this.init(oSnakeBody, className, StrategyEnum.DIE);
    return oSnakeBody;
}

Ground.js

const oGround = new Ground(Base_X_Point, Base_Y_Point, XLen * SquareSize, YLen * SquareSize);
oGround.init = function () {
     
    this.viewContent.style.position = 'absolute';
    this.viewContent.style.left = '50%';
    this.viewContent.style.top = '50%';
    this.viewContent.style.transform = 'translateX(-50%) translateY(-50%)';
    this.viewContent.style.height = this.height + 'px';
    this.viewContent.style.width = this.width + 'px';
    this.viewContent.style.backgroundColor = '#225675';
    document.body.appendChild(this.viewContent);

    this.SquareTable = [];
    for (let y = 0; y < YLen; y++) {
     
        this.SquareTable[y] = new Array(XLen);
        for (let x = 0; x < XLen; x++) {
     
            let newSquare = null;
            if (x === 0 || y === 0 || x === XLen - 1 || y === YLen - 1) {
     
                newSquare = SquareFactory.create('Stone', x, y, 'stone');
            } else {
     
                newSquare = SquareFactory.create('Floor', x, y, 'floor');
            }
            this.viewContent.appendChild(newSquare.viewContent);
            this.SquareTable[y][x] = newSquare;
        }
    }
};

oGround.remove = function (x, y) {
     
    const curSquare = this.SquareTable[y][x];
    this.viewContent.removeChild(curSquare.viewContent);
    this.SquareTable[y][x] = null;
};

oGround.append = function (square) {
     
    this.viewContent.appendChild(square.viewContent);
    this.SquareTable[square.y][square.x] = square;
};

Snake.js

const oSnake = new Snake();
oSnake.head = null;
oSnake.tail = null;

oSnake.init = function (ground) {
     
    const snakeHead = SquareFactory.create('SnakeHead', 3, 1, 'snake-head');
    const snakeBody1 = SquareFactory.create('SnakeBody', 2, 1, 'snake-body');
    const snakeBody2 = SquareFactory.create('SnakeBody', 1, 1, 'snake-body');

    // 显示初始的蛇
    ground.remove(snakeHead.x, snakeHead.y);
    ground.append(snakeHead);

    ground.remove(snakeBody1.x, snakeBody1.y);
    ground.append(snakeBody1);

    ground.remove(snakeBody2.x, snakeBody2.y);
    ground.append(snakeBody2);

    // 形成双向链表
    snakeHead.next = snakeBody1;
    snakeHead.last = null;

    snakeBody1.next = snakeBody2;
    snakeBody1.last = snakeHead;

    snakeBody2.next = null;
    snakeBody2.last = snakeBody1;

    oSnake.head = snakeHead;
    oSnake.tail = snakeBody2;

    // 默认的方向
    this.direction = DirectionNum.RIGHT;

};

// 蛇触发的状态方法
oSnake.stategies = {
     
    move(snake, square, ground, flag) {
     
        // flag 表示是否吃到食物的移动   为true 表示吃到了食物,此时尾部不删除,若是没吃到食物,伤处尾部
        // 在原蛇头处创建一个新的蛇身体
        const newBody = SquareFactory.create('SnakeBody', snake.head.x, snake.head.y, 'snake-body');
        newBody.next = snake.head.next;
        snake.head.next.last = newBody;
        newBody.last = null;
        ground.remove(snake.head.x, snake.head.y);
        ground.append(newBody);

        // 新建蛇头
        const newHead = SquareFactory.create('SnakeHead', square.x, square.y, 'snake-head');
        newHead.viewContent.style.transform = 'rotate(' + snake.direction.rotate + 'deg)';
        ground.remove(square.x, square.y);
        ground.append(newHead);
        newHead.next = newBody;
        newHead.last = null;
        newBody.last = newHead;
        snake.head = newHead;

        if (!flag) {
     
            // 删除最后一节身体,添加地板
            const newFloor = SquareFactory.create('Floor', snake.tail.x, snake.tail.y, 'floor');
            ground.remove(snake.tail.x, snake.tail.y);
            ground.append(newFloor);
            snake.tail = snake.tail.last;
            snake.tail.next = null;
        }
    },
    eat(snake, square, ground) {
     
        this.move(snake, square, ground, true);
        oGame.score += 1;
        createFood(oGround);
    },
    die() {
     
        oGame.over();
    },
};

// 做一个预判,以蛇头为参考,根据自身的方向,判断下一个碰到的方块是什么
oSnake.move = function (ground) {
     
    const nextSquare = ground.SquareTable[this.head.y + this.direction.y][this.head.x + this.direction.x];
    if (typeof nextSquare.touch === 'function') {
     
        this.stategies[nextSquare.touch()](this, nextSquare, oGround);
    }
};

Game.js

const oGame = new Game();
oGame.timer = null;
oGame.score = 0;

// 游戏初始化
oGame.init = function () {
     
    restartBtn.style.display = 'none';
    oGame.score = 0;
    oGround.init();
    // next hit delay
    document.onkeydown = function (e) {
     
        if (e.key === 'ArrowLeft' && oSnake.direction !== DirectionNum.RIGHT) {
     
            oSnake.direction = DirectionNum.LEFT;
        } else if (e.key === 'ArrowUp' && oSnake.direction !== DirectionNum.DOWN) {
     
            oSnake.direction = DirectionNum.UP;
        } else if (e.key === 'ArrowDown' && oSnake.direction !== DirectionNum.UP) {
     
            oSnake.direction = DirectionNum.DOWN;
        } else if (e.key === 'ArrowRight' && oSnake.direction !== DirectionNum.LEFT) {
     
            oSnake.direction = DirectionNum.RIGHT;
        }
    }
};

// 游戏开始
oGame.start = function () {
     
    this.timer = setInterval(() => {
     
        oSnake.move(oGround);
    }, Interval);
};

// 游戏结束
oGame.over = function () {
     
    clearInterval(this.timer);
    alert(`最终得分为:${
       this.score}`);
    const restartBtn = document.getElementById('restart');
    restartBtn.style.display = 'block';
    restartBtn.parentNode.style.display = 'block';
};

function createFood(ground) {
     
    let foodX = null;
    let foodY = null;
    let flag = true;
    while (flag) {
     
        foodX = Math.floor(Math.random() * 28 + 1);
        foodY = Math.floor(Math.random() * 28 + 1);
        if (ground.SquareTable[foodY][foodX].type === 'floor') {
     
            flag = false;
            break;
        }
    }
    const oFood = SquareFactory.create('Food', foodX, foodY, 'food');
    ground.remove(foodX, foodY);
    ground.append(oFood);
}

oGame.init();

handleEvent.js

// 点击开始按钮
startBtn.onclick = function () {
     
    oSnake.init(oGround);
    createFood(oGround);
    this.style.display = 'none';
    this.parentNode.style.display = 'none';
    oGame.start();
}

// 点击重新开始事件
restartBtn.onclick = function () {
     
    oGame.init();
    restartBtn.parentNode.style.display = 'none';
    startBtn.onclick();
};

speed.innerHTML = Level + '';

// 加快速度
speedUp.onclick = function () {
     
    if (Level < 8) {
     
        Level += 1;
        Interval -= 20;
    }
    speed.innerHTML = Level + '';
}

// 减慢速度
speedDown.onclick = function () {
     
    if (Level > 1) {
     
        Level -= 1;
        Interval += 20;
    }
    speed.innerHTML = Level + '';
}

index.html

DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>Greedy-Snake-Protitle>
        <link rel="stylesheet" href="src/CSS/index.css">
    head>
    <body>

        <div class="btn-container">
            <div class="speed-control">
                <button id="speed-down">难度降低button>
                <div id="speed">div>
                <button id="speed-up">难度提升button>
            div>
            <button id="start">开始游戏button>
            <button id="restart" style="display: none">重新开始button>
        div>


        <script src="./src/js/utils.js">script>
        <script src="./src/js/initVar.js">script>
        <script src="./src/js/SquareFactory.js">script>
        <script src="./src/js/Ground.js">script>
        <script src="./src/js/Snake.js">script>
        <script src="./src/js/Game.js">script>
        <script src="src/js/handleEvent.js">script>
    body>
html>

index.css

:root, body {
     
    height: 100%;
    width: 100%;
    margin: 0;
    padding: 0;
    overflow: hidden;
}

.btn-container {
     
    position: relative;
    height: 100%;
    width: 100%;
    z-index: 999;
    top: 0;
    right: 0;
    left: 0;
    bottom: 0;
    background-color: rgba(0, 0, 0, 0.5);
}

.btn-container .speed-control {
     
    display: flex;
    justify-content: space-between;
    position: absolute;
    font-size: 30px;
    width: 200px;
    left: 50%;
    top: 40%;
    transform: translateX(-50%) translateY(-50%);
}


.btn-container #restart,
.btn-container #start {
     
    position: absolute;
    border-radius: 10px;
    font-size: 30px;
    width: 200px;
    height: 50px;
    left: 50%;
    top: 50%;
    transform: translateX(-50%) translateY(-50%);
}

.stone {
     
    background-color: #7dd9ff;
}

.floor {
     
    background-color: #225675;
}

.snake-head {
     
    background-image: url(../assets/snakeHead.png);
    background-size: cover;
    border-top-right-radius: 30%;
    border-bottom-right-radius: 30%;
}

.snake-body {
     
    background-color: #9ddbb1;
    border-radius: 50%;
}

.food {
     
    background-image: url(../assets/food.png);
    background-size: cover;
}

你可能感兴趣的:(#,前端——设计模式,javascript,前端,开发语言)