Browse Source

基本交互逻辑完成

master
jiannibang 6 years ago
parent
commit
3b2cebcc92
  1. BIN
      h5/snake/bg.jpg
  2. BIN
      h5/snake/buttons.png
  3. 435
      h5/snake/core/control.js
  4. 397
      h5/snake/core/model.js
  5. 403
      h5/snake/core/view.js
  6. 0
      h5/snake/food.png
  7. 39426
      h5/snake/lib/pixi.js
  8. 36
      h5/snake/lib/utils/getContentBoxSize.js
  9. 1
      h5/snake/lib/utils/noHello.js
  10. BIN
      h5/snake/modal.png
  11. 36
      h5/snake/snake.css
  12. 71
      h5/snake/snake.js

BIN
h5/snake/bg.jpg

Binary file not shown.

Before

Width:  |  Height:  |  Size: 354 KiB

After

Width:  |  Height:  |  Size: 159 KiB

BIN
h5/snake/buttons.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 104 KiB

435
h5/snake/core/control.js

@ -4,226 +4,229 @@
*/
// 游戏通用ticker
import ticker from '../lib/utils/ticker';
import ticker from "../lib/utils/ticker";
// timer
import timer from '../lib/utils/timer';
import timer from "../lib/utils/timer";
// 随机打散数组
import randomList from '../lib/utils/randomList';
// 随机打散数组
import randomList from "../lib/utils/randomList";
// 事件
import Events from '../lib/utils/events';
import Events from "../lib/utils/events";
// control 类
export default class control {
// 构建函数
constructor(model, view) {
this.model = model;
this.view = view;
// 挂载一个 speed 属性
Reflect.defineProperty(this, "speed", {
get: function() {
return this.speedScalar || 1;
},
set: function(value) {
if(this.speedScalar !== value) {
this.speedScalar = value;
this.interval = 300 / this.speedScalar;
// 更新 timer
timer.set(this.intervalID, {delay: this.interval});
}
}
});
// tickHandle 绑定当前 this
this.tickHandle = this.tickHandle.bind(this);
// this.update 绑定 this
this.update = this.update.bind(this);
// 挂载事件对象
this.event = new Events();
// 四个方向
this.fourDirections = ["left", "up", "right", "down"];
}
// 初始化
init(config = {}) {
// 添加 ticker
ticker.addEventListener("tick", this.tickHandle);
// 默认暂停 ticker
this.pause();
let {
width = 640,
height = 640,
row = 50,
column = 50,
border = 0x999999,
color = 0x000000, // 蛇的节点颜色
food = color, // 食物颜色
min = 3, // 初始长度
speed = 1 // 速度标量
} = config;
// 存一份 config 到 this
this.config = config;
// 初始化 model
this.model.init({row, column, min});
// view.data
let data = {
zone: this.model.zone,
snake: this.model.snake,
food: this.model.food
};
// 初始化 view
this.view.init({width, height, row, column, border, color, food, data});
// interval 的间隔
this.interval = 300 / this.speedScalar ;
// 定时更新view
this.intervalID = timer.setInterval(this.update, this.interval);
// 速度标量
this.speed = speed;
// 蛇长度
this.length = this.model.snake.length;
// 初始化食物
this.food = this.model.food;
// 用户操作的方向列表
this.directions = [];
// 总计时
if(config.time > 0) {
let time = config.time / 1000;
timer.setTimeout(() => this.gameover("timeout"), config.time);
// 倒数
timer.setInterval(() => this.event.dispatch("countdown", --time), 1000);
}
}
// 销毁
destroy() {
// 移除 ticker
ticker.removeEventListener("tick", this.tickHandle);
// 清空 timer
timer.clean();
// 销毁 model
this.model.destroy();
// 销毁 view
this.view.destroy();
// GAMEOVER
this.GAMEOVER = false
}
// 转向
turn(direction) {
// 只保存第一次方向操作
if(this.fourDirections.indexOf[direction] === -1) return;
let directionA = direction, directionB = this.directions[0] || this.direction;
// 给操作列表加个容积 5
if(this.directions.length < 5 && directionA !== directionB && !this.isAdverse(directionA, directionB)) {
this.directions.unshift(directionA);
}
}
// 判断两个方向是否相反
isAdverse(directionA, directionB) {
let indexA = this.fourDirections.indexOf(directionA),
indexB = this.fourDirections.indexOf(directionB);
if(Math.abs(indexA - indexB) === 2) {
return true;
}
return false;
}
// 暂停
pause() {
if(this.GAMEOVER) return ;
ticker.pause();
}
// 恢复
resume() {
if(this.GAMEOVER) return ;
ticker.resume();
}
// start
start() {
if(this.GAMEOVER) return ;
// this.resume();
// 蛇的随机运动方向
let {leader, zone} = this.model;
// 控制方向的变量是 this.direction。this.nextDirection 表示下一个方向
this.directions.push(
randomList(
this.fourDirections
.filter(
(item) => leader[item] !== -1 && zone[leader[item]].fill === undefined
),
1
)
);
this.update();
}
// 重新开始
restart() {
this.destroy();
this.init(this.config);
this.start();
}
// gameover
gameover(type) {
if(this.GAMEOVER) return ;
this.event.dispatch("gameover", type);
this.pause();
this.GAMEOVER = true;
}
// update
update() {
// this.direction 表示蛇头节点的运动方向
this.direction = this.directions.pop() || this.direction;
this.model.move(this.direction);
if(this.model.bar !== undefined) {
// gameover
this.gameover(this.model.bar);
}
let data = {snake: this.model.snake, food: this.model.food};
if(this.model.dirty) {
// model 有变化
let hasEatEvent = false;
if(this.food !== this.model.food) {
this.food = this.model.food;
hasEatEvent = true;
this.event.dispatch("before-eat");
this.length = this.model.snake.length;
}
this.view.update(data);
hasEatEvent && this.event.dispatch("eat");
this.model.cleanDirty();
}
}
// tickHandle
tickHandle() {
timer.update(ticker.paused, ticker.elapsedMS * 1000);
if(!ticker.paused) {
this.view.updateTicker();
}
}
}
export default class control {
// 构建函数
constructor(model, view) {
this.model = model;
this.view = view;
// 挂载一个 speed 属性
Reflect.defineProperty(this, "speed", {
get: function () {
return this.speedScalar || 1;
},
set: function (value) {
if (this.speedScalar !== value) {
this.speedScalar = value;
this.interval = 300 / this.speedScalar;
// 更新 timer
timer.set(this.intervalID, { delay: this.interval });
}
},
});
// tickHandle 绑定当前 this
this.tickHandle = this.tickHandle.bind(this);
// this.update 绑定 this
this.update = this.update.bind(this);
// 挂载事件对象
this.event = new Events();
// 四个方向
this.fourDirections = ["left", "up", "right", "down"];
}
// 初始化
init(config = {}) {
// 添加 ticker
ticker.addEventListener("tick", this.tickHandle);
// 默认暂停 ticker
this.pause();
let {
width = 640,
height = 640,
row = 50,
column = 50,
border = 0x999999,
color = 0x000000, // 蛇的节点颜色
food = color, // 食物颜色
min = 3, // 初始长度
speed = 1, // 速度标量
} = config;
// 存一份 config 到 this
this.config = config;
// 初始化 model
this.model.init({ row, column, min });
// view.data
let data = {
zone: this.model.zone,
snake: this.model.snake,
food: this.model.food,
};
// 初始化 view
this.view.init({ width, height, row, column, border, color, food, data });
// interval 的间隔
this.interval = 300 / this.speedScalar;
// 定时更新view
this.intervalID = timer.setInterval(this.update, this.interval);
// 速度标量
this.speed = speed;
// 蛇长度
this.length = this.model.snake.length;
// 初始化食物
this.food = this.model.food;
// 用户操作的方向列表
this.directions = [];
// 总计时
if (config.time > 0) {
let time = config.time / 1000;
timer.setTimeout(() => this.gameover("timeout"), config.time);
// 倒数
timer.setInterval(() => this.event.dispatch("countdown", --time), 1000);
}
}
// 销毁
destroy() {
// 移除 ticker
ticker.removeEventListener("tick", this.tickHandle);
// 清空 timer
timer.clean();
// 销毁 model
this.model.destroy();
// 销毁 view
this.view.destroy();
// GAMEOVER
this.GAMEOVER = false;
}
// 转向
turn(direction) {
// 只保存第一次方向操作
if (this.fourDirections.indexOf[direction] === -1) return;
let directionA = direction,
directionB = this.directions[0] || this.direction;
// 给操作列表加个容积 5
if (
this.directions.length < 5 &&
directionA !== directionB &&
!this.isAdverse(directionA, directionB)
) {
this.directions.unshift(directionA);
}
}
// 判断两个方向是否相反
isAdverse(directionA, directionB) {
let indexA = this.fourDirections.indexOf(directionA),
indexB = this.fourDirections.indexOf(directionB);
if (Math.abs(indexA - indexB) === 2) {
return true;
}
return false;
}
// 暂停
pause() {
if (this.GAMEOVER) return;
ticker.pause();
}
// 恢复
resume() {
if (this.GAMEOVER) return;
ticker.resume();
}
// start
start() {
if (this.GAMEOVER) return;
// 蛇的随机运动方向
let { leader, zone } = this.model;
// 控制方向的变量是 this.direction。this.nextDirection 表示下一个方向
this.directions.push(
randomList(
this.fourDirections.filter(
(item) => leader[item] !== -1 && zone[leader[item]].fill === undefined
),
1
)
);
this.update();
}
// 重新开始
restart() {
this.destroy();
this.init(this.config);
this.start();
this.event.dispatch("restart");
}
// gameover
gameover(type) {
if (this.GAMEOVER) return;
this.event.dispatch("gameover", type);
this.pause();
this.GAMEOVER = true;
}
// update
update() {
// this.direction 表示蛇头节点的运动方向
this.direction = this.directions.pop() || this.direction;
this.model.move(this.direction);
if (this.model.bar !== undefined) {
// gameover
this.gameover(this.model.bar);
}
let data = { snake: this.model.snake, food: this.model.food };
if (this.model.dirty) {
// model 有变化
let hasEatEvent = false;
if (this.food !== this.model.food) {
this.food = this.model.food;
hasEatEvent = true;
this.event.dispatch("before-eat");
this.length = this.model.snake.length;
}
this.view.update(data);
hasEatEvent && this.event.dispatch("eat");
this.model.cleanDirty();
}
}
// tickHandle
tickHandle() {
timer.update(ticker.paused, ticker.elapsedMS * 1000);
if (!ticker.paused) {
this.view.updateTicker();
}
}
}

