6dc79a2c12
- 私人剪贴板新增限时直通URL生成功能 - doc_cloud_keeper新增标题复制按钮 - 新增贪蛇出动游戏工具 - 新增OSS中转直链工具 - 添加批量抽帧测试脚本
102 lines
3.0 KiB
JavaScript
102 lines
3.0 KiB
JavaScript
/* 贪蛇出动 - AI 蛇控制器:觅食/避障/边界回避/随机加速 */
|
|
(function () {
|
|
'use strict';
|
|
window.GS = window.GS || {};
|
|
const U = GS.U;
|
|
|
|
class AI {
|
|
constructor(snake) {
|
|
this.s = snake;
|
|
this.decideTimer = U.rand(0, 0.3);
|
|
this.wanderAngle = snake.angle;
|
|
this.boostTimer = 0;
|
|
}
|
|
|
|
update(dt, world, snakes) {
|
|
const s = this.s;
|
|
if (s.dead) return;
|
|
this.decideTimer -= dt;
|
|
this.boostTimer -= dt;
|
|
if (this.decideTimer > 0) {
|
|
s.boosting = this.boostTimer > 0;
|
|
return;
|
|
}
|
|
this.decideTimer = 0.15;
|
|
|
|
const h = s.head;
|
|
const look = 90 + s.radius * 3;
|
|
const aheadX = h.x + Math.cos(s.angle) * look;
|
|
const aheadY = h.y + Math.sin(s.angle) * look;
|
|
|
|
// 1) 边界回避
|
|
const margin = 220;
|
|
let avoid = null;
|
|
if (aheadX < margin || aheadY < margin || aheadX > world.size - margin || aheadY > world.size - margin) {
|
|
avoid = Math.atan2(world.size / 2 - h.y, world.size / 2 - h.x);
|
|
}
|
|
|
|
// 2) 蛇身回避:检查前方采样点是否撞上其他蛇
|
|
if (avoid == null) {
|
|
for (const o of snakes) {
|
|
if (o === s || o.dead) continue;
|
|
const segs = o.segments();
|
|
const rr = (s.radius + o.radius + 26);
|
|
for (let i = 0; i < segs.length; i += 2) {
|
|
if (U.dist2(aheadX, aheadY, segs[i].x, segs[i].y) < rr * rr) {
|
|
// 向左或向右转,选更开阔的一侧
|
|
const left = s.angle - Math.PI / 2, right = s.angle + Math.PI / 2;
|
|
avoid = this._clearer(left, right, h, snakes, s);
|
|
break;
|
|
}
|
|
}
|
|
if (avoid != null) break;
|
|
}
|
|
}
|
|
|
|
if (avoid != null) {
|
|
s.targetAngle = avoid;
|
|
s.boosting = false;
|
|
return;
|
|
}
|
|
|
|
// 3) 觅食:找最近金币
|
|
let best = null, bestD = 520 * 520;
|
|
const near = world.grid.query(h.x, h.y, 520);
|
|
for (const c of near) {
|
|
const d2 = U.dist2(h.x, h.y, c.x, c.y);
|
|
if (d2 < bestD) { bestD = d2; best = c; }
|
|
}
|
|
if (best) {
|
|
s.targetAngle = Math.atan2(best.y - h.y, best.x - h.x);
|
|
} else {
|
|
// 漫游:小幅随机偏转
|
|
this.wanderAngle += U.rand(-0.6, 0.6);
|
|
s.targetAngle = this.wanderAngle;
|
|
}
|
|
|
|
// 4) 随机加速:追食或逃跑时小概率冲刺
|
|
if (Math.random() < 0.06 && s.score > 30) this.boostTimer = U.rand(0.4, 1.0);
|
|
s.boosting = this.boostTimer > 0;
|
|
}
|
|
|
|
_clearer(a1, a2, h, snakes, s) {
|
|
const score = a => {
|
|
const px = h.x + Math.cos(a) * 160, py = h.y + Math.sin(a) * 160;
|
|
let d = 1e9;
|
|
for (const o of snakes) {
|
|
if (o === s || o.dead) continue;
|
|
const segs = o.segments();
|
|
for (let i = 0; i < segs.length; i += 3) {
|
|
const dd = U.dist2(px, py, segs[i].x, segs[i].y);
|
|
if (dd < d) d = dd;
|
|
}
|
|
}
|
|
return d;
|
|
};
|
|
return score(a1) >= score(a2) ? a1 : a2;
|
|
}
|
|
}
|
|
|
|
GS.AI = AI;
|
|
})();
|