9

根据上一篇转盘抽奖,在开发消消乐游戏上又扩展了一下目录结构

变更如下:

新增mvc思想
新增事件派发机制
添加波动平均算法
添加费雪耶兹算法
添加时间控制器
取消精灵构建类
导演类dirctor变成mvc入口
游戏运行移到控制器control里面

需要技能

1、pixi.js和tweenMax.js。(这两个主要用在视图层,开发游戏精灵,也可以用原生canvas代替)
2、初步了解一下mvc的模式

目录结构

clipboard.png

Timer

时间控制器,主要是封装一下游戏运行状态和对requestAnimation进行封装

// 时间控制器
class Timer {
    constructor() {
        this.showSpirt = [];
        this.START = 1; // 开始
        this.END = 2; // 结束
        this.PAUSE = 3; // 暂停
        this.ERROR = 4; // 异常
        this.state = this.START;
        this.lastTime = 0;
        this.timer = null;
        this.timeDown = null;
        this.totalTime = 30;
    }
    run(fn) {
        var self = this;
        var requestAnimation =
            window.requestAnimationFrame ||
            window.webkitRequestAnimationFrame ||
            window.mozRequestAnimationFrame ||
            window.oRequestAnimationFrame ||
            window.msRequestAnimationFrame;
        function ani() {
            fn(self.timeDown);
            if (self.state != self.END) {
                requestAnimation(ani);
            }
        }
        this.timeDown = new Date().getTime() + this.totalTime * 1000;
        requestAnimation(ani);
    }
}
export default Timer;

Index

游戏入口主要是初始化游戏和懒加载添加所需插件

import Director from './director';
import $loader from '../../../common/util/loader';
import Loading from '../../../core/comp/loading/loading';

/*
 * popHappy 消消乐
 * */
class Game {
    constructor(dataStore, res) {
        this.gameManager = dataStore.gameManager;
        // 数据层,保存游戏全部数据
        this.dataStore = dataStore;
        this.resource = res;
        this.$container = dataStore.$container;
        this.load = Loading.getInstance(dataStore.$gameConfig.container);
        this.load.hideLoading();
    }
    addJs() {
        return Promise.all([$loader.$loaderPixi(), $loader.$loaderTweenMax()]);
    }
    // 游戏开始运行
    start() {
        // 导演实例,游戏执行核心
        this.load.showLoading();
        this.addJs().then(_ => {
            this.load.hideLoading();
            this.director = new Director(this.dataStore);
            this.director.enter();
        });
    }
}
export default Game;

Config

const config = {
    containWidth: 660, // 容器宽度
    containHeight: 950, // 容器高度
    containPaddingLeft: 20, // 左边填充值
    containPaddingTop: 20, // 上面填充值
    containY: 100, // 容器Y轴坐标
    containCol: 6, // 网格的列数量
    containRow: 9, // 网格的行数量
    containColMargin: 6, // 网格列之间距离
    containRowMargin: 4, // 网格行之间距离
    spirtWidth: 110, // 精灵元素宽度
    spirtHeight: 106 // 精灵元素高度
};
export default config;

Event

事件派发机制,主要是派发事件,用做组件通信

/**
 * @ author: leeenx
 * @ 事件封装
 * @ object.on(event, fn) // 监听一个事件
 * @ object.off(event, fn) // 取消监听
 * @ object.once(event, fn) // 只监听一次事件
 * @ object.dispacth(event, arg) // 触发一个事件
 */

