根据上一篇转盘抽奖,在开发消消乐游戏上又扩展了一下目录结构
变更如下:
新增mvc思想
新增事件派发机制
添加波动平均算法
添加费雪耶兹算法
添加时间控制器
取消精灵构建类
导演类dirctor变成mvc入口
游戏运行移到控制器control里面
需要技能
1、pixi.js和tweenMax.js。(这两个主要用在视图层,开发游戏精灵,也可以用原生canvas代替)
2、初步了解一下mvc的模式
目录结构
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;
}
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。