Inital commit

This commit is contained in:
2026-07-22 00:34:09 -05:00
commit 0b24de2767
24 changed files with 2691 additions and 0 deletions
+71
View File
@@ -0,0 +1,71 @@
// ---------------------------------------------------------------------------
// Shared game constants & helpers (used by both the server sim and the client)
// ---------------------------------------------------------------------------
export const TICK_RATE = 30; // server simulation ticks per second
export const SNAP_EVERY = 2; // send a snapshot every N ticks (15 Hz)
export const ARENA_RADIUS = 4200; // playable circle radius in world px
export const BASE_SPEED = 178; // px per second
export const BOOST_MULT = 1.95; // speed multiplier while boosting
export const TURN_RATE = 4.4; // radians per second
export const BOOST_TURN_RATE = 3.8; // slightly stiffer steering while boosting
export const START_SCORE = 25;
export const MIN_BOOST_SCORE = 18; // cannot boost below this score
export const BOOST_DRAIN = 16; // score per second burned while boosting
export const BOOST_DROP_INTERVAL = 0.26; // seconds between tail pellets while boosting
export const PELLET_COUNT = 950; // ambient pellets kept alive in the arena
export const BOT_TARGET_TOTAL = 12; // arena is kept filled to this many snakes
export const RESPAWN_PROTECT_TICKS = TICK_RATE * 2; // ~2s of spawn protection
// --- derived snake geometry -------------------------------------------------
export function snakeRadius(score: number): number {
return 5 + Math.sqrt(Math.max(0, score)) * 0.32;
}
export function snakePointCount(score: number): number {
return Math.min(420, Math.round(14 + Math.sqrt(Math.max(0, score)) * 2.1));
}
export function snakeSpacing(score: number): number {
return Math.max(5.5, snakeRadius(score) * 0.5);
}
// --- math helpers ------------------------------------------------------------
export function clamp(v: number, lo: number, hi: number): number {
return v < lo ? lo : v > hi ? hi : v;
}
/** Normalize an angle to (-PI, PI]. */
export function normAngle(a: number): number {
while (a > Math.PI) a -= Math.PI * 2;
while (a <= -Math.PI) a += Math.PI * 2;
return a;
}
export function dist(x1: number, y1: number, x2: number, y2: number): number {
return Math.hypot(x2 - x1, y2 - y1);
}
// --- palettes ---------------------------------------------------------------
export const SNAKE_COLORS = [
'#ff5b5b', '#ffb84d', '#ffe14d', '#7dff5b', '#4dffd2', '#4dc9ff',
'#8a7dff', '#c95bff', '#ff5bd0', '#9dff4d', '#5bff9d', '#ffd24d',
];
export const PELLET_COLORS = [
'#ff6b6b', '#ffd93d', '#6bff8f', '#4dd2ff',
'#c586ff', '#ff8fe1', '#8ffcff', '#ffe28f',
];
export const BOT_NAMES = [
'Noodle', 'Viper', 'Slinky', 'Zigzag', 'Cobra', 'Python', 'Nagini',
'Basilisk', 'Wiggles', 'Turbo', 'Boa', 'Rattles', 'Medusa',
'Sidewinder', 'Anaconda', 'DangerNoodle', 'Hiss', 'Monty',
];
+72
View File
@@ -0,0 +1,72 @@
// ---------------------------------------------------------------------------
// Network protocol shared between server and client (JSON over WebSocket)
// ---------------------------------------------------------------------------
export interface PelletData {
id: number;
x: number;
y: number;
v: number; // food value (also drives render size)
c: number; // color index into PELLET_COLORS
}
export interface SnakeSnap {
id: number;
n: string; // nickname
c: number; // color index into SNAKE_COLORS
s: number; // score
p: number[]; // flat body points [x0,y0,x1,y1,...], head first
b: boolean; // boosting
k: number; // kills
sp?: boolean; // spawn protection active (ghost)
}
export type GameEvent =
| { t: 'die'; id: number; x: number; y: number; c: number; s: number; p: number[] }
| { t: 'join'; id: number; n: string }
| { t: 'leave'; id: number };
export interface LeaderboardRow {
id: number;
n: string;
s: number;
}
export interface YouState {
score: number;
rank: number;
alive: boolean;
kills: number;
best: number;
}
// --- server -> client -------------------------------------------------------
export interface InitMsg {
t: 'init';
id: number; // your snake id
arena: number; // arena radius
pellets: PelletData[]; // full pellet list (deltas arrive afterwards)
}
export interface SnapMsg {
t: 'snap';
tick: number;
snakes: SnakeSnap[];
add: PelletData[]; // pellets added since last snapshot
rem: number[]; // pellet ids removed since last snapshot
ev: GameEvent[]; // events since last snapshot
lb: LeaderboardRow[];
you?: YouState;
total: number; // total snakes alive
}
export type ServerMsg = InitMsg | SnapMsg;
// --- client -> server -------------------------------------------------------
export interface JoinMsg { t: 'join'; name: string }
export interface InputMsg { t: 'in'; a: number; b: boolean } // angle, boosting
export interface RespawnMsg { t: 'respawn' }
export type ClientMsg = JoinMsg | InputMsg | RespawnMsg;