export default class Events {
    constructor() {
        // 定义的事件与回调
        this.defineEvent = {};
    }
    // 注册事件
    register(event, cb) {
        if (!this.defineEvent[event]) {
            this.defineEvent[event] = [cb];
        } else {
            this.defineEvent[event].push(cb);
        }
    }
    // 派遣事件
    dispatch(event, arg) {
        if (this.defineEvent[event]) {
            /* eslint-disable */
            {
                for (
                    let i = 0, len = this.defineEvent[event].length;
                    i < len;
                    ++i
                ) {
                    this.defineEvent[event][i] &&
                        this.defineEvent[event][i](arg);
                }
            }
        }
    }
    // on 监听
    on(event, cb) {
        return this.register(event, cb);
    }
    // off 方法
    off(event, cb) {
        if (this.defineEvent[event]) {
            if (typeof cb == 'undefined') {
                delete this.defineEvent[event]; // 表示全部删除
            } else {
                // 遍历查找
                for (
                    let i = 0, len = this.defineEvent[event].length;
                    i < len;
                    ++i
                ) {
                    if (cb == this.defineEvent[event][i]) {
                        this.defineEvent[event][i] = null; // 标记为空 - 防止dispath 长度变化
                        // 延时删除对应事件
                        setTimeout(
                            () => this.defineEvent[event].splice(i, 1),
                            0
                        );
                        break;
                    }
                }
            }
        }
    }

    // once 方法,监听一次
    once(event, cb) {
        let onceCb = () => {
            cb && cb();
            this.off(event, onceCb);
        };
        this.register(event, onceCb);
    }
}

Dirctor

导演类,初始化mvc,游戏初始化布局入口,同时监听游戏结束业务逻辑。

import Model from './core/Model';
import View from './core/View';
import Control from './core/Control';
import Director from '../../comp/director/director';
class EqxDir extends Director {
    constructor(dataStore) {
        let { gameManager, $gameConfig } = dataStore;
        super(gameManager);
        this.dataStore = dataStore;
        this.$gameConfig = $gameConfig;

        // 初始化mvc
        this.model = new Model();
        this.view = new View(dataStore);
        // mv 由 c 控制
        this.constrol = new Control(this.model, this.view);

        this.event = this.constrol.event;
        // 监听游戏结束,请求提交分数接口
        this.event.on('game-over', score => {
            this.gameOver(score);
        });
    }
    enter() {
        this.constrol.enter();
    }
}

export default EqxDir;

View

视图层:

通过pixi.js初始化布局页面效果
通过tweenMax.js对精灵做动画效果处理
updated函数,监听model数据变化来处理视图显示
import config from '../config';
import HOST from '../../../../common/host';
import {
    tapstart,
    tapmove,
    tapend
} from '../../../../core/common/util/compaty';

export default class View {
    constructor(dataStore) {
        // dataStore.$container.find('canvas').remove();
        this.gameJson = dataStore.gameJson;
        this.$gameConfig = dataStore.$gameConfig;

        this.width = this.setCanvas(dataStore.$gameConfig.container).width; // 设置容器宽高
        this.height = this.setCanvas(dataStore.$gameConfig.container).height; // 设置容器宽高
        let app = new PIXI.Application({
            width: this.width,
            height: this.height,
            // backgroundColor: 0xff0000,
            resolution: 1
        });
        Object.assign(this, app);
        this.view = app.view;
        dataStore.$container.prepend(app.view);

        // 表格尺寸
        this.gridWidth = config.containWidth;
        this.gridHeight = config.containHeight;
        // 表格的行列数
        this.col = config.containCol;
        this.row = config.containRow;

        // spirte
        this.spriteWidth = config.spirtWidth;
        this.spriteHeight = config.spirtHeight;

        // 砖块数组
        this.tiles = new Array(config.containRow * config.containCol);
        // 游戏背景
        let emptySprite = PIXI.Sprite.fromImage(
            HOST.FILE + this.gameJson.staticSpirts.BGIMG.imgUrl
        );
        emptySprite.width = this.width;
        emptySprite.height = this.gameJson.staticSpirts.BGIMG.height;
        emptySprite.position.x = 0;
        emptySprite.position.y = 0;
        this.stage.addChild(emptySprite);

        // 绘制游戏区域
        this.area = new PIXI.Container();
        this.area.width = 660;
        this.area.height = 950;
        this.area.x = 45;
        this.area.y = 150;

        // 绘制一个矩形
        let rect1 = new PIXI.Graphics();
        rect1.beginFill(0x000000, 0.6);
        rect1.lineStyle();
        rect1.drawRect(0, 0, this.area._width, this.area._height);
        rect1.endFill();
        this.area.addChild(rect1);

        this.area.mask = rect1;

        // 绘制遮罩
        let rect2 = new PIXI.Graphics();
        rect2.beginFill(0x000000, 0.6);
        rect2.lineStyle();
        rect2.drawRect(0, 0, this.area._width, this.area._height);
        rect2.endFill();
        this.area.addChild(rect2);

        // 游戏单独一个容器
        this.game = new PIXI.Container();

        // 添加到舞台
        this.game.addChild(this.area);
        // 添加到舞台
        this.stage.addChild(this.game);

        // this.paused
        this.paused = true;

        this.stage.addChild(this.drawScore(), this.drawTimer());

        // 添加点击事件
        this.addClick();

        // 添加监控
        this.addWatch();
        this.total = 0;
        this.time = 30;
    }
    init() {
        // 添加监控时间事件
        this.event.on('view-time', time => {
            this.time = time;
        });
        // 显示游戏界面
        this.showGame();
        // 开启点击
        this.area.interactive = true;
        // 显示砖块
        this.area.renderable = true;
        let arr = this.tiles.map((tile, index) => {
            let { col, row } = this.getColAndRow(tile.index);
            /* eslint-disable */
            return this.topToDown.call(this, col, row, tile, index);
        });
        Promise.all(arr).then(() => {
            // 派发下掉动作完成,开启消消乐功能
            this.event.dispatch('view-start');
        });
    }
    addWatch() {
        Reflect.defineProperty(this, 'total', {
            get: () => this._total || 0,
            set: value => {
                this._total = value;
                this.scoreLabel.text = value;
            }
        });
        Reflect.defineProperty(this, 'time', {
            get: () => this._time || 30,
            set: value => {
                this._time = value;
                this.timeLabel.text = value;
            }
        });
    }
    drawScore() {
        // 绘制头像,分数组合和透明矩形
        return scoreC;
    }
    drawTimer() {
        // 绘制时间,文本和遮罩
        return scoreC;
    }

