1export const PHYSICS = { 2 gravity: 640, 3 flapVy: -196, 4 maxFall: 268, 5 crashBounce: -70, 6 birdX: 66, 7 birdW: 22, 8 birdH: 20, 9 groundY: 194, 10 columnW: 22, 11 columnGap: 112, 12 hitbox: { x: 5, y: 6, w: 12, h: 8 }, 13 startY: 82, 14 viewW: 256, 15 viewH: 224, 16 gapMinY: 24, 17 gapBottomPad: 30, 18 gapWander: 44, 19 firstColumnPad: 60, 20 speed0: 60, 21 speed20: 94, 22 gap0: 72, 23 gap20: 56, 24 scoreRamp: 20, 25}; 26 27export const POINTS = { 28 base: 80, 29 step: 10, 30}; 31 32export function awardPoints(n) { 33 return POINTS.base + (n - 1) * POINTS.step; 34} 35 36function clamp01(t) { 37 return Math.min(1, Math.max(0, t)); 38} 39 40function lerp(a, b, t) { 41 return a + (b - a) * clamp01(t); 42} 43 44export function scrollSpeed(score) { 45 return lerp(PHYSICS.speed0, PHYSICS.speed20, score / PHYSICS.scoreRamp); 46} 47 48export function gapHeight(score) { 49 return Math.round(lerp(PHYSICS.gap0, PHYSICS.gap20, score / PHYSICS.scoreRamp)); 50} 51 52function makeRng(seed) { 53 let s = seed >>> 0; 54 return () => { 55 s += 1831565813; 56 let t = s; 57 t = Math.imul(t ^ (t >>> 15), t | 1); 58 t ^= t + Math.imul(t ^ (t >>> 7), t | 61); 59 return ((t ^ (t >>> 14)) >>> 0) / 4294967296; 60 }; 61} 62 63export function createWorld({ seed = 1 } = {}) { 64 return { 65 phase: "ready", 66 rng: makeRng(seed), 67 elapsed: 0, 68 t: 0, 69 y: PHYSICS.startY, 70 vy: 0, 71 columns: [], 72 score: 0, 73 clears: 0, 74 events: [], 75 }; 76} 77 78export function birdHitbox(world) { 79 const { x, y, w, h } = PHYSICS.hitbox; 80 return { 81 x: PHYSICS.birdX + x, 82 y: world.y + y, 83 w, 84 h, 85 }; 86} 87 88export function columnBoxes(column) { 89 const gapBottom = column.gapY + column.gap; 90 return { 91 top: { x: column.x, y: 0, w: PHYSICS.columnW, h: column.gapY }, 92 bottom: { 93 x: column.x, 94 y: gapBottom, 95 w: PHYSICS.columnW, 96 h: PHYSICS.groundY - gapBottom, 97 }, 98 }; 99} 100 101function aabb(a, b) { 102 return a.x < b.x + b.w && a.x + a.w > b.x && a.y < b.y + b.h && a.y + a.h > b.y; 103} 104 105function pickGapY(world, gap) { 106 const minCenter = PHYSICS.gapMinY + gap / 2; 107 const maxCenter = PHYSICS.groundY - gap - PHYSICS.gapBottomPad + gap / 2; 108 const last = world.columns[world.columns.length - 1]; 109 const prevCenter = last 110 ? last.gapY + last.gap / 2 111 : (minCenter + maxCenter) / 2; 112 const wander = (world.rng() * 2 - 1) * PHYSICS.gapWander; 113 const center = Math.min(maxCenter, Math.max(minCenter, prevCenter + wander)); 114 return Math.round(center - gap / 2); 115} 116 117function spawnColumn(world, x) { 118 const gap = gapHeight(world.clears); 119 world.columns.push({ 120 x, 121 gap, 122 gapY: pickGapY(world, gap), 123 passed: false, 124 }); 125} 126 127export function flap(world) { 128 if (world.phase === "falling" || world.phase === "dead") return false; 129 if (world.phase === "ready") { 130 world.phase = "playing"; 131 world.t = 0; 132 } 133 world.vy = PHYSICS.flapVy; 134 world.events.push({ type: "flap" }); 135 return true; 136} 137 138function crashIntoColumn(world, hitY) { 139 world.phase = "falling"; 140 world.t = 0; 141 world.vy = PHYSICS.crashBounce; 142 world.events.push({ 143 type: "crash", 144 x: PHYSICS.birdX + PHYSICS.birdW / 2, 145 y: hitY, 146 }); 147} 148 149export function updateWorld(world, dt, viewport) { 150 world.t += dt; 151 if (world.phase === "dead" || world.phase === "ready") return; 152 153 world.elapsed += dt; 154 world.vy = Math.min(PHYSICS.maxFall, world.vy + PHYSICS.gravity * dt); 155 world.y += world.vy * dt; 156 if (world.y < 0) { 157 world.y = 0; 158 world.vy = 0; 159 } 160 161 if (world.phase === "falling") { 162 if (world.y + PHYSICS.birdH >= PHYSICS.groundY) { 163 world.y = PHYSICS.groundY - PHYSICS.birdH; 164 world.vy = 0; 165 world.phase = "dead"; 166 world.t = 0; 167 world.events.push({ 168 type: "land", 169 x: PHYSICS.birdX + PHYSICS.birdW / 2, 170 y: PHYSICS.groundY, 171 }); 172 } 173 return; 174 } 175 176 const speed = scrollSpeed(world.clears); 177 for (const column of world.columns) column.x -= speed * dt; 178 if (world.columns.length > 0 && world.columns[0].x + PHYSICS.columnW < 0) { 179 world.columns.shift(); 180 } 181 if (world.columns.length === 0) {
182 spawnColumn(world, viewport.width + PHYSICS.firstColumnPad); 183 } 184 while (world.columns[world.columns.length - 1].x < viewport.width) { 185 spawnColumn( 186 world, 187 world.columns[world.columns.length - 1].x + PHYSICS.columnGap, 188 ); 189 } 190 191 if (world.y + PHYSICS.birdH >= PHYSICS.groundY) { 192 world.y = PHYSICS.groundY - PHYSICS.birdH; 193 world.vy = 0; 194 world.phase = "dead"; 195 world.t = 0; 196 const x = PHYSICS.birdX + PHYSICS.birdW / 2; 197 world.events.push({ type: "crash", x, y: PHYSICS.groundY }); 198 world.events.push({ type: "land", x, y: PHYSICS.groundY }); 199 return; 200 } 201 202 const box = birdHitbox(world); 203 for (const column of world.columns) { 204 const { top, bottom } = columnBoxes(column); 205 if (aabb(box, top) || aabb(box, bottom)) { 206 crashIntoColumn(world, box.y + box.h / 2); 207 return; 208 } 209 } 210 211 const birdCenterX = PHYSICS.birdX + PHYSICS.birdW / 2; 212 for (const column of world.columns) { 213 if (column.passed || column.x + PHYSICS.columnW > birdCenterX) continue; 214 column.passed = true; 215 world.clears += 1; 216 const gained = awardPoints(world.clears); 217 world.score += gained; 218 world.events.push({ 219 type: "score", 220 score: world.score, 221 gained, 222 clears: world.clears, 223 }); 224 } 225}
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.