|
|
@ -5,22 +5,15 @@ |
|
|
// 链表类
|
|
|
// 链表类
|
|
|
import Chain from "../lib/utils/Chain"; |
|
|
import Chain from "../lib/utils/Chain"; |
|
|
import food from "../food.png"; |
|
|
import food from "../food.png"; |
|
|
|
|
|
import TWEEN from "@tweenjs/tween.js"; |
|
|
|
|
|
import eye from "../eye.png"; |
|
|
|
|
|
import mouth from "../mouth.png"; |
|
|
|
|
|
import tear from "../tear.png"; |
|
|
|
|
|
import stars from "../stars.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 startColor = "#fec321"; |
|
|
|
|
|
const endColor = "#e29b01"; |
|
|
|
|
|
|
|
|
const getDir = (x1, y1, x2, y2) => { |
|
|
|
|
|
return x2 > x1 ? "right" : x2 < x1 ? "left" : y2 > y1 ? "bottom" : "top"; |
|
|
|
|
|
}; |
|
|
|
|
|
// view 类
|
|
|
// view 类
|
|
|
export default class view { |
|
|
export default class view { |
|
|
// 构造函数
|
|
|
// 构造函数
|
|
|
@ -44,6 +37,14 @@ export default class view { |
|
|
this.ctx = ctx; |
|
|
this.ctx = ctx; |
|
|
this.foodImg = new Image(120, 120); |
|
|
this.foodImg = new Image(120, 120); |
|
|
this.foodImg.src = food; |
|
|
this.foodImg.src = food; |
|
|
|
|
|
this.eyeImg = new Image(143, 65); |
|
|
|
|
|
this.eyeImg.src = eye; |
|
|
|
|
|
this.mouthImg = new Image(131, 67); |
|
|
|
|
|
this.mouthImg.src = mouth; |
|
|
|
|
|
this.tearImg = new Image(129, 154); |
|
|
|
|
|
this.tearImg.src = tear; |
|
|
|
|
|
this.starsImg = new Image(224, 162); |
|
|
|
|
|
this.starsImg.src = stars; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
// 初始化
|
|
|
// 初始化
|
|
|
@ -62,16 +63,19 @@ export default class view { |
|
|
// 食物
|
|
|
// 食物
|
|
|
this.food = {}; |
|
|
this.food = {}; |
|
|
this.food.visible = false; |
|
|
this.food.visible = false; |
|
|
|
|
|
|
|
|
|
|
|
this.lastFood = null; |
|
|
// 通过 model.zone 创建一张快速定位表
|
|
|
// 通过 model.zone 创建一张快速定位表
|
|
|
this.createQuickMap(this.data.zone); |
|
|
this.createQuickMap(this.data.zone); |
|
|
|
|
|
|
|
|
|
|
|
this.tail = null; |
|
|
// 同步 model 的初始数据
|
|
|
// 同步 model 的初始数据
|
|
|
for (let { data } of this.data.snake) { |
|
|
for (let { data } of this.data.snake) { |
|
|
this.snake.push(data); |
|
|
this.snake.push(data); |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
setInterval(interval, timer) { |
|
|
|
|
|
this.interval = interval; |
|
|
|
|
|
this.timer = timer; |
|
|
|
|
|
} |
|
|
destroy() { |
|
|
destroy() { |
|
|
this.snake.clean(); |
|
|
this.snake.clean(); |
|
|
} |
|
|
} |
|
|
@ -97,7 +101,7 @@ export default class view { |
|
|
this.food.dirty++; |
|
|
this.food.dirty++; |
|
|
this.food.clearDirty++; |
|
|
this.food.clearDirty++; |
|
|
const [left, top] = this.getPosition(index); |
|
|
const [left, top] = this.getPosition(index); |
|
|
Object.assign(this.food, { left, top }); |
|
|
|
|
|
|
|
|
Object.assign(this.food, { index, left, top }); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
// ticker update
|
|
|
// ticker update
|
|
|
@ -108,7 +112,11 @@ export default class view { |
|
|
// 状态更新
|
|
|
// 状态更新
|
|
|
update(data) { |
|
|
update(data) { |
|
|
// 食物更新
|
|
|
// 食物更新
|
|
|
this.food !== data.food && this.feed(data.food); |
|
|
|
|
|
|
|
|
if (this.food.index !== data.food) { |
|
|
|
|
|
this.lastFood = this.food.index; |
|
|
|
|
|
this.feed(data.food); |
|
|
|
|
|
} |
|
|
|
|
|
this.tail = this.snake.last(); |
|
|
this.updateDelta(data.snake); |
|
|
this.updateDelta(data.snake); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
@ -168,7 +176,7 @@ export default class view { |
|
|
} |
|
|
} |
|
|
// 不匹配
|
|
|
// 不匹配
|
|
|
else { |
|
|
else { |
|
|
snakeB.pop(); |
|
|
|
|
|
|
|
|
this.tail = snakeB.pop(); |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
// 尾指针未匹配上,走 catch 通道
|
|
|
// 尾指针未匹配上,走 catch 通道
|
|
|
@ -189,226 +197,239 @@ export default class view { |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
renderFood() { |
|
|
|
|
|
|
|
|
renderFood(t) { |
|
|
let { width, height } = this.config.size; |
|
|
let { width, height } = this.config.size; |
|
|
|
|
|
const ratio = Math.abs(t - 0.5) * 0.5 + 1; |
|
|
if (this.food && this.food.visible) { |
|
|
if (this.food && this.food.visible) { |
|
|
this.ctx.drawImage( |
|
|
this.ctx.drawImage( |
|
|
this.foodImg, |
|
|
this.foodImg, |
|
|
this.food.left, |
|
|
|
|
|
this.food.top, |
|
|
|
|
|
width, |
|
|
|
|
|
height |
|
|
|
|
|
|
|
|
this.food.left - (width * (ratio - 1)) / 2, |
|
|
|
|
|
this.food.top - (height * (ratio - 1)) / 2, |
|
|
|
|
|
width * ratio, |
|
|
|
|
|
height * ratio |
|
|
); |
|
|
); |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
drawHeadTail(x, y, dir, index, length) { |
|
|
|
|
|
const { ctx } = this; |
|
|
|
|
|
|
|
|
renderSnake(t, lose) { |
|
|
|
|
|
let { length } = this.snake; |
|
|
let { width, height } = this.config.size; |
|
|
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) |
|
|
|
|
|
); |
|
|
|
|
|
|
|
|
if (lose) t = 1 - lose; |
|
|
|
|
|
const list = []; |
|
|
|
|
|
for (let body of this.snake) { |
|
|
|
|
|
list.push(body); |
|
|
|
|
|
} |
|
|
|
|
|
let lastFoodPos = |
|
|
|
|
|
this.lastFood === null ? this.lastFood : this.getPosition(this.lastFood); |
|
|
|
|
|
lastFoodPos = lastFoodPos |
|
|
|
|
|
? { x: lastFoodPos[0] + width / 2, y: lastFoodPos[1] + height / 2 } |
|
|
|
|
|
: lastFoodPos; |
|
|
|
|
|
const points = list.map(({ data }) => { |
|
|
|
|
|
const [x, y] = this.getPosition(data); |
|
|
|
|
|
return { x: x + width / 2, y: y + height / 2 }; |
|
|
|
|
|
}); |
|
|
|
|
|
const { ctx } = this; |
|
|
|
|
|
|
|
|
// k
|
|
|
|
|
|
// |
|
|
|
|
|
|
// |
|
|
|
|
|
|
// |
|
|
|
|
|
|
// g---h---i
|
|
|
|
|
|
// | / \ |
|
|
|
|
|
|
// f j
|
|
|
|
|
|
// | |
|
|
|
|
|
|
// a-b---c---d-e
|
|
|
|
|
|
// |
|
|
|
|
|
|
// |
|
|
|
|
|
|
// |
|
|
|
|
|
|
// l
|
|
|
|
|
|
|
|
|
const lineWidth = (width / 120) * 90; |
|
|
|
|
|
|
|
|
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(); |
|
|
|
|
|
|
|
|
let pre = null; |
|
|
|
|
|
let headPos = null; |
|
|
|
|
|
let headRad = null; |
|
|
|
|
|
if (this.tail !== null) { |
|
|
|
|
|
let [x, y] = this.getPosition(this.tail.data); |
|
|
|
|
|
x = x + width / 2; |
|
|
|
|
|
y = y + height / 2; |
|
|
|
|
|
points.push({ x, y }); |
|
|
|
|
|
length++; |
|
|
|
|
|
} |
|
|
|
|
|
points.forEach(({ x, y }, i) => { |
|
|
|
|
|
if (!pre) { |
|
|
|
|
|
pre = { x, y }; |
|
|
|
|
|
} else { |
|
|
|
|
|
const start = |
|
|
|
|
|
i === 1 ? { x: (pre.x - x) * t + x, y: (pre.y - y) * t + y } : pre; |
|
|
|
|
|
const end = |
|
|
|
|
|
i === length - 1 |
|
|
|
|
|
? { |
|
|
|
|
|
x: (x - pre.x) * (1 - t) + pre.x, |
|
|
|
|
|
y: (y - pre.y) * (1 - t) + pre.y, |
|
|
|
|
|
} |
|
|
|
|
|
: { x, y }; |
|
|
|
|
|
const isV = pre.x === x; |
|
|
|
|
|
const gStart = { |
|
|
|
|
|
x: isV ? x : x - i * (x - pre.x), |
|
|
|
|
|
y: isV ? y - i * (y - pre.y) : y, |
|
|
|
|
|
}; |
|
|
|
|
|
const gEnd = { |
|
|
|
|
|
x: isV ? x : x + (length - i) * (x - pre.x), |
|
|
|
|
|
y: isV ? y + (length - i) * (y - pre.y) : y, |
|
|
|
|
|
}; |
|
|
|
|
|
const gradient = ctx.createLinearGradient( |
|
|
|
|
|
gStart.x, |
|
|
|
|
|
gStart.y, |
|
|
|
|
|
gEnd.x, |
|
|
|
|
|
gEnd.y |
|
|
|
|
|
); |
|
|
|
|
|
gradient.addColorStop(0, startColor); |
|
|
|
|
|
gradient.addColorStop(1, endColor); |
|
|
|
|
|
ctx.strokeStyle = gradient; |
|
|
|
|
|
ctx.beginPath(); |
|
|
|
|
|
ctx.moveTo(start.x, start.y); |
|
|
|
|
|
ctx.lineCap = "round"; |
|
|
|
|
|
ctx.lineWidth = lineWidth; |
|
|
|
|
|
ctx.lineTo(end.x, end.y); |
|
|
|
|
|
ctx.stroke(); |
|
|
|
|
|
ctx.closePath(); |
|
|
|
|
|
if ( |
|
|
|
|
|
lastFoodPos && |
|
|
|
|
|
Math.abs(lastFoodPos.x - x) < 1 && |
|
|
|
|
|
Math.abs(lastFoodPos.y - y) < 1 |
|
|
|
|
|
) { |
|
|
|
|
|
if (i === length - 2) this.lastFood = null; |
|
|
|
|
|
ctx.fillStyle = gradient; |
|
|
|
|
|
ctx.beginPath(); |
|
|
|
|
|
ctx.arc( |
|
|
|
|
|
x, |
|
|
|
|
|
y, |
|
|
|
|
|
(lineWidth + (width - lineWidth) * (1 - i / length)) / 2, |
|
|
|
|
|
0, |
|
|
|
|
|
Math.PI * 2 |
|
|
|
|
|
); |
|
|
|
|
|
ctx.fill(); |
|
|
|
|
|
} |
|
|
|
|
|
if (i === 1) { |
|
|
|
|
|
headPos = start; |
|
|
|
|
|
headRad = Math.atan2(end.y - pre.y, end.x - pre.x) - Math.PI / 2; |
|
|
|
|
|
} |
|
|
|
|
|
pre = { x, y }; |
|
|
|
|
|
} |
|
|
|
|
|
}); |
|
|
|
|
|
lose |
|
|
|
|
|
? this.renderTear(headPos, headRad, lose) |
|
|
|
|
|
: this.renderEye(headPos, headRad); |
|
|
|
|
|
const foodPos = { |
|
|
|
|
|
x: this.food.left + width / 2, |
|
|
|
|
|
y: this.food.top + height / 2, |
|
|
|
|
|
}; |
|
|
|
|
|
const foodInRange = |
|
|
|
|
|
Math.abs(foodPos.x - headPos.x) <= width * 2 && |
|
|
|
|
|
Math.abs(foodPos.y - headPos.y) <= width * 2; |
|
|
|
|
|
|
|
|
|
|
|
if (foodInRange && !lose) this.renderMouth(headPos, headRad, t); |
|
|
} |
|
|
} |
|
|
drawRec(x, y, dir, i, length) { |
|
|
|
|
|
const { ctx } = this; |
|
|
|
|
|
|
|
|
renderMouth(pos, rad, t) { |
|
|
|
|
|
t = 1; |
|
|
let { width, height } = this.config.size; |
|
|
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) |
|
|
|
|
|
|
|
|
const r = (width / 120) * 90; |
|
|
|
|
|
const { ctx } = this; |
|
|
|
|
|
ctx.save(); |
|
|
|
|
|
ctx.translate(pos.x, pos.y); |
|
|
|
|
|
ctx.rotate(rad); |
|
|
|
|
|
const imgHeight = (width / this.mouthImg.width) * this.mouthImg.height; |
|
|
|
|
|
ctx.drawImage( |
|
|
|
|
|
this.mouthImg, |
|
|
|
|
|
-width / 2, |
|
|
|
|
|
-height / 2 + imgHeight / 4, |
|
|
|
|
|
width, |
|
|
|
|
|
imgHeight * t |
|
|
); |
|
|
); |
|
|
|
|
|
|
|
|
// 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(); |
|
|
|
|
|
|
|
|
ctx.restore(); |
|
|
} |
|
|
} |
|
|
drawCorner(x, y, dir, i, length) { |
|
|
|
|
|
|
|
|
renderTear(pos, rad, t) { |
|
|
|
|
|
let { width } = this.config.size; |
|
|
|
|
|
const r = ((width / 120) * 90) / 2; |
|
|
const { ctx } = this; |
|
|
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) |
|
|
|
|
|
|
|
|
{ |
|
|
|
|
|
ctx.save(); |
|
|
|
|
|
ctx.translate(pos.x, pos.y); |
|
|
|
|
|
ctx.rotate(rad); |
|
|
|
|
|
const displayWidth = (width / 120) * this.tearImg.width; |
|
|
|
|
|
const displayHeight = (width / 120) * this.tearImg.height; |
|
|
|
|
|
ctx.drawImage( |
|
|
|
|
|
this.tearImg, |
|
|
|
|
|
-displayWidth / 2, |
|
|
|
|
|
-displayHeight / 2 + (width / 120) * 100 - r, |
|
|
|
|
|
displayWidth, |
|
|
|
|
|
displayHeight |
|
|
|
|
|
); |
|
|
|
|
|
ctx.restore(); |
|
|
|
|
|
} |
|
|
|
|
|
{ |
|
|
|
|
|
ctx.save(); |
|
|
|
|
|
ctx.translate(pos.x, pos.y); |
|
|
|
|
|
ctx.rotate(rad); |
|
|
|
|
|
const displayWidth = (width / 120) * this.starsImg.width * t; |
|
|
|
|
|
const displayHeight = (width / 120) * this.starsImg.height * t; |
|
|
|
|
|
ctx.drawImage( |
|
|
|
|
|
this.starsImg, |
|
|
|
|
|
-displayWidth / 2, |
|
|
|
|
|
-displayHeight / 2 + (width / 120) * 100 - r, |
|
|
|
|
|
displayWidth, |
|
|
|
|
|
displayHeight |
|
|
|
|
|
); |
|
|
|
|
|
ctx.restore(); |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
renderEye(pos, rad) { |
|
|
|
|
|
let { width } = this.config.size; |
|
|
|
|
|
const r = ((width / 120) * 90) / 2; |
|
|
|
|
|
const { ctx } = this; |
|
|
|
|
|
ctx.save(); |
|
|
|
|
|
ctx.translate(pos.x, pos.y); |
|
|
|
|
|
ctx.rotate(rad); |
|
|
|
|
|
const displayWidth = (width / 120) * this.eyeImg.width; |
|
|
|
|
|
const displayHeight = (width / 120) * this.eyeImg.height; |
|
|
|
|
|
ctx.drawImage( |
|
|
|
|
|
this.eyeImg, |
|
|
|
|
|
-displayWidth / 2, |
|
|
|
|
|
-displayHeight / 2 + (width / 120) * 100 - r, |
|
|
|
|
|
displayWidth, |
|
|
|
|
|
displayHeight |
|
|
); |
|
|
); |
|
|
|
|
|
|
|
|
//
|
|
|
|
|
|
// 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(); |
|
|
|
|
|
|
|
|
ctx.restore(); |
|
|
} |
|
|
} |
|
|
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); |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
renderLose() { |
|
|
|
|
|
return new Promise((resolve) => { |
|
|
|
|
|
this.currentTween && this.currentTween.stop(); |
|
|
|
|
|
const tween = new TWEEN.Tween({ t: 0 }); |
|
|
|
|
|
tween.easing(TWEEN.Easing.Elastic.Out); |
|
|
|
|
|
tween.to({ t: 1 }, 500); |
|
|
|
|
|
tween.onComplete(resolve); |
|
|
|
|
|
tween.onUpdate(({ t }) => { |
|
|
|
|
|
this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height); |
|
|
|
|
|
this.renderFood(1); |
|
|
|
|
|
this.renderSnake(1, t); |
|
|
|
|
|
requestAnimationFrame(() => { |
|
|
|
|
|
TWEEN.update(); |
|
|
|
|
|
}); |
|
|
|
|
|
}); |
|
|
|
|
|
requestAnimationFrame(() => { |
|
|
|
|
|
TWEEN.update(); |
|
|
|
|
|
}); |
|
|
|
|
|
tween.start(); |
|
|
}); |
|
|
}); |
|
|
} |
|
|
} |
|
|
// 渲染
|
|
|
// 渲染
|
|
|
render() { |
|
|
render() { |
|
|
this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height); |
|
|
|
|
|
this.renderFood(); |
|
|
|
|
|
this.renderSnake(); |
|
|
|
|
|
|
|
|
if (this.interval) { |
|
|
|
|
|
const tween = new TWEEN.Tween({ t: 0 }); |
|
|
|
|
|
this.currentTween = tween; |
|
|
|
|
|
tween.to({ t: 1 }, this.interval); |
|
|
|
|
|
tween.onUpdate(({ t }) => { |
|
|
|
|
|
this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height); |
|
|
|
|
|
this.renderFood(t); |
|
|
|
|
|
this.renderSnake(t); |
|
|
|
|
|
requestAnimationFrame(() => { |
|
|
|
|
|
TWEEN.update(); |
|
|
|
|
|
}); |
|
|
|
|
|
}); |
|
|
|
|
|
requestAnimationFrame(() => { |
|
|
|
|
|
TWEEN.update(); |
|
|
|
|
|
}); |
|
|
|
|
|
tween.start(); |
|
|
|
|
|
} else { |
|
|
|
|
|
this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height); |
|
|
|
|
|
this.renderFood(0); |
|
|
|
|
|
this.renderSnake(0); |
|
|
|
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
|