    addClick() {
        let isClick = false,
            initX,
            initY,
            initTime,
            cScale = this.$gameConfig['cScale'] || 1;
        // 添加移动开始事件
        this.view.addEventListener(tapstart, event => {
            if (this.paused === true) return;
            initX = event.offsetX / cScale - this.area.x;
            initY = event.targetTouches[0].clientY - this.area.y;
            initTime = new Date().getTime();
        });
        this.view.addEventListener(tapmove, event => {
            // 暂停不触发事件,移动过程中,不出发移动事件
            if (this.paused === true) return;
            let time = new Date().getTime();
            if (time - initTime >= 30) {
                // 移动只触发一次
                if (isClick == true) return;
                isClick = true;
                // let x = event.offsetX / cScale - this.area.x;
                // let y = event.offsetY / cScale - this.area.y;
                let x = event.targetTouches[0].clientX - this.area.x;

                let y = event.targetTouches[0].clientY - this.area.y;
                let angel = getAngel({ x: initX, y: initY }, { x, y });
                let orientation = 0;
                if (angel >= -45 && angel < 45) {
                    orientation = 3;
                } else if (angel >= -135 && angel < -45) {
                    orientation = 0;
                } else if (angel >= 45 && angel < 135) {
                    orientation = 1;
                } else {
                    orientation = 2;
                }
                let col = (initX / this.spriteWidth) >> 0,
                    row = (initY / this.spriteHeight) >> 0;
                let position = col * this.row + row;
                this.event.dispatch('view-tap', { position, orientation });
            }
        });
        this.view.addEventListener(tapend, function(event) {
            // 暂停不触发事件
            setTimeout(() => {
                isClick = false;
            }, 600);
        });
        // 计算角度
        function getAngel(origin, target) {
            let rX = target['x'] - origin['x'];
            let rY = target['y'] - origin['y'];
            let angel = (Math.atan2(rY, rX) / Math.PI) * 180;
            return angel;
        }
    }

    // 初始化下掉动画
    topToDown(col, row, tile, i) {
        return new Promise(resolve => {
            TweenMax.to(tile.sprite, 0.5, {
                x: col * this.spriteWidth + this.spriteWidth / 2,
                y: row * this.spriteHeight + this.spriteHeight / 2,
                delay: ((i / this.col) >> 0) * 0.05,
                ease: Linear.easeNone,
                onComplete: () => {
                    resolve();
                }
            });
        });
    }
    // 获取当前砖块的横纵位置
    getColAndRow(index) {
        // let { index } = tile;
        let col = (index / this.row) >> 0;
        let row = index % this.row;
        return { col, row };
    }

