/* 贪蛇出动 - 世界:金币生成/拾取/磁铁/边界/相机/死亡掉币 */ (function () { 'use strict'; window.GS = window.GS || {}; const U = GS.U; class World { constructor() { this.size = GS.CFG.worldSize; this.coins = []; // {x,y,r,value,kind:0普通 1星 2大, phase} this.grid = new U.Grid(220); this.cam = { x: this.size / 2, y: this.size / 2, scale: 1 }; this.seed(); } seed() { this.coins.length = 0; for (let i = 0; i < GS.CFG.coinCount; i++) this.coins.push(this._makeCoin()); this._rebuild(); } _makeCoin(x, y, kind) { const k = kind != null ? kind : (Math.random() < 0.08 ? 1 : 0); const base = { 0: { r: 7, value: 2 }, 1: { r: 12, value: 10 }, // 星币:+幸运星 2: { r: 14, value: 20 } // 大金币:尸体掉落 }[k]; return { x: x != null ? x : U.rand(60, this.size - 60), y: y != null ? y : U.rand(60, this.size - 60), r: base.r, value: base.value, kind: k, phase: U.rand(0, Math.PI * 2), vx: 0, vy: 0 }; } _rebuild() { this.grid.clear(); for (const c of this.coins) this.grid.insert(c); } // 尸体掉币:沿身体间隔掉落 dropFromSegments(segs) { for (let i = 0; i < segs.length; i += 2) { const s = segs[i]; this.coins.push(this._makeCoin( U.clamp(s.x + U.rand(-14, 14), 40, this.size - 40), U.clamp(s.y + U.rand(-14, 14), 40, this.size - 40), 2)); } this._rebuild(); } // 外部新增单枚金币(加速掉落等),并重建网格索引 addCoin(x, y, kind) { const c = this._makeCoin( U.clamp(x, 40, this.size - 40), U.clamp(y, 40, this.size - 40), kind || 0); c.r = kind === 2 ? 14 : (kind === 1 ? 12 : 5); c.value = kind === 2 ? 20 : (kind === 1 ? 10 : 1); this.coins.push(c); this.grid.insert(c); return c; } // 宝藏屋奖励:玩家附近撒一大簇金币 treasureBurst(x, y) { for (let i = 0; i < 70; i++) { const a = U.rand(0, Math.PI * 2), d = U.rand(30, 320); this.coins.push(this._makeCoin( U.clamp(x + Math.cos(a) * d, 40, this.size - 40), U.clamp(y + Math.sin(a) * d, 40, this.size - 40), Math.random() < 0.25 ? 1 : (Math.random() < 0.4 ? 2 : 0))); } this._rebuild(); } update(dt, snakes, now) { let moved = false; for (const s of snakes) { if (s.dead) continue; const h = s.head; const magnet = now < s.magnetUntil; const eatR = s.radius + 16; const pullR = magnet ? 260 : eatR; const near = this.grid.query(h.x, h.y, Math.max(pullR, 60)); for (const c of near) { if (c._eaten) continue; const d2 = U.dist2(h.x, h.y, c.x, c.y); if (magnet && d2 < pullR * pullR) { const d = Math.sqrt(d2) || 1; const pull = 420 * dt; c.x += (h.x - c.x) / d * pull; c.y += (h.y - c.y) / d * pull; moved = true; } if (d2 < (eatR + c.r) * (eatR + c.r)) { c._eaten = true; s.grow(c.value); s.coins += c.value; if (c.kind === 1) s.lucky += 1; } } } if (this.coins.some(c => c._eaten)) { this.coins = this.coins.filter(c => !c._eaten); // 补充普通金币保持总量 while (this.coins.length < GS.CFG.coinCount) this.coins.push(this._makeCoin()); moved = true; } if (moved) this._rebuild(); } // 相机跟随 updateCam(target, viewW, viewH, dt) { const k = 1 - Math.pow(0.001, dt); // 平滑跟随 this.cam.x += (target.x - this.cam.x) * k; this.cam.y += (target.y - this.cam.y) * k; const base = Math.min(viewW, viewH) / 720; this.cam.scale = U.clamp(base, 0.7, 1.6); } inBounds(x, y, margin) { return x > margin && y > margin && x < this.size - margin && y < this.size - margin; } // 碰撞判定:返回 {type:'body', snake} 或 {type:'wall'} 或 null collide(s, snakes) { const h = s.head; if (!this.inBounds(h.x, h.y, s.radius * 0.5)) return { type: 'wall' }; for (const o of snakes) { if (o === s || o.dead) continue; const segs = o.segments(); const rr = (s.radius * 0.8 + o.radius * 0.9); // 跳过对方头部附近(允许并排) for (let i = 2; i < segs.length; i++) { const g = segs[i]; if (U.dist2(h.x, h.y, g.x, g.y) < rr * rr) return { type: 'body', snake: o }; } } return null; } } GS.World = World; })();