397
h5/snake/core/model.js

@ -3,206 +3,207 @@
@ 贪吃蛇的 model
*/
// 随机打散数组
import randomList from '../lib/utils/randomList';
// 随机打散数组
import randomList from "../lib/utils/randomList";
// 链表类
import Chain from '../lib/utils/Chain';
import Chain from "../lib/utils/Chain";
// model 类
export default class model {
// 构造函数
constructor() {
// 活动空间
this.zone = [];
// 蛇链表
this.snake = new Chain();
// 封装 snake 的 unshift, push, shift, pop 方法
let {unshift, push, shift, pop} = this.snake;
this.snake.unshift = (index) => {
unshift.call(this.snake, index);
// 更新 zone
this.updateZone(index, "snake", "unshift");
}
this.snake.shift = () => {
let index = shift.call(this.snake).data;
// 更新 zone
this.updateZone(index, undefined, "shift");
}
this.snake.push = (index) => {
push.call(this.snake, index);
// 更新 zone
this.updateZone(index, "snake", "push");
}
this.snake.pop = () => {
let index = pop.call(this.snake).data;
// 更新 zone
this.updateZone(index, undefined, "pop");
}
// 投食后自动更新 zone
Reflect.defineProperty(this, "food", {
get: () => {
return Reflect.get(this, "_food")
},
set: (value) => {
// 将值记录到 _food
Reflect.set(this, "_food", value);
// 更新 zone
value !== undefined && this.updateZone(value, "food");
}
});
}
// 初始化
init(config) {
// 指定 zone 长度
this.zone.length = config.row * config.column;
// 填充 zone 的初始信息
for(let i = 0, len = this.zone.length; i < len; ++i) {
let [col, row] = [i % config.column, (i / config.row) >> 0]
this.zone[i] = {
col: col,
row: row,
left: col > 0 ? i - 1 : -1,
right: col < config.column - 1 ? i + 1 : -1,
up: row > 0 ? i - config.column : -1,
down: row < config.row - 1 ? i + config.column : -1
}
}
// 初始蛇的长度
while(this.snake.length < config.min) {
let index = this.snake.length ? this.neighbour() : (Math.random() * this.zone.length)>>0;
this.snake.unshift(index);
}
// 投食
this.feed();
}
// 销毁
destroy() {
// 清空 zone 内容
this.zone = [];
// 清空链表数组
this.snake.clean();
// 清除食物
this.food = undefined;
// 清空 bar
delete this.bar;
}
// 邻居元素
neighbour() {
return randomList(
[
this.leader.left,
this.leader.right,
this.leader.up,
this.leader.down
],
1,
(index) => index !== -1 && this.zone[index].fill === undefined
);
}
// zone 区域更新状态
updateZone(index, fill, type) {
// console.log(index, fill, type);
// fill == undefine 表示 free
this.zone[index].fill = fill;
// leader 更新
this.updateLeader();
}
// 更新蛇头在 zone 的坐标
updateLeader() {
if(this.snake.length !== 0) {
this.leader = this.zone[this.snake.first().data];
}
this.dirty = true;
}
// 清理dirty
cleanDirty() {
this.dirty = false;
}
// 蛇运动
move(direction) {
let index = this.leader[direction], skipTail = false;
if(-1 === index) {
// 撞墙
this.collision("bounds");
return ;
}
if(this.snake.last().data === index) {
// 即将撞上的尾巴
skipTail = true;
}
let next = this.zone[index];
switch(next.fill) {
// 吃食
case "food": this.eat(); break;
// 撞到自己
case "snake": {
// 判断是否咬尾
if(!skipTail) {
this.collision("self"); break;
}
}
// 默认前进
default: this.snake.pop() & this.snake.unshift(index);
}
}
// 吃食
eat() {
// 食物变成了头
this.snake.unshift(this.food);
// 重新投食
this.feed();
}
// 撞到东西
collision(bar) {
// bar 不为空就认为游戏结束
this.bar = bar;
}
// 赌博
bet() {
let rnd = Math.random() * this.zone.length >> 0;
return this.zone[rnd].fill === undefined ? rnd : -1;
}
// 随机喂食
feed() {
// 赌一次
let rnd = this.bet();
if(rnd !== -1) {
this.food = rnd;
return;
}
let index = 0,
count = 0,
len = this.zone.length - this.snake.length;
rnd = (Math.random() * count>>0) + 1;
// 无法投食
if(0 === len) {
this.food = undefined;
return ;
}
while(rnd !== count) {
this.zone[index++].fill === undefined && ++count;
}
this.food = index - 1;
}
}
// 构造函数
constructor() {
// 活动空间
this.zone = [];
// 蛇链表
this.snake = new Chain();
// 封装 snake 的 unshift, push, shift, pop 方法
let { unshift, push, shift, pop } = this.snake;
this.snake.unshift = (index) => {
unshift.call(this.snake, index);
// 更新 zone
this.updateZone(index, "snake", "unshift");
};
this.snake.shift = () => {
let index = shift.call(this.snake).data;
// 更新 zone
this.updateZone(index, undefined, "shift");
};
this.snake.push = (index) => {
push.call(this.snake, index);
// 更新 zone
this.updateZone(index, "snake", "push");
};
this.snake.pop = () => {
let index = pop.call(this.snake).data;
// 更新 zone
this.updateZone(index, undefined, "pop");
};
// 投食后自动更新 zone
Reflect.defineProperty(this, "food", {
get: () => {
return Reflect.get(this, "_food");
},
set: (value) => {
// 将值记录到 _food
Reflect.set(this, "_food", value);
// 更新 zone
value !== undefined && this.updateZone(value, "food");
},
});
}
// 初始化
init(config) {
// 指定 zone 长度
this.zone.length = config.row * config.column;
// 填充 zone 的初始信息
for (let i = 0, len = this.zone.length; i < len; ++i) {
let [col, row] = [i % config.column, Math.floor(i / config.column)];
this.zone[i] = {
col: col,
row: row,
left: col > 0 ? i - 1 : -1,
right: col < config.column - 1 ? i + 1 : -1,
up: row > 0 ? i - config.column : -1,
down: row < config.row - 1 ? i + config.column : -1,
};
}
// 初始蛇的长度
while (this.snake.length < config.min) {
let index = this.snake.length
? this.neighbour()
: (Math.random() * this.zone.length) >> 0;
this.snake.unshift(index);
}
// 投食
this.feed();
}
// 销毁
destroy() {
// 清空 zone 内容
this.zone = [];
// 清空链表数组
this.snake.clean();
// 清除食物
this.food = undefined;
// 清空 bar
delete this.bar;
}
// 邻居元素
neighbour() {
return randomList(
[this.leader.left, this.leader.right, this.leader.up, this.leader.down],
1,
(index) => index !== -1 && this.zone[index].fill === undefined
);
}
// zone 区域更新状态
updateZone(index, fill, type) {
// console.log(index, fill, type);
// fill == undefine 表示 free
this.zone[index].fill = fill;
// leader 更新
this.updateLeader();
}
// 更新蛇头在 zone 的坐标
updateLeader() {
if (this.snake.length !== 0) {
this.leader = this.zone[this.snake.first().data];
}
this.dirty = true;
}
// 清理dirty
cleanDirty() {
this.dirty = false;
}
// 蛇运动
move(direction) {
let index = this.leader[direction],
skipTail = false;
if (-1 === index) {
// 撞墙
this.collision("bounds");
return;
}
if (this.snake.last().data === index) {
// 即将撞上的尾巴
skipTail = true;
}
let next = this.zone[index];
switch (next.fill) {
// 吃食
case "food":
this.eat();
break;
// 撞到自己
case "snake": {
// 判断是否咬尾
if (!skipTail) {
this.collision("self");
break;
}
}
// 默认前进
default:
this.snake.pop() & this.snake.unshift(index);
}
}
// 吃食
eat() {
// 食物变成了头
this.snake.unshift(this.food);
// 重新投食
this.feed();
}
// 撞到东西
collision(bar) {
// bar 不为空就认为游戏结束
this.bar = bar;
}
// 赌博
bet() {
let rnd = (Math.random() * this.zone.length) >> 0;
return this.zone[rnd].fill === undefined ? rnd : -1;
}
// 随机喂食
feed() {
// 赌一次
let rnd = this.bet();
if (rnd !== -1) {
this.food = rnd;
return;
}
let index = 0,
count = 0,
len = this.zone.length - this.snake.length;
rnd = ((Math.random() * count) >> 0) + 1;
// 无法投食
if (0 === len) {
this.food = undefined;
return;
}
while (rnd !== count) {
this.zone[index++].fill === undefined && ++count;
}
this.food = index - 1;
}
}