    // 生成对应的精灵
    generateSpirt(clr = 5) {
        let imgObj = [
            HOST.FILE + this.gameJson.dynamicSpirts[0],
            HOST.FILE + this.gameJson.dynamicSpirts[1],
            HOST.FILE + this.gameJson.dynamicSpirts[2],
            HOST.FILE + this.gameJson.dynamicSpirts[3],
            HOST.FILE + this.gameJson.dynamicSpirts[4]
        ];
        /* eslint-disalbe */
        let sprite = new PIXI.Sprite.fromImage(imgObj[clr]);
        sprite.width = this.spriteWidth;
        sprite.height = this.spriteHeight;
        sprite.x = 280;
        sprite.anchor.x = 0.5;
        sprite.anchor.y = 0.5;
        return sprite;
    }
    // 更新砖块
    update({ originIndex, index, clr, removed, score, type }) {
        if (originIndex === undefined || clr === undefined) return;
        let tile = this.tiles[originIndex];
        // tile 不存在,生成对应砖块
        if (tile === undefined) {
            this.tiles[originIndex] = tile = {
                sprite: this.generateSpirt(clr),
                clr,
                originIndex,
                index,
                removed: false
            };
            // 添加到舞台
            this.area.addChild(tile.sprite);
        }

        if (tile.removed !== removed) {
            this.bomb(removed, tile, index);
        }
        // index当前索引发生改变,表示位置发生改变
        if (tile.index !== index) {
            this.updateTileIndex(tile, index, type);
        }
        // tile 存在,判断颜色是否一样
        else if (tile.clr !== clr) {
            this.updateTileClr(tile, clr);
        }
    }
    // 砖块位置变化
    updateTileIndex(tile, index, type) {
        let { col, row } = this.getColAndRow(index || tile.originIndex);
        let x = col * this.spriteWidth;
        let y = row * this.spriteHeight;
        if (type == 2) {
            // 交换位置
            TweenMax.to(tile.sprite, 0.2, {
                x: x + this.spriteWidth / 2,
                y: y + this.spriteHeight / 2,
                ease: Linear.easeNone
            });
        } else if (tile.index < index) {
            // 游戏过程,未消除的砖块下落
            TweenMax.to(tile.sprite, 0.2, {
                x: x + this.spriteWidth / 2,
                y: y + this.spriteHeight / 2,
                delay: 0,
                ease: Linear.easeNone
            });
        }
        tile.index = index;
    }
    // 颜色发生改变
    updateTileClr(tile, clr) {
        if (clr === undefined) return;
        tile.sprite = this.generateSpirt(clr);
        tile.clr = clr;
    }

    // 消除砖块和添加砖块
    bomb(removed, tile, index) {
        if (removed === true) {
            // 游戏过程,有动画 缩小
            TweenMax.to(tile.sprite, 0.2, {
                width: 0,
                height: 0,
                ease: Linear.easeNone,
                onComplete: () => {
                    this.area.removeChild(tile.sprite);
                    tile.sprite.width = this.spriteWidth;
                    tile.sprite.height = this.spriteHeight;
                    tile.removed = removed;
                    this.total += 3;
                }
            });
        } else {
            // 从上倒下下落动画
            this.area.addChild(tile.sprite);
            let { col, row } = this.getColAndRow(index);
            let x = col * this.spriteWidth;
            let y = row * this.spriteHeight;

            // 游戏过程,有动画
            TweenMax.fromTo(
                tile.sprite,
                0.2,
                {
                    x: x + this.spriteWidth / 2,
                    y:
                        -this.spriteHeight * (this.row - row) +
                        this.spriteHeight / 2,
                    delay: 0,
                    ease: Linear.easeNone
                },
                {
                    x: x + this.spriteWidth / 2,
                    y: y + this.spriteHeight / 2,
                    delay: 0,
                    ease: Linear.easeNone,
                    onComplete: () => {
                        tile.removed = removed;
                    }
                }
            );
        }
    }

