Back to Crossfire
SOURCE / PINNED RELEASE

Made of little things.

Crossfire

Release
9614abe48623
Author-recorded commit
6f6c8299e2fb…
License
LICENSE
Author’s source reference
nostr://npub1n8ga89w8h6tvwamxusfyzexw8gjy84yxu9rxgnmk955cxtml4ujswzxydd/wss%3A%2F%2Fgit.napplet.soy%2F/n-347ab685aef

Archive hash verified: 34d5295141b419db. The source-to-build association is the author’s claim; it has not been independently rebuilt.

src/game.ts
export const W = 960, H = 640;
export const walls = [[210,130,90,160],[660,350,90,160],[400,280,160,80],[660,130,90,65],[210,445,90,65]];
export type Input = { x:number; y:number; a:number; fire:boolean };
export type Player = { id:string; name?:string; spawn:number; active:boolean; x:number; y:number; a:number; hp:number; score:number; dead:number; shield:number; cooldown:number };
export type Bullet = { id?:number; x:number; y:number; a:number; owner:number; life:number };
export type World = { p:Player[]; b:Bullet[]; winner:number };
export const idle = ():Input => ({x:0,y:0,a:0,fire:false});
export const spawns = [[100,100],[860,540],[860,100],[100,540],[480,100],[480,540],[100,320],[860,320]];
export const colors = ['#91edbc','#ff927d','#9fc4ff','#edafff','#ffe28a','#76e3ed','#ffadd0','#c4ccff'];
export function player(id:string,i:number):Player {return {id,spawn:0,active:true,x:spawns[i][0],y:spawns[i][1],a:0,hp:100,score:0,dead:0,shield:1.5,cooldown:0};}
export function createWorld(ids=['you','bot']):World { return {p:ids.map(player),b:[],winner:-1}; }
const clamp=(n:number,l:number,h:number)=>Math.max(l,Math.min(h,n));
export function blocked(x:number,y:number,r:number){return x<r||y<r||x>W-r||y>H-r||walls.some(([wx,wy,w,h])=>x>wx-r&&x<wx+w+r&&y>wy-r&&y<wy+h+r);}
export function movePlayer(p:Player,u:Input,dt:number){
 if(p.dead||!p.active)return;
 const len=Math.max(1,Math.hypot(u.x,u.y));
 const x=clamp(p.x+u.x/len*220*dt,16,W-16),y=clamp(p.y+u.y/len*220*dt,16,H-16);
 if(!blocked(x,p.y,15))p.x=x;if(!blocked(p.x,y,15))p.y=y;
}
let bulletId=0;
export function step(s:World, inputs:Input[], dt:number){
 if(s.winner>=0)return;
 s.p.forEach((p,i)=>{
  if(!p.active)return;
  p.shield=Math.max(0,p.shield-dt);p.cooldown-=dt;
  if(p.dead>0){p.dead=Math.max(0,p.dead-dt);if(!p.dead){p.spawn++;p.hp=100;p.x=spawns[i][0];p.y=spawns[i][1];p.shield=1.5;}return;}
  const u=inputs[i]||idle();movePlayer(p,u,dt);p.a=u.a;
  if(u.fire&&p.cooldown<=0){p.cooldown=.18;p.shield=0;s.b.push({id:++bulletId,x:p.x+Math.cos(p.a)*22,y:p.y+Math.sin(p.a)*22,a:p.a,owner:i,life:1.2});}
 });
 s.b=s.b.filter(b=>{
  b.life-=dt;b.x+=Math.cos(b.a)*620*dt;b.y+=Math.sin(b.a)*620*dt;
  if(b.life<=0||blocked(b.x,b.y,3))return false;
  const t=s.p.find((p,i)=>i!==b.owner&&p.active&&!p.dead&&!p.shield&&Math.hypot(p.x-b.x,p.y-b.y)<=18);if(!t)return true;
  t.hp-=25;if(t.hp<=0){t.dead=2;s.p[b.owner].score++;if(s.p[b.owner].score>=7)s.winner=b.owner;}return false;
 });
}
export function validInput(v:unknown):v is Input {if(!v||typeof v!=='object')return false;const u=v as Input;return Number.isFinite(u.x)&&Math.abs(u.x)<=1&&Number.isFinite(u.y)&&Math.abs(u.y)<=1&&Number.isFinite(u.a)&&Math.abs(u.a)<=10&&typeof u.fire==='boolean';}
export function validWorld(v:unknown):v is World {
 if(!v||typeof v!=='object')return false;const s=v as World;
 return Number.isInteger(s.winner)&&s.winner>=-1&&s.winner<s.p?.length&&Array.isArray(s.p)&&s.p.length>=1&&s.p.length<=8&&s.p.every(p=>p&&typeof p==='object')&&new Set(s.p.map(p=>p.id)).size===s.p.length&&s.p.every(p=>p&&typeof p.id==='string'&&p.id.length<=64&&(p.name===undefined||(typeof p.name==='string'&&p.name.length<=32))&&Number.isSafeInteger(p.spawn)&&p.spawn>=0&&typeof p.active==='boolean'&&[p.x,p.y,p.a,p.hp,p.score,p.dead,p.shield,p.cooldown].every(Number.isFinite)&&p.x>=0&&p.x<=W&&p.y>=0&&p.y<=H&&p.hp>=0&&p.hp<=100&&Number.isInteger(p.score)&&p.score>=0&&p.score<=7&&p.dead>=0&&p.dead<=2&&p.shield>=0&&p.shield<=1.5)&&Array.isArray(s.b)&&s.b.length<=64&&s.b.every(b=>b&&[b.x,b.y,b.a,b.life].every(Number.isFinite)&&Number.isInteger(b.owner)&&b.owner>=0&&b.owner<s.p.length&&Math.abs(b.x)<=W+100&&Math.abs(b.y)<=H+100&&b.life<=1.2);
}