403
h5/snake/core/view.js

@ -2,128 +2,67 @@
author: leeenx
@ 贪吃蛇的 view
*/
// 获取 Graphics 的 content-box
import "../lib/utils/getContentBoxSize";
// 不显示 PIXI 信息
import "../lib/utils/noHello";
// 链表类
import Chain from "../lib/utils/Chain";
import food from "../food.png";
const startColor = 0xfec321;
const endColor = 0xe29b01;
const rotate = (cx, cy, point, rad) => {
const { x, y } = point;
let cos = Math.cos(rad),
sin = Math.sin(rad),
nx = cos * (x - cx) + sin * (y - cy) + cx,
ny = cos * (y - cy) - sin * (x - cx) + cy;
point.x = nx;
point.y = ny;
};
const getDir = (x1, y1, x2, y2) => {
return x2 > x1 ? "right" : x2 < x1 ? "left" : y2 > y1 ? "bottom" : "top";
};
// view 类
export default class view {
// 构造函数
constructor(dom, width, height) {
// 创建一个 app
let app = new PIXI.Application(width, height, {
transparent: true,
});
// canvas 添加到 page
dom.appendChild(app.view);
// 销毁pixijs的ticker
app.ticker.destroy();
const canvas = document.createElement("canvas");
canvas.style.width = width + "px";
canvas.style.height = height + "px";
canvas.width = width * window.devicePixelRatio;
canvas.height = height * window.devicePixelRatio;
const ctx = canvas.getContext("2d");
ctx.scale(window.devicePixelRatio, window.devicePixelRatio);
dom.appendChild(canvas);
// 创建 view 的蛇
this.snake = new Chain();
// shift & unshift & pop & push & insertAfter 自动关联 addNode & removeNode
let { shift, unshift, pop, push, insertAfter } = this.snake;
// 封闭 shift
this.snake.shift = () => {
// 回收尾节点
this.collect(shift.call(this.snake).node);
};
// 封装 unshift
this.snake.unshift = (data) => {
unshift.call(this.snake, data);
let node = (this.snake.first().node = this.calloc());
node.setPostion(...this.getPosition(data));
};
// 封闭 pop
this.snake.pop = () => {
// 回收尾节点
this.collect(pop.call(this.snake).node);
};
// 封装 push
this.snake.push = (data) => {
push.call(this.snake, data);
let node = (this.snake.last().node = this.calloc());
node.setPostion(...this.getPosition(data));
};
// 封装 insertAfter
this.snake.insertAfter = (index, data) => {
insertAfter.call(this.snake, index, data);
let node = (this.snake.at(index + 1).node = this.calloc());
node.setPostion(...this.getPosition(data));
};
// 回收节点
this.collection = [];
// 保证 updateTicker 指针永远指向 view
this.updateTicker = this.updateTicker.bind(this);
// 挂载到this
this.app = app;
this.stage = app.stage;
// 扩展 PIXI.Graphics
PIXI.Graphics.prototype.setPostion = function (x, y = x) {
this.x = x + this.pivot.x;
this.y = y + this.pivot.y;
};
this.canvas = canvas;
this.ctx = ctx;
this.foodImg = new Image(120, 120);
this.foodImg.src = food;
}
// 初始化
init(config = {}) {
// pixijs
let {
app,
app: { stage, renderer },
} = this;
// 蛇的尺寸挂载到 config
config.size = {
width: config.width / config.column,
height: config.height / config.row,
};
console.log(config.size);
// 初化data
this.data = config.data;
// 全局 config 挂载
this.config = config;
// 游戏活动区
stage.addChild((this.zone = new PIXI.Container()));
// 绘制边界
// this.drawBounds();
// 食物
this.food = this.calloc();
this.food = {};
this.food.visible = false;
// 食物动画 - blink
let [from, to] = [
{
alpha: 1,
},
{
alpha: 0,
},
];
TweenMax.fromTo(this.food, 0.2, from, to).repeat(-1).yoyo(true);
// 通过 model.zone 创建一张快速定位表
this.createQuickMap(this.data.zone);
@ -134,43 +73,8 @@ export default class view {
}
destroy() {
let {
app,
app: { stage },
} = this;
// 销毁所有子节点
for (let child of stage.children) {
child.destroy();
}
stage.removeChildren();
this.collection = [];
this.snake.clean();
}
// 绘制四条边界
drawBounds() {
let {
app,
app: { stage },
} = this,
{ border, width, height } = this.config,
thickness = 8;
let bounds = new PIXI.Graphics()
.beginFill(0xffffff, 1)
.lineStyle(thickness, border, 1)
.drawRect(0, 0, width + thickness, height + thickness);
bounds.x = bounds.y = (app.view.width - bounds.cwidth) / 2;
stage.addChild(bounds);
// bounds 的 index
stage.setChildIndex(bounds, 0);
// 活动空间定位
[this.zone.x, this.zone.y] = [
bounds.x + thickness / 2,
bounds.y + thickness / 2,
];
}
// 快速寻位表
createQuickMap(map) {
@ -187,36 +91,13 @@ export default class view {
return this.quickMap[index];
}
// 创建节点
calloc() {
let node;
if (this.collection.length === 0) {
node = new PIXI.Graphics();
let { width, height } = this.config.size;
node.beginFill(this.config.color, 1).drawRect(0, 0, width, height);
node.pivot.set(width / 2, height / 2);
node.setPostion(0);
} else {
node = this.collection.pop();
}
// 默认显示在容器里
this.zone.addChild(node);
return node;
}
// 回收节点
collect(node) {
node && this.collection.push(node) & this.zone.removeChild(node);
}
// 随机生成食物
feed(index) {
this.food.visible = 1;
this.food.graphicsData[0].fillColor = this.config.food;
this.food.dirty++;
this.food.clearDirty++;
this.food.setPostion(...this.getPosition(index));
const [left, top] = this.getPosition(index);
Object.assign(this.food, { left, top });
}
// ticker update
@ -308,8 +189,226 @@ export default class view {
}
}
renderFood() {
let { width, height } = this.config.size;
if (this.food && this.food.visible) {
this.ctx.drawImage(
this.foodImg,
this.food.left,
this.food.top,
width,
height
);
}
}
drawHeadTail(x, y, dir, index, length) {
const { ctx } = this;
let { width, height } = this.config.size;
const sWidth = (width / 120) * 90;
const r = sWidth / 2;
const gap = (width - sWidth) / 2;
const a = { x, y: y + height };
const b = { x: x + gap, y: a.y };
const c = { x: x + width / 2, y: a.y };
const d = { x: x + gap + sWidth, y: a.y };
const e = { x: x + width, y: a.y };
const f = { x: b.x, y: y + r };
const g = { x: b.x, y };
const h = { x: c.x, y };
const i = { x: d.x, y };
const j = { x: d.x, y: f.y };
const k = { x: x + width / 2, y: y - index * height };
const l = { x: k.x, y: y + (length - index) * height };
let rad =
dir === "bottom"
? 0
: dir === "top"
? Math.PI
: dir === "left"
? -Math.PI / 2
: Math.PI / 2;
if (index) rad += Math.PI;
[a, b, c, d, e, f, g, h, i, j, k, l].forEach((point) =>
rotate(x + width / 2, y + height / 2, point, rad)
);
// k
// |
// |
// |
// g---h---i
// | / \ |
// f j
// | |
// a-b---c---d-e
// |
// |
// |
// l
const gradient = ctx.createLinearGradient(k.x, k.y, l.x, l.y);
gradient.addColorStop(0, "#" + startColor.toString(16));
gradient.addColorStop(1, "#" + endColor.toString(16));
ctx.fillStyle = gradient;
ctx.strokeStyle = gradient;
ctx.beginPath();
ctx.moveTo(b.x, b.y);
ctx.lineTo(f.x, f.y);
ctx.arcTo(g.x, g.y, h.x, h.y, r);
ctx.arcTo(i.x, i.y, j.x, j.y, r);
ctx.lineTo(d.x, d.y);
ctx.closePath();
ctx.fill();
ctx.stroke();
}
drawRec(x, y, dir, i, length) {
const { ctx } = this;
let { width, height } = this.config.size;
const sWidth = (width / 120) * 90;
const gap = (width - sWidth) / 2;
const a = { x: x + gap, y: y + height };
const b = { x: a.x, y };
const c = { x: x + gap + sWidth, y };
const d = { x: c.x, y: a.y };
const e = { x: x + width / 2, y: y - i * height };
const f = { x: e.x, y: y + (length - i) * height };
let rad =
dir === "bottom"
? 0
: dir === "top"
? Math.PI
: dir === "left"
? -Math.PI / 2
: Math.PI / 2;
[a, b, c, d, e, f].forEach((point) =>
rotate(x + width / 2, y + height / 2, point, rad)
);
// e
// |
// b---c
// | |
// a---d
// |
// f
const gradient = ctx.createLinearGradient(e.x, e.y, f.x, f.y);
gradient.addColorStop(0, "#" + startColor.toString(16));
gradient.addColorStop(1, "#" + endColor.toString(16));
ctx.fillStyle = gradient;
ctx.strokeStyle = gradient;
ctx.beginPath();
ctx.moveTo(a.x, a.y);
ctx.lineTo(b.x, b.y);
ctx.lineTo(c.x, c.y);
ctx.lineTo(d.x, d.y);
ctx.closePath();
ctx.fill();
ctx.stroke();
}
drawCorner(x, y, dir, i, length) {
const { ctx } = this;
let { width, height } = this.config.size;
const sWidth = (width / 120) * 90;
const gap = (width - sWidth) / 2;
const sqrt2 = width / 2 / Math.sqrt(2);
const a = { x: x + gap, y: y + height };
const b = { x: x + gap + sWidth, y: a.y };
const c = { x: b.x, y: y + gap + sWidth };
const d = { x: x + width, y: c.y };
const e = { x: d.x, y: y + gap };
const f = { x: a.x, y: e.y };
let g = {
x: x + width / 2 + ((length - i) * 2 + 1) * sqrt2,
y: y + height / 2 + ((length - i) * 2 + 1) * sqrt2,
};
let h = {
x: x + width / 2 - (i * 2 + 1) * sqrt2,
y: y + height / 2 - (i * 2 + 1) * sqrt2,
};
// if (["rightbottom", "lefttop", "topright", "bottomleft"].includes(dir)) {
// let tmp = h;
// h = g;
// g = tmp;
// }
let rad =
dir === "bottomright" || dir === "rightbottom"
? 0
: dir === "topleft" || dir === "lefttop"
? Math.PI
: dir === "righttop" || dir === "topright"
? Math.PI / 2
: -Math.PI / 2;
[a, b, c, d, e, f, g, h].forEach((point) =>
rotate(x + width / 2, y + height / 2, point, rad)
);
//
// g
// /
// -------------
// | |
// ----f-------e
// | | |
// | | c---d
// | | | |
// ----a---b----
// /
// h
//
const gradient = ctx.createLinearGradient(h.x, h.y, g.x, g.y);
gradient.addColorStop(0, "#" + startColor.toString(16));
gradient.addColorStop(1, "#" + endColor.toString(16));
ctx.fillStyle = gradient;
ctx.strokeStyle = gradient;
ctx.beginPath();
ctx.moveTo(a.x, a.y);
ctx.lineTo(b.x, b.y);
ctx.arcTo(c.x, c.y, d.x, d.y, gap);
ctx.lineTo(e.x, e.y);
ctx.arcTo(f.x, f.y, a.x, a.y, gap + sWidth);
ctx.closePath();
ctx.fill();
ctx.stroke();
}
renderSnake() {
const { length } = this.snake;
const list = [];
for (let body of this.snake) {
list.push(body);
}
list.forEach(({ data }, i) => {
const pre = list[i - 1];
const nxt = list[i + 1];
const [x, y] = this.getPosition(data);
if (i === 0) {
// head
const [xNxt, yNxt] = this.getPosition(nxt.data);
const dir = getDir(x, y, xNxt, yNxt);
this.drawHeadTail(x, y, dir, i, list.length);
} else if (i === length - 1) {
const [xPre, yPre] = this.getPosition(pre.data);
const dir = getDir(xPre, yPre, x, y);
this.drawHeadTail(x, y, dir, i, list.length);
} else {
const [xNxt, yNxt] = this.getPosition(nxt.data);
const dir1 = getDir(x, y, xNxt, yNxt);
const [xPre, yPre] = this.getPosition(pre.data);
const dir2 = getDir(xPre, yPre, x, y);
if (dir1 === dir2) {
this.drawRec(x, y, dir1, i, length);
} else {
const dir3 = getDir(x, y, xPre, yPre);
this.drawCorner(x, y, dir1 + dir3, i, length);
}
}
});
}
// 渲染
render() {
this.app.renderer.render(this.app.stage);
this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
this.renderFood();
this.renderSnake();
}
}

0
h5/snake/苹果.png → h5/snake/food.png

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 16 KiB

39426
h5/snake/lib/pixi.js

File diff suppressed because it is too large

36
h5/snake/lib/utils/getContentBoxSize.js

@ -1,36 +0,0 @@
/*
author: leeenx
date: 2017.09.08
@ 获取不带描边的boudary
*/
import * as PIXI from "../pixi";
{
let dirty = Symbol("dirty");
let getContentBox = function () {
if (this[dirty] == this.dirty) return;
this[dirty] = this.dirty; // 表示已经更新
let cp = this.clone();
let graphicsData = cp.graphicsData;
for (let graphics of graphicsData) {
graphics.lineWidth = 0;
}
this._cwidth = cp.width;
this._cheight = cp.height;
};
Object.defineProperties(PIXI.Graphics.prototype, {
_cwidth: { writable: true, value: 0 },
_cheight: { writable: true, value: 0 },
cwidth: {
get: function () {
getContentBox.call(this);
return this._cwidth;
},
},
cheight: {
get: function () {
getContentBox.call(this);
return this._cheight;
},
},
});
}

1
h5/snake/lib/utils/noHello.js

@ -1 +0,0 @@
PIXI.utils.skipHello();

BIN
h5/snake/modal.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 920 KiB

36
h5/snake/snake.css

@ -5,3 +5,39 @@
background-image: url(./bg.jpg);
background-size: cover;
}
.snake > .buttons {
position: absolute;
width: 22.22222222222%;
height: 22.22222222222%;
right: 0.4%;
bottom: 0.97%;
opacity: 0.6;
background-image: url(./buttons.png);
background-size: cover;
}
.snake > .modal {
display: none;
position: absolute;
width: 100%;
height: 100%;
top: 0;
left: 0;
background: rgba(0, 0, 0, 0.6);
z-index: 1;
}
.snake > .modal.show {
display: block;
}
.snake > .modal::after {
content: "";
position: absolute;
width: 65.83333333333333%;
height: 41.94444444444444%;
top: 0;
left: 0;
bottom: 0;
right: 0;
margin: auto;
background-image: url(./modal.png);
background-size: cover;
}