    // 显示游戏界面
    showGame() {
        this.game.renderable = true;
    }
    // 设置容器宽高
    setCanvas($container) {
        let container =
            $container.selector == 'body' ? $container : $container.parent();
        let w = container.width();
        let h = container.height();
        let width = 750;
        let height = (h * width) / w;
        return { width, height };
    }
    // 暂停按钮
    stop() {
        this.paused = true;
    }
    // 恢复渲染
    resume() {
        this.paused = false;
    }
}

Model

数据层,主要做数据处理,包括砖块数量、打散砖块、改变位置、计算消除砖块

import quickWave from '../libs/quickWave';
import shuffle from '../libs/shuffle';
import config from '../config';

export default class Model {
    constructor() {
        // 行列数
        this.row = config.containRow;
        this.col = config.containCol;

        // 表格总数 6*9
        this.gridCellCount = config.containCol * config.containRow;

        // 砖块
        this.tiles = new Array(this.gridCellCount);
        for (let i = 0; i < this.gridCellCount; ++i) {
            this.tiles[i] = {
                // 是否移除
                removed: false
            };
        }
        // 游戏状态
        this.state = true;
    }

    // 填充数组 ---- count 表示几种颜色
    init() {
        // 色砖小计数
        let subtotal = 0;
        // 波动均分色块
        let arr = quickWave(5, 4, 4); // 此处可优化,业务逻辑可以放在均分内部
        arr.forEach((count, clr) => {
            count += 11;
            // 色砖数量
            while (count-- > 0) {
                let tile = this.tiles[subtotal++];
                tile.clr = clr;
            }
        });
        // 打散 tiles
        shuffle(this.tiles);

        // 存入 grid
        this.grid = this.tiles.map((tile, index) => {
            // 实时索引
            tile.index = index;
            // 原索引
            tile.originIndex = index;
            // 默认在舞台上
            tile.removed = false;
            // 欲消除状态
            tile.status = false;
            // 默认是消除换位
            tile.type = 1;
            return tile;
        });
    }
    // 消除砖块
    is() {
        let newGrid = [...this.grid];
        // 竖消,判断砖块欲消除状态
        for (let i = 0; i < this.col; i++) {
            let xBox = newGrid.splice(0, this.row);
            this.setxBox(xBox);
        }
        // 横消,判断砖块欲消除状态
        for (let i = 0; i < this.row; i++) {
            let xBox = [];
            for (let j = 0; j < this.row * this.col; j += this.row) {
                xBox.push(this.grid[i + j]);
            }
            this.setxBox(xBox);
        }

        // 通过欲消除状态,改变在舞台的呈现形式 status 赋值给removed
        this.grid.forEach(tile => {
            tile.removed = tile.status;
        });
        // 消除砖块后,砖块的index值改变
        this.changeIndex();
    }
    setxBox(arr) {
        // 把欲消除的内容status标记为true
        for (let i = 0; i < 5; i++) {
            let rBox = [];
            let xBox = [];
            let len = arr.length;
            arr.forEach((tile, index) => {
                if (tile.clr == i && index != len - 1) {
                    // 不是最后一位,同一种颜色push到欲消除数组
                    xBox.push(tile);
                } else if (
                    tile.clr == i &&
                    index == len - 1 &&
                    xBox.length >= 2
                ) {
                    // 最后一位,并且内部可消除满足3个 放到欲消除数组,同时合并到结果数组里面
                    xBox.push(tile);
                    rBox = [...rBox, ...xBox];
                } else if (xBox.length < 3) {
                    // 删除欲消除数组
                    xBox.length = 0;
                } else {
                    // 把消除数组放到结果数组里
                    rBox = [...rBox, ...xBox];
                    xBox.length = 0;
                }
            });
            if (rBox.length > 2) {
                rBox.forEach(tile => {
                    tile.status = true;
                });
            }
        }
    }

