← DOOM results

DOOM

ox alpha · full

Initial view of DOOM

Reviews & generation details

Reviews

No human review yet. Unreviewed does not mean unsuccessful.

Generation context

Imported historical artifact. Its exact original instructions and lineage are not known.

Generation prompt · exact text unavailable

See the current prompt for reference. It may differ from the instructions used to generate this artifact.

Inspect original source 11,547 bytes · SHA-256 ec34dca5b80d
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>DEMON PROTOCOL — Retro FPS</title>
<style>
  html,body{margin:0;height:100%;background:#000;overflow:hidden;font-family:"Courier New",monospace;}
  #wrap{position:relative;width:100vw;height:100vh;display:flex;align-items:center;justify-content:center;background:#000;}
  canvas{image-rendering:pixelated;cursor:crosshair;}
  #start{position:absolute;color:#f44;font-size:26px;text-align:center;line-height:1.8;
    background:rgba(0,0,0,.85);padding:30px 50px;border:3px solid #f44;letter-spacing:2px;z-index:10;}
  #start b{color:#ff0}
</style>
</head>
<body>
<div id="wrap">
  <canvas id="cv" width="640" height="400"></canvas>
  <div id="start">
    DEMON PROTOCOL<br>
    <b>WASD</b> move &nbsp; <b>MOUSE</b> look &nbsp; <b>CLICK</b> shoot<br>
    <b>1</b> Pistol &nbsp; <b>2</b> Shotgun &nbsp; <b>3</b> Minigun<br><br>
    CLICK TO LOCK MOUSE AND BEGIN<br>
    <span style="color:#888;font-size:16px">Kill demons. Find the glowing EXIT portal.</span>
  </div>
</div>
<script>
"use strict";
// ============ MAP ============
const MAPSTR=[
"########################",
"#......#####...........#",
"#.g....#...#..a....h...#",
"#......#...#........t..#",
"#..##..#...####..#######",
"#..##..........#.....#.#",
"#......g.......#..t..#.#",
"###.#########..#.....#.#",
"#.....#.....#..###.###.#",
"#..h..#..a..#....#.#...#",
"#.....#.....#.t..#.#.g.#",
"###.###############.#...#".slice(0,24),
"#...................#.#",
"#.#####.#####.####..#.#",
"#.#...#.#...#.#..#..#.#",
"#.#.g.#.#.h.#.#a.#..#.#",
"#.#...#.#...#.#..#..#.#",
"#.##.##.##.##.##.##.#.#",
"#..........g.........E#",
"#.t####################",
"#..#..................#",
"#a.#...g....t....h...s#",
"#..#..................#",
"########################"];
const MW=24,MH=24;
let grid=[],spawn={x:2,y:2};
const enemies=[],pickups=[];
for(let y=0;y<MH;y++){grid[y]=[];
 for(let x=0;x<MW;x++){
  let c=(MAPSTR[y]||"").padEnd(24,"#")[x];
  const here={x:x+.5,y:y+.5};
  if(c==='#')grid[y][x]=1;
  else{grid[y][x]=0;
   if(c==='P')spawn=here;
   else if(c==='g')enemies.push(mkEnemy(here.x,here.y,'grunt'));
   else if(c==='t')enemies.push(mkEnemy(here.x,here.y,'tank'));
   else if(c==='h')pickups.push({x:here.x,y:here.y,type:'health',r:0});
   else if(c==='a')pickups.push({x:here.x,y:here.y,type:'ammo',r:0});
   else if(c==='s')pickups.push({x:here.x,y:here.y,type:'shells',r:0});
  }
 }}
function mkEnemy(x,y,t){return{x,y,type:t,hp:t==='tank'?90:45,maxhp:t==='tank'?90:45,
 speed:t==='tank'?1.1:1.7,r:t===  'tank'?0.45:0.32,cool:Math.random()*2,pain:0,bob:Math.random()*6};}

// ============ SPRITE ART (procedural pixel art) ============
function makeTex(sz,fn){const c=document.createElement('canvas');c.width=c.height=sz;const g=c.getContext('2d');fn(g,sz);return c;}
function px(g,x,y,w,h,col){g.fillStyle=col;g.fillRect(x,y,w,h);}
const TEX={
grunt:makeTex(48,(g,s)=>{ // brown demon soldier
 px(g,17,4,14,12,'#c8a06a');px(g,20,8,3,4,'#f00');px(g,26,8,3,4,'#f00');px(g,19,13,11,3,'#600');
 px(g,15,16,18,20,'#7a4f28');px(g,19,18,4,8,'#a5713c');px(g,26,18,4,8,'#a5713c');
 px(g,13,36,8,10,'#5c3a1c');px(g,27,36,8,10,'#5c3a1c');px(g,22,22,5,12,'#333');}),
tank:makeTex(56,(g,s)=>{ // hulking red brute
 px(g,14,4,28,16,'#8a2020');px(g,19,9,5,5,'#ff0');px(g,33,9,5,5,'#ff0');px(g,18,17,21,4,'#400');
 px(g,10,20,36,24,'#a03030');px(g,16,24,6,10,'#c85050');px(g,34,24,6,10,'#c85050');
 px(g,12,44,12,12,'#601515');px(g,32,44,12,12,'#601515');}),
health:makeTex(32,(g)=>{px(g,4,10,24,18,'#ddd');px(g,4,10,24,4,'#fff');px(g,13,13,6,12,'#e00');px(g,9,17,14,5,'#e00');}),
ammo:makeTex(32,(g)=>{px(g,5,12,22,15,'#887');px(g,5,12,22,4,'#aa9');px(g,9,16,3,7,'#fd0');px(g,14,16,3,7,'#fd0');px(g,19,16,3,7,'#fd0');}),
shells:makeTex(32,(g)=>{px(g,5,12,22,15,'#072');px(g,5,12,22,4,'#094');px(g,8,16,4,8,'#f80');px(g,14,16,4,8,'#f80');px(g,20,16,4,8,'#f80');}),
bolt:makeTex(24,(g)=>{px(g,6,6,12,12,'#f0f');px(g,9,9,6,6,'#fdf');})};

// ============ STATE ============
const cv=document.getElementById('cv'),ctx=cv.getContext('2d'),W=640,H=400;
const P={x:spawn.x,y:spawn.y,a:0,hp:100,weapon:0,bobT:0,dead:false,deadT:0,deaths:0};
const WEAPONS=[
 {name:'PISTOL',rate:.32,dmg:18,pellets:1,spread:.015,ammo:'bullets',kick:4},
 {name:'SHOTGUN',rate:.85,dmg:11,pellets:7,spread:.13,ammo:'shells',kick:12},
 {name:'MINIGUN',rate:.085,dmg:9,pellets:1,spread:.06,ammo:'bullets',kick:3}];
const AMMO={bullets:60,shells:8};
let have=[true,false,false],cool=0,flash=0,kick=0,shakeT=0;
let projs=[],parts=[],state='menu',msg='',msgT=0,kills=0,totalKills=enemies.length,time=0;
const keys={};let mouseDown=false;

addEventListener('keydown',e=>{keys[e.code]=true;
 if(e.code==='Digit1'&&have[0])P.weapon=0;
 if(e.code==='Digit2'&&have[1]){P.weapon=1;say('SHOTGUN ACQUIRED');}
 if(e.code==='Digit3'&&have[2]){P.weapon=2;say('MINIGUN ACQUIRED');}});
addEventListener('keyup',e=>keys[e.code]=false);
document.addEventListener('pointerlockchange',()=>{if(document.pointerLockElement){state=state==='menu'?'play':state;document.getElementById('start').style.display='none';}});
cv.addEventListener('click',()=>{if(state!=='win')cv.requestPointerLock();initAudio();});
addEventListener('mousemove',e=>{if(document.pointerLockElement)P.a+=e.movementX*.0026;});
addEventListener('mousedown',()=>mouseDown=true);
addEventListener('mouseup',()=>mouseDown=false);

function say(t){msg=t;msgT=2;}

// ============ AUDIO ============
let AC=null;
function initAudio(){if(!AC)try{AC=new (window.AudioContext||window.webkitAudioContext)();}catch(e){}}
function tone(f,dur,type,vol,slide){if(!AC)return;const o=AC.createOscillator(),g=AC.createGain();
 o.type=type||'square';o.frequency.value=f;if(slide)o.frequency.exponentialRampToValueAtTime(slide,AC.currentTime+dur);
 g.gain.value=vol||.08;g.gain.exponentialRampToValueAtTime(.001,AC.currentTime+dur);
 o.connect(g);g.connect(AC.destination);o.start();o.stop(AC.currentTime+dur);}
function noise(dur,vol){if(!AC)return;const n=AC.sampleRate*dur|0,b=AC.createBuffer(1,n,AC.sampleRate),d=b.getChannelData(0);
 for(let i=0;i<n;i++)d[i]=(Math.random()*2-1)*(1-i/n);
 const s=AC.createBufferSource(),g=AC.createGain();s.buffer=b;g.gain.value=vol||.15;s.connect(g);g.connect(AC.destination);s.start();}

// ============ RAYCAST HELPERS ============
function isWall(x,y){const gx=x|0,gy=y|0;return gx<0||gy<0||gx>=MW||gy>=MH||grid[gy][gx]===1;}
function castRay(ox,oy,dx,dy){ // DDA -> {dist, side}
 let mapX=ox|0,mapY=oy|0;
 const ddx=Math.abs(1/(dx||1e-9)),ddy=Math.abs(1/(dy||1e-9));
 let stepX,stepY,sdx,sdy;
 if(dx<0){stepX=-1;sdx=(ox-mapX)*ddx;}else{stepX=1;sdx=(mapX+1-ox)*ddx;}
 if(dy<0){stepY=-1;sdy=(oy-mapY)*ddy;}else{stepY=1;sdy=(mapY+1-oy)*ddy;}
 let side=0;
 for(let i=0;i<64;i++){
  if(sdx<sdy){sdx+=ddx;mapX+=stepX;side=0;}else{sdy+=ddy;mapY+=stepY;side=1;}
  if(mapX<0||mapY<0||mapX>=MW||mapY>=MH)break;
  if(grid[mapY][mapX]===1)break;
 }
 return {dist:side===0?sdx-ddx:sdy-ddy,side};
}
function los(x1,y1,x2,y2){const dx=x2-x1,dy=y2-y1,d=Math.hypot(dx,dy);return castRay(x1,y1,dx/d,dy/d).dist>d;}

// ============ COMBAT ============
function hitscan(ang,dmg){
 const dx=Math.cos(ang),dy=Math.sin(ang);
 const wallD=castRay(P.x,P.y,dx,dy).dist;
 let best=null,bd=wallD;
 for(const e of enemies){
  if(e.hp<=0)continue;
  const rx=e.x-P.x,ry=e.y-P.y,t=rx*dx+ry*dy;
  if(t>0&&t<bd&&Math.abs(rx*dy-ry*dx)<e.r+.15&&los(P.x,P.y,e.x,e.y)){best=e;bd=t;}
 }
 if(best)hurtEnemy(best,dmg,best.x,best.y);
 else{const hx=P.x+dx*(wallD-.05),hy=P.y+dy*(wallD-.05);spark(hx,hy,'#aa6');}
}
function spark(x,y,c){for(let i=0;i<6;i++)parts.push({x,y,vx:(Math.random()-.5)*4,vy:(Math.random()-.5)*4,t:.3,c});}
function hurtEnemy(e,dmg,hx,hy){e.hp-=dmg;e.pain=.25;noise(.08,.12);
 for(let i=0;i<8;i++)parts.push({x:hx,y:hy,vx:(Math.random()-.5)*4,vy:(Math.random()-.5)*4,t:.35,c:'#a00'});
 if(e.hp<=0){kills++;tone(90,.5,'sawtooth',.12,30);say('DEMON DOWN!  '+kills+'/'+totalKills);}
 else tone(160,.1,'square',.06);}
function fire(){
 const w=WEAPONS[P.weapon];
 if(cool>0)return;
 if(AMMO[w.ammo]<=0){tone(80,.1,'square',.05);cool=.3;return;}
 AMMO[w.ammo]--;cool=w.rate;flash=.07;kick=w.kick;shakeT=.08;
 for(let i=0;i<w.pellets;i++)hitscan(P.a+(Math.random()-.5)*w.spread*2,w.dmg);
 if(P.weapon===0)tone(700,.09,'square',.09,200);
 else if(P.weapon===1)noise(.22,.3);
 else tone(500,.06,'sawtooth',.07,300);
}
function hurtPlayer(d){
 if(P.dead)return;
 P.hp-=d;shakeT=.15;noise(.15,.2);
 if(P.hp<=0){P.hp=0;P.dead=true;P.deadT=0;P.deaths++;tone(60,1.2,'sawtooth',.15,20);}
}

// ============ UPDATE ============
function tryMove(nx,ny){
 const r=.28;
 if(!isWall(nx+r,P.y)&&!isWall(nx-r,P.y))P.x=nx;
 if(!isWall(P.x,ny+r)&&!isWall(P.x,ny-r))P.y=ny;
}
function isExit(x,y){const gx=x|0,gy=y|0;return (MAPSTR[gy]||'')[gx]==='E';}
function update(dt){
 time+=dt;
 if(state!=='play')return;
 if(msgT>0)msgT-=dt;
 if(flash>0)flash-=dt;if(shakeT>0)shakeT-=dt;if(kick>0)kick*=Math.pow(.001,dt);
 if(P.dead){P.deadT+=dt;
  if(P.deadT>2.2){P.dead=false;P.hp=100;P.x=spawn.x;P.y=spawn.y;AMMO.bullets=Math.max(AMMO.bullets,40);AMMO.shells=Math.max(AMMO.shells,4);say('RESPAWNED');}
  return;}
 // move
 const sp=3.4*dt*(keys.ShiftLeft?1.6:1);
 let mx=0,my=0;
 if(keys.KeyW){mx+=Math.cos(P.a);my+=Math.sin(P.a);}
 if(keys.KeyS){mx-=Math.cos(P.a);my-=Math.sin(P.a);}
 if(keys.KeyA){mx+=Math.cos(P.a-Math.PI/2);my+=Math.sin(P.a-Math.PI/2);}
 if(keys.KeyD){mx+=Math.cos(P.a+Math.PI/2);my+=Math.sin(P.a+Math.PI/2);}
 const ml=Math.hypot(mx,my);
 if(ml>0){tryMove(P.x+mx/ml*sp,P.y+my/ml*sp);P.bobT+=dt*(keys.ShiftLeft?14:9);}
 if(keys.ArrowLeft)P.a-=2.4*dt;if(keys.ArrowRight)P.a+=2.4*dt;
 // shoot
 cool-=dt;
 if(mouseDown)fire();
 // pickups
 for(let i=pickups.length-1;i>=0;i--){const p=pickups[i];
  if(Math.hypot(p.x-P.x,p.y-P.y)<.7){
   if(p.type==='health'){if(P.hp>=100)continue;P.hp=Math.min(100,P.hp+30);say('+30 HEALTH');}
   if(p.type==='ammo'){AMMO.bullets+=30;have[2]=true;say('+30 BULLETS — MINIGUN UNLOCKED');}
   if(p.type==='shells'){AMMO.shells+=6;have[1]=true;say('+6 SHELLS — SHOTGUN UNLOCKED');}
   tone(900,.12,'square',.08,1400);pickups.splice(i,1);
  }}
 // exit
 if(isExit(P.x,P.y)){state='win';document.exitPointerLock();tone(400,.8,'square',.1,1200);setTimeout(()=>tone(600,.8,'square',.1,1500),300);}
 // enemies
 for(const e of enemies){
  if(e.hp<=0)continue;
  e.bob+=dt*6;if(e.pain>0)e.pain-=dt;
  const dx=P.x-e.x,dy=P.y-e.y,d=Math.hypot(dx,dy),seen=d<11&&los(e.x,e.y,P.x,P.y);
  if(seen)e.cool-=dt;
  if(seen&&d>1.2){
   const s=e.speed*dt,r=e.r;
   const nx=e.x+dx/d*s,ny=e.y+dy/d*s;
   if(!isWall(nx+r,e.y)&&!isWall(nx-r,e.y))e.x=nx;
   if(!isWall(e.x,ny+r)&&!isWall(e.x,ny-r))e.y=ny;
   for(const o of enemies){if(o!==e&&o.hp>0){const ox=e.x-o.x,oy=e.y-o.y,od=Math.hypot(ox,oy);
    if(od<r+o.r&&od>0){e.x+=ox/od*.02;e.y+=oy/od*.02;}}}
  }
  if(seen&&e.cool<=0){
   if(e.type==='grunt'){
    e.cool=1.4+Math.random();
    projs.push({x:e.x,y:e.y,vx:dx/d*7,vy:dy/d*7,t:2,dmg:7});
    tone(220,.1,'square',.07);
   }else{
    e.cool=1.9+Math.random()*.8;
    projs.push({x:e.x,y:e.y,vx:dx/d*5.5,vy:dy/d*5.5,t:4,dmg:16});
    tone(140,.25,'sawtooth',.09,90);
   }}
 }
 // projectiles
 for(let i=projs.length-1;i>=0;i--){const p=projs[i];
  p.x+=p.vx*dt;p.y+=p.vy*dt;p.t-=dt;
  if(isWall(p.x,p.y)||p.t<=0){projs.splice(i,1);continue;}
  if(Math.hypot(p.x-P.x,p.y-P.y)<.45){hurtPlayer(p.dmg);projs.splice(i,1);}
 }
 // particles
 for(let i=parts.length-1;i>=0;i--){const q=parts[i];q.x+=q.vx*dt;q.y+=q.vy*dt;q.t-=dt;if(q.t<=0)parts.splice(i,1);}
}
</script>
</body>
</html>