mvc之消消樂

根據上一篇轉盤抽獎,在開發消消樂遊戲上又擴展了一下目錄結構web

變動以下:

新增mvc思想
新增事件派發機制
添加波動平均算法
添加費雪耶茲算法
添加時間控制器
取消精靈構建類
導演類dirctor變成mvc入口
遊戲運行移到控制器control裏面

須要技能

一、pixi.js和tweenMax.js。(這兩個主要用在視圖層,開發遊戲精靈,也能夠用原生canvas代替)
二、初步瞭解一下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

遊戲入口主要是初始化遊戲和懶加載添加所需插件canvas

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,遊戲初始化佈局入口,同時監聽遊戲結束業務邏輯。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

視圖層:app

經過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

數據層,主要作數據處理,包括磚塊數量、打散磚塊、改變位置、計算消除磚塊dom

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和modelide

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;
}
相關文章
相關標籤/搜索