    // 改变index
    changeIndex() {
        // 竖直移动
        let newGrid = [];
        for (let i = 0; i < this.col; i++) {
            let xBox = this.grid.splice(0, this.row);
            newGrid = [...newGrid, ...this.setIBox(xBox)];
        }
        this.timer && clearTimeout(this.timer);
        // 等消失之后在重新计算
        this.timer = setTimeout(() => {
            this.grid = newGrid.map((tile, index) => {
                if (tile.removed == true) {
                    tile.clr = (Math.random() * 5) >> 0;
                    this.paused = true;
                }
                // 默认在舞台上
                tile.removed = false;

                // tile.originIndex = index;
                tile.status = false;

                tile.type = 1;
                // 实时索引
                tile.index = index;

                return tile;
            });
            if (this.paused == true && this.state) {
                setTimeout(() => {
                    this.is();
                    this.paused = false;
                }, 500);
            } else {
                this.move = true;
            }
        }, 300);
    }
    // 把每一列的消除项添坑,并重新导出
    setIBox(arr) {
        let len = arr.length;
        let newArr = [];
        for (let i = len - 1; i >= 0; i--) {
            if (arr[i].removed == true) {
                newArr.unshift(arr.splice(i, 1)[0]);
            }
        }
        arr = [...newArr, ...arr];
        return arr;
    }
    // 更改两个点坐标
    setTileDoubleIndex({ position, orientation }) {
        let obj = { 0: -1, 1: 1, 2: -9, 3: 9 };
        let one = position; // 目标位置
        let two = position + obj[orientation]; // 被交换位置
        let topBorder = parseInt(one / this.row) * this.row; // 上边界
        let bottomBorder = parseInt(one / this.row) * this.row + this.row; // 底边界
        // 判断替换不能出边界,不能超过总边界,如果是上下方向,不能超过当前上下边界
        if (
            two < 0 ||
            two > 53 ||
            ((orientation == 0 || orientation == 1) &&
                (two < topBorder || two >= bottomBorder))
        ) {
            return;
        }
        // 两个砖块交换index,
        let tileOneIndex = this.grid[one].index;
        let tileTwoIndex = this.grid[two].index;
        this.grid[one].type = 2;
        this.grid[one].index = tileTwoIndex;
        this.grid[two].type = 2;
        this.grid[two].index = tileOneIndex;
        let tile = this.grid[one];
        this.grid[one] = this.grid[two];
        this.grid[two] = tile;
        // 校验每一个是否有消除状态
        if (this.checkOne(one) || this.checkOne(two)) {
            setTimeout(() => {
                this.is();
                this.paused = false;
                this.move = false;
            }, 500);
        } else {
            // 不能消除,把替换的位置,在替换回来
            setTimeout(() => {
                let tileOneIndex = this.grid[one].index;
                let tileTwoIndex = this.grid[two].index;
                this.grid[one].type = 2;
                this.grid[one].index = tileTwoIndex;
                this.grid[two].type = 2;
                this.grid[two].index = tileOneIndex;
                let tile = this.grid[one];
                this.grid[one] = this.grid[two];
                this.grid[two] = tile;
                this.paused = true;
                this.move = true;
            }, 200);
        }
    }
    /**
     * 检测单个是否可以消除
     */
    checkOne(position) {
        let clr = this.grid[position].clr;
        let obj = { 0: -1, 1: 1, 2: -9, 3: 9 };
        let fanObj = { 0: 1, 1: 0, 2: 3, 3: 2 };
        let topBorder = parseInt(position / this.row) * this.row;
        let bottomBorder = parseInt(position / this.row) * this.row + this.row;
        let statue = false;
        let index = 1;
        // 方向判断是否可以消除
        function getOri(position, orientation, step) {
            // 满足3个跳出递归
            if (index >= 3) {
                return;
            }
            let two = position + obj[orientation] * step;
            if (
                two < 0 ||
                two > 53 ||
                ((orientation == 0 || orientation == 1) &&
                    (two < topBorder || two >= bottomBorder))
            ) {
                // 如果出边界不处理
            } else if (this.grid[two].clr == clr) {
                index++;
                getOri.call(this, this.grid[two].index, orientation, 1);
                getOri.call(this, this.grid[two].index, fanObj[orientation], 2);
            }
        }
        for (let i in obj) {
            index = 0;
            getOri.call(this, position, i, 1);
            if (index >= 3) {
                statue = true;
            }
        }
        // 返回当前,校验状态
        return statue;
    }
    /**
     * @ 检查是否死局
     * @ 非死局会返回一个索引值
     * @ 死局返回 false
     */
    check() {
        if (this.tileCount === 0) return false;
        return true;
    }
}