71
h5/snake/snake.js

@ -17,10 +17,34 @@ export default class SnakeClass extends SnakeControl {
const bg = document.createElement("div");
bg.className = "snake";
bg.style.paddingTop = `${(240 / 2160) * width}px`;
container.appendChild(bg);
const buttons = document.createElement("div");
buttons.className = "buttons";
bg.appendChild(buttons);
container.appendChild(bg);
super(new SnakeModel(), new SnakeView(bg, width, height));
Object.assign(this, {
showModal: true,
container: bg,
buttons,
containerWidth: width,
});
const modal = document.createElement("div");
modal.className = "modal show";
modal.addEventListener("click", (e) => {
e.stopPropagation();
e.preventDefault();
if (this.showModal) {
this.showModal = false;
modal.className = "modal";
this.resume();
}
});
bg.appendChild(modal);
this.init({
time: 3000000, // 总时间
width,
@ -31,20 +55,28 @@ export default class SnakeClass extends SnakeControl {
border: 0x414042,
color: 0x414042, // 蛇的节点颜色
food: 0x990000, // 食物的颜色
min: 4, // 初始长度
speed: 1.5, // 速度标量
min: 2, // 初始长度
speed: 0.5, // 速度标量
});
this.event.on("gameover", (res) => {
onLose(res);
});
this.event.on("restart", () => {
this.showModal = true;
modal.className = "modal show";
});
this.event.on("gameover", onLose);
// snakeGame.event.on("eat", (food) => {
// console.log("吃到食物,当前长度: " + snakeGame.length);
// });
this.start();
this.addControl();
}
dispose() {}
addControl() {
let controller = document.querySelector(".snake-direction"),
curDirection,
{ top, left, width, height } = controller.getBoundingClientRect(),
let controller = this.buttons;
let curDirection;
const { containerWidth } = this;
let { top, left, width, height } = controller.getBoundingClientRect(),
x = left + width / 2,
y = top + height / 2,
deg45 = Math.PI / 4,
@ -57,27 +89,24 @@ export default class SnakeClass extends SnakeControl {
controller.addEventListener(
"touchstart",
({ targetTouches: [{ pageX, pageY }] }) => {
checkDirection(pageX - x, pageY - y);
({ targetTouches: [{ clientX, clientY }] }) => {
checkDirection(clientX - x, clientY - y);
}
);
controller.addEventListener(
"touchmove",
({ targetTouches: [{ pageX, pageY }] }) => {
checkDirection(pageX - x, pageY - y);
({ targetTouches: [{ clientX, clientY }] }) => {
checkDirection(clientX - x, clientY - y);
}
);
controller.addEventListener(
"touchend",
({ changedTouches: [{ pageX, pageY }] }) => {
curDirection = undefined;
controller.className = "snake-direction";
}
);
controller.addEventListener("touchend", () => {
curDirection = undefined;
controller.className = "buttons snake-direction";
});
let checkDirection = function (x, y) {
let checkDirection = (x, y) => {
let radian = Math.asin(y / Math.sqrt(Math.pow(x, 2) + Math.pow(y, 2)));
// 1~2象限
if ((x > 0 && y < 0) || (x > 0 && y > 0)) {
@ -97,9 +126,9 @@ export default class SnakeClass extends SnakeControl {
direction = "left";
}
direction === curDirection ||
snakeGame.turn(
this.turn(
(curDirection = direction),
(controller.className = "snake-direction " + direction)
(controller.className = "buttons snake-direction " + direction)
);
};
}

Loading…
Cancel
Save