Control

包括:监听每一个砖块属性变化、注册游戏结束事件、初始化view和model

import Event from '../libs/Event';
import Timer from '../timer';
import { changeTimeStamp } from '../../../common/timeDown';

export default class Control {
    constructor(model, view) {
        this.model = model;
        this.view = view;

        // event事件
        this.event = new Event();

        // view 与control 共享一个event
        this.view.event = this.event;

        // timer
        let timer = new Timer();

        // 数据绑定: model.tiles -> view.tiles
        model.tiles.forEach(tile => {
            Reflect.defineProperty(tile, 'index', {
                set: value => {
                    if (value === tile._index) return false;
                    Reflect.set(tile, '_index', value);
                    // 与view同步数据
                    view.update(tile);
                },
                get: () => Reflect.get(tile, '_index')
            });
            Reflect.defineProperty(tile, 'clr', {
                set: value => {
                    if (value === tile._clr) return false;
                    Reflect.set(tile, '_clr', value);
                    // 与view同步数据
                    view.update(tile);
                },
                get: () => Reflect.get(tile, '_clr')
            });
            Reflect.defineProperty(tile, 'removed', {
                set: value => {
                    if (value === tile._removed) return false;
                    Reflect.set(tile, '_removed', value);
                    // 与view同步数据
                    view.update(tile);
                },
                get: () => Reflect.get(tile, '_removed') || false
            });
        });
        // 监听model数据运行格式
        Reflect.defineProperty(model, 'move', {
            set: value => {
                if (value === model._paused) return false;
                Reflect.set(model, '_move', value);
                // 与view同步数据
                if (value) {
                    this.resume();
                } else {
                    this.stop();
                }
            },
            get: () => Reflect.get(model, '_move') || false
        });
        // 监听点击事件
        this.event.on('view-tap', moveObj => {
            // 暂停状态下锁屏
            if (this.paused === true) return;
            // 消除 model 的砖块
            model.setTileDoubleIndex(moveObj);
        });
        // 开启消消乐功能
        this.event.on('view-start', () => {
            setTimeout(() => {
                this.model.is();
                timer.run(timeDown => {
                    let data = changeTimeStamp(timeDown);
                    let time = 0;
                    if (data) {
                        time = data.sec + '.' + data.ms.toString().substr(0, 2);
                    } else {
                        if (model.move === true) {
                            timer.state = timer.END;
                            time = '0.00';
                            model.state = false;
                            model.move = false;
                            // 派发游戏结束
                            this.event.dispatch('game-over', view.total);
                        }
                    }
                    this.event.dispatch('view-time', time);
                });
            }, 500);
        });
    }
    // 初关卡
    init() {
        // 默认五个颜色
        this.model.init();
        // 砖块动画
        this.view.init();
    }
    // 指定关数
    enter() {
        this.init();
    }

    // 恢复游戏
    resume() {
        // 恢复渲染
        this.view.resume();
        // 标记恢复
        this.paused = false;
    }
    // 暂停游戏
    stop() {
        // 恢复渲染
        this.view.stop();
        // 标记恢复
        this.paused = true;
    }
}

波动平均算法

主要是快速分配方法,每次动态获取当前数值的波峰和波谷
参考文献:https://aotu.io/notes/2018/01...

费雪耶兹算法

快速随机,如果用sort做随机,第一:时间复杂度高,第二:并不算真正的随机

/*
  @ Fisher–Yates(费雪耶兹算法)
*/

export default function shuffle(a) {
    for (let i = a.length; i; i--) {
        let j = Math.floor(Math.random() * i);
        [a[i - 1], a[j]] = [a[j], a[i - 1]];
    }
    return a;
}

王明
154 声望12 粉丝