from pathlib import Path
html = r'''<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width,initial-scale=1,maximum-scale=1,user-scalable=no">
<meta name="theme-color" content="#10132d">
<title>Bashbit Apocalypse Survival</title>
<style>
:root{color-scheme:dark;--gold:#ffd43b;--red:#ff4b52;--panel:rgba(11,13,34,.88)}
*{box-sizing:border-box}html,body{margin:0;width:100%;height:100%;overflow:hidden;background:#10132d;font-family:system-ui,-apple-system,Segoe UI,sans-serif;color:#fff;touch-action:none}
#game{display:block;width:100%;height:100%;image-rendering:pixelated}
#hud{position:fixed;inset:0;pointer-events:none;padding:12px;display:flex;flex-direction:column;justify-content:space-between;text-shadow:2px 2px #111}
.top{display:flex;justify-content:space-between;align-items:flex-start;gap:8px;flex-wrap:wrap}
.brand{font-weight:1000;font-size:clamp(16px,3vw,30px);line-height:1.02;letter-spacing:-.7px;color:#fff;max-width:50%;text-transform:uppercase}
.brand span{display:block;color:#ff454b}
.pill{background:var(--panel);border:2px solid #666a9d;border-radius:12px;padding:7px 12px;font-weight:900;font-size:clamp(13px,2.3vw,20px);box-shadow:0 4px 0 #090a1b}
#score{color:var(--gold)}#health{color:#ff656b}
#bossbar{position:absolute;top:16%;left:50%;transform:translateX(-50%);width:min(390px,70vw);display:none;text-align:center;font-weight:1000;color:#ff505a;font-size:clamp(15px,2.5vw,24px)}
.bar{height:15px;border:2px solid #f5e9ee;background:#230e21;border-radius:8px;overflow:hidden;margin-top:4px}.bar i{display:block;width:100%;height:100%;background:linear-gradient(90deg,#ff313e,#b40024)}
#power{position:absolute;right:12px;top:90px;display:none;background:#24160b;border:2px solid var(--gold);border-radius:12px;padding:8px 12px;color:var(--gold);font-weight:900;box-shadow:0 0 20px #ffb80055}
#message{position:absolute;left:50%;top:40%;transform:translate(-50%,-50%);text-align:center;background:var(--panel);border:2px solid #8e91c3;border-radius:14px;padding:12px 18px;font-weight:900;font-size:clamp(15px,3vw,25px);display:none;max-width:90%;white-space:normal}
#help{font-size:12px;color:#d8dcff;opacity:.9;margin-top:6px}
#bottom{display:flex;justify-content:space-between;align-items:end;gap:10px}
#weapon{background:var(--panel);border:2px solid #7d80a8;border-radius:10px;padding:7px 10px;font-size:13px;font-weight:900}
#weapon strong{color:var(--gold)}
#controls{position:fixed;inset:auto 0 12px 0;display:flex;justify-content:space-between;align-items:end;padding:0 14px;pointer-events:none}
#stick{width:122px;height:122px;border-radius:50%;background:#252847aa;border:3px solid #a7acd477;position:relative;pointer-events:auto;touch-action:none}
#nub{position:absolute;left:50%;top:50%;transform:translate(-50%,-50%);width:48px;height:48px;border-radius:50%;background:#b6bbd888;border:2px solid #e5e7ff99}
#fire{width:86px;height:86px;border-radius:50%;background:#ff454b99;border:3px solid #ffe3e3;box-shadow:0 0 0 5px #ff454b33;color:white;font-size:29px;font-weight:1000;pointer-events:auto;touch-action:none}
#pause{pointer-events:auto;border:0;border-radius:9px;background:#17182dcc;color:white;padding:8px 12px;font-weight:900;font-size:18px}
#overlay{position:fixed;inset:0;background:linear-gradient(#0b1024bb,#0b1024dd);display:flex;align-items:center;justify-content:center;padding:18px;z-index:5}
.card{width:min(520px,100%);border:2px solid #6e72a8;border-radius:20px;background:linear-gradient(145deg,#202344,#101327);padding:24px;text-align:center;box-shadow:0 18px 80px #0009}
.card h1{font-size:clamp(27px,7vw,48px);line-height:.98;margin:0 0 12px;font-weight:1000;text-transform:uppercase}
.card h1 span{color:#ff4b52}.card p{color:#d7dbf9;line-height:1.5}
button.start{border:0;border-radius:12px;padding:13px 25px;background:var(--gold);color:#171327;font-size:18px;font-weight:1000;cursor:pointer}
.small{font-size:12px;color:#aeb3dc!important}
@media(min-width:800px){#controls{display:none}#help{font-size:14px}}
@media(max-width:500px){#hud{padding:9px}.brand{max-width:46%}#power{top:82px;right:8px;font-size:12px}#stick{width:108px;height:108px}#fire{width:78px;height:78px}#controls{padding:0 12px;bottom:12px}}
</style>
</head>
<body>
<canvas id="game" aria-label="Bashbit Apocalypse Survival game"></canvas>
<div id="hud">
<div class="top">
<div class="brand">Bashbit <span>Apocalypse Survival</span><div id="help">WASD / arrows to move · Mouse to aim & shoot</div></div>
<div class="pill" id="score">POINTS: 0</div>
<div class="pill" id="health">♥ 100</div>
</div>
<div id="bossbar">☠ BASHOMBIE<div class="bar"><i id="bossfill"></i></div></div>
<div id="power">BASHTURRET ACTIVE · <span id="timer">30</span>s</div>
<div id="message"></div>
<div id="bottom"><div id="weapon">WEAPON: <strong id="weaponName">REGULAR GUN</strong></div><button id="pause">Ⅱ</button></div>
</div>
<div id="controls"><div id="stick"><div id="nub"></div></div><button id="fire" aria-label="Fire">➤</button></div>
<div id="overlay"><div class="card"><h1>Bashbit <span>Apocalypse<br>Survival</span></h1><p>Survive the city, take down slow zombie waves, earn points, and defeat <b>Bashombie</b>. Beat the boss to unlock the <b>BashTurret</b> — a devastating 30-second machine-gun power-up.</p><p><b>PC:</b> WASD/arrows to move, mouse to aim and fire, or hold Space to shoot.<br><b>Mobile:</b> use the joystick and fire button.</p><button class="start" id="start">START SURVIVING</button><p class="small">Infinite survival · Single player · Best score saved on this device</p></div></div>
<script>
(()=>{
const canvas=document.getElementById('game'),ctx=canvas.getContext('2d');
const $=id=>document.getElementById(id);
let W=960,H=540,dpr=1, running=false,paused=false,gameOver=false,last=0,elapsed=0,spawn=0,bossAt=38,boss=null,power=0,score=0,best=0,shake=0;
try{best=Number(localStorage.getItem('bashbitBest')||0)}catch(e){}
const keys={},bullets=[],enemyBullets=[],zombies=[],particles=[],buildings=[];
const player={x:0,y:0,r:13,hp:100,speed:190,fire:0,face:0,invuln:0};
let mouse={x:W*.7,y:H*.5,down:false},joy={x:0,y:0,active:false},touchFire=false;
function resize(){dpr=Math.min(devicePixelRatio||1,2);W=innerWidth;H=innerHeight;canvas.width=Math.floor(W*dpr);canvas.height=Math.floor(H*dpr);canvas.style.width=W+'px';canvas.style.height=H+'px';ctx.setTransform(dpr,0,0,dpr,0,0);player.x=Math.min(Math.max(player.x,30),W-30);player.y=player.y||H*.58;player.y=Math.min(Math.max(player.y,H*.28),H-40);makeBuildings()}
function makeBuildings(){buildings.length=0;let x=-20;while(x<W+100){let bw=70+Math.random()*100,bh=70+Math.random()*160;buildings.push({x,w:bw,h:bh,c:['#242944','#30304c','#252a42','#38324b'][Math.floor(Math.random()*4)],seed:Math.random()*99});x+=bw+8}}
function rand(a,b){return a+Math.random()*(b-a)}
function clamp(v,a,b){return Math.max(a,Math.min(b,v))}
function say(text,ms=2200){const el=$('message');el.innerHTML=text;el.style.display='block';clearTimeout(say.t);say.t=setTimeout(()=>el.style.display='none',ms)}
function addScore(n){score+=n;$('score').textContent='POINTS: '+score}
function spawnZombie(){let side=Math.random()<.5?-1:1;let y=H*.47+Math.random()*H*.42;zombies.push({x:side<0?-25:W+25,y,r:12,hp:2+Math.min(3,Math.floor(elapsed/55)),speed:rand(19,32)+Math.min(12,elapsed*.08),face:side<0?0:Math.PI,type:'normal',hit:0})}
function spawnBoss(){boss={x:W+50,y:H*.61,r:31,hp:90,max:90,speed:13,hit:0,face:Math.PI};$('bossbar').style.display='block';say('⚠ BASHOMBIE HAS ENTERED THE CITY!',3000)}
function shoot(){if(player.fire>0)return;player.fire=power>0?.075:.24;let a=player.face;let fast=power>0;bullets.push({x:player.x+Math.cos(a)*18,y:player.y+Math.sin(a)*18,vx:Math.cos(a)*(fast?690:520),vy:Math.sin(a)*(fast?690:520),life:1.2,dmg:fast?3:1,r:fast?4:3,color:fast?'#ffd84d':'#fff2ba'});if(fast){for(let i=0;i<2;i++)particles.push({x:player.x+Math.cos(a)*20,y:player.y+Math.sin(a)*20,vx:rand(-45,45),vy:rand(-45,45),life:.15,color:'#ffb52e'})}}
function burst(x,y,color,n=8){for(let i=0;i<n;i++){let a=rand(0,Math.PI*2),s=rand(25,120);particles.push({x,y,vx:Math.cos(a)*s,vy:Math.sin(a)*s,life:rand(.2,.55),color})}}
function hurt(n){if(player.invuln>0)return;player.hp-=n;player.invuln=.6;shake=5;$('health').textContent='♥ '+Math.max(0,Math.ceil(player.hp));if(player.hp<=0){player.hp=0;gameOver=true;running=false;try{if(score>best){best=score;localStorage.setItem('bashbitBest',best)}}catch(e){}$('overlay').style.display='flex';$('overlay').querySelector('h1').innerHTML='YOU <span>FELL</span>'; $('overlay').querySelector('p').innerHTML='Final score: <b>'+score+'</b><br>Best score: <b>'+best+'</b><br>The city still needs a survivor.';$('start').textContent='TRY AGAIN'}}
function update(dt){
if(!running||paused||gameOver)return;
elapsed+=dt;spawn+=dt;player.fire-=dt;player.invuln-=dt;player.x=clamp(player.x,18,W-18);player.y=clamp(player.y,H*.28,H-35);
let mx=(keys.a||keys.ArrowLeft?-1:0)+(keys.d||keys.ArrowRight?1:0)+joy.x;
let my=(keys.w||keys.ArrowUp?-1:0)+(keys.s||keys.ArrowDown?1:0)+joy.y;
let mag=Math.hypot(mx,my);if(mag>1){mx/=mag;my/=mag}player.x+=mx*player.speed*dt;player.y+=my*player.speed*dt;
if(mouse.down||keys[' ']||touchFire)shoot();
if(power>0){power=Math.max(0,power-dt);$('power').style.display='block';$('timer').textContent=Math.ceil(power);$('weaponName').textContent='BASHTURRET'}else{$('power').style.display='none';$('weaponName').textContent='REGULAR GUN'}
if(spawn>Math.max(.75,1.75-elapsed*.006)){spawn=0;if(zombies.length<Math.min(30,5+Math.floor(elapsed/8)))spawnZombie()}
if(!boss&&elapsed>=bossAt){spawnBoss();bossAt+=Math.max(48,75-elapsed*.1)}
if(boss){boss.x+=(player.x>boss.x?-1:1)*boss.speed*dt;boss.y+=(player.y>boss.y?1:-1)*boss.speed*.3*dt;boss.y=clamp(boss.y,H*.4,H*.78);boss.hit-=dt;if(Math.hypot(boss.x-player.x,boss.y-player.y)<boss.r+player.r)hurt(14*dt)}
for(let z of zombies){let a=Math.atan2(player.y-z.y,player.x-z.x);z.x+=Math.cos(a)*z.speed*dt;z.y+=Math.sin(a)*z.speed*dt;z.face=a;z.hit-=dt;if(Math.hypot(z.x-player.x,z.y-player.y)<z.r+player.r)hurt(12*dt)}
for(let b of bullets){b.x+=b.vx*dt;b.y+=b.vy*dt;b.life-=dt;for(let z of zombies){if(z.hp>0&&Math.hypot(b.x-z.x,b.y-z.y)<z.r+b.r){z.hp-=b.dmg;b.life=0;burst(b.x,b.y,'#ffdb79',3);if(z.hp<=0){addScore(10);burst(z.x,z.y,'#76bb75',7)}}}if(boss&&Math.hypot(b.x-boss.x,b.y-boss.y)<boss.r+b.r){boss.hp-=b.dmg;b.hit=.08;b.life=0;burst(b.x,b.y,'#ff4a54',4);if(boss.hp<=0){addScore(500);burst(boss.x,boss.y,'#ffcb45',28);power=30;boss=null;$('bossbar').style.display='none';say('BASHOMBIE DEFEATED! +500 POINTS<br><span style="color:#ffd43b">BASHTURRET UNLOCKED · 30 SECONDS</span>',3800);}}
}
for(let i=zombies.length-1;i>=0;i--)if(zombies[i].hp<=0)zombies.splice(i,1);
for(let i=bullets.length-1;i>=0;i--)if(bullets[i].life<=0||bullets[i].x<-30||bullets[i].x>W+30||bullets[i].y<-30||bullets[i].y>H+30)bullets.splice(i,1);
for(let i=particles.length-1;i>=0;i--){let p=particles[i];p.x+=p.vx*dt;p.y+=p.vy*dt;p.life-=dt;if(p.life<=0)particles.splice(i,1)}
if(boss){$('bossfill').style.width=clamp(boss.hp/boss.max*100,0,100)+'%'}
$('score').textContent='POINTS: '+score;
}
function rect(x,y,w,h,c){ctx.fillStyle=c;ctx.fillRect(Math.round(x),Math.round(y),Math.round(w),Math.round(h))}
function draw(){
ctx.save();if(shake>0){ctx.translate(rand(-shake,shake),rand(-shake,shake));shake=Math.max(0,shake-.5)}
// twilight skyline
let sky=ctx.createLinearGradient(0,0,0,H);sky.addColorStop(0,'#22264f');sky.addColorStop(.6,'#4b4267');sky.addColorStop(1,'#25263f');ctx.fillStyle=sky;ctx.fillRect(0,0,W,H);
ctx.fillStyle='#f3dca5';ctx.beginPath();ctx.arc(W*.79,H*.14,Math.min(35,W*.045),0,Math.PI*2);ctx.fill();
// distant buildings
for(let i=0;i<buildings.length;i++){let b=buildings[i],y=H*.42-b.h*.65;rect(b.x,y,b.w,b.h,b.c);rect(b.x-3,y,b.w+6,5,'#171b35');for(let wx=b.x+10;wx<b.x+b.w-8;wx+=18)for(let wy=y+13;wy<y+b.h-12;wy+=21)rect(wx,wy,7,10,((Math.floor(wx+wy+b.seed)%4)!==0)?'#e4a66b':'#41435e')}
// road and sidewalk
rect(0,H*.52,W,H*.12,'#44415a');rect(0,H*.64,W,H*.36,'#292b43');rect(0,H*.64,W,5,'#77718b');rect(0,H*.69,W,3,'#55536c');
for(let x=-(elapsed*25%130);x<W;x+=130)rect(x,H*.79,62,4,'#b3a0a0');
// debris / barricades
for(let i=0;i<8;i++){let x=(i*173+70)%W;rect(x,H*.87,22,7,'#55546a');rect(x+4,H*.855,11,6,'#77738a')}
// city sign
rect(18,H*.42,90,52,'#20233c');rect(18,H*.42,90,4,'#77718c');ctx.fillStyle='#c3a7a0';ctx.font='bold 12px monospace';ctx.fillText('STAY ALIVE',25,H*.46+10);
// draw bullets
for(let b of bullets){ctx.strokeStyle=b.color;ctx.lineWidth=b.r;ctx.beginPath();ctx.moveTo(b.x,b.y);ctx.lineTo(b.x-b.vx*.018,b.y-b.vy*.018);ctx.stroke()}
// zombies
for(let z of zombies){ctx.save();ctx.translate(z.x,z.y);ctx.rotate(z.face);rect(-10,-10,20,22,'#4d8a67');rect(-9,-15,18,15,'#6ea66f');rect(1,-10,4,3,'#f7e58b');rect(-7,-4,5,3,'#f7e58b');rect(0,1,8,3,'#451f2c');rect(-8,11,6,10,'#33374a');rect(3,11,6,10,'#33374a');rect(9,0,7,5,'#4d8a67');ctx.restore()}
// boss
if(boss){ctx.save();ctx.translate(boss.x,boss.y);ctx.rotate(boss.face);rect(-24,-27,48,53,'#4e795d');rect(-20,-38,40,28,'#6e9668');rect(-16,-28,7,5,'#fff06b');rect(7,-28,7,5,'#fff06b');rect(-10,-13,22,7,'#431b29');rect(-16,24,13,18,'#24263b');rect(5,24,13,18,'#24263b');rect(18,-10,16,10,'#4e795d');rect(-32,-4,13,17,'#4e795d');rect(-16,-8,10,5,'#8d383d');ctx.restore()}
// player faces target
ctx.save();ctx.translate(player.x,player.y);ctx.rotate(player.face);if(player.invuln>0&&Math.floor(elapsed*15)%2===0)ctx.globalAlpha=.5;
rect(-11,-12,22,25,'#cf3d4d');rect(-8,-17,16,13,'#e9b28b');rect(-7,-19,15,6,'#342b3d');rect(2,-13,3,3,'#1b2037');rect(-8,10,7,12,'#252a43');rect(3,10,7,12,'#252a43');rect(5,-4,22,6,power>0?'#ffd43b':'#c3c7d6');rect(20,-5,8,8,'#2b3047');ctx.restore();
for(let p of particles){ctx.globalAlpha=clamp(p.life*3,0,1);rect(p.x,p.y,4,4,p.color)}ctx.globalAlpha=1;
// subtle vignette
let vg=ctx.createRadialGradient(W/2,H/2,H*.2,W/2,H/2,H*.8);vg.addColorStop(0,'#00000000');vg.addColorStop(1,'#080a2099');ctx.fillStyle=vg;ctx.fillRect(0,0,W,H);
ctx.restore();
}
function loop(t){let dt=Math.min(.035,(t-last)/1000||0);last=t;update(dt);draw();requestAnimationFrame(loop)}
function start(){score=0;elapsed=0;spawn=0;boss=null;bossAt=38;power=0;bullets.length=0;zombies.length=0;particles.length=0;player.x=W*.45;player.y=H*.61;player.hp=100;player.invuln=0;gameOver=false;paused=false;running=true;$('health').textContent='♥ 100';$('score').textContent='POINTS: 0';$('weaponName').textContent='REGULAR GUN';$('overlay').style.display='none';$('bossbar').style.display='none';$('message').style.display='none';$('overlay').querySelector('h1').innerHTML='Bashbit <span>Apocalypse<br>Survival</span>'}
$('start').addEventListener('click',start);
$('pause').addEventListener('click',()=>{if(!running)return;paused=!paused;$('pause').textContent=paused?'▶':'Ⅱ';if(paused)say('PAUSED',100000);else $('message').style.display='none'});
window.addEventListener('keydown',e=>{keys[e.key]=true;keys[e.key.toLowerCase()]=true;if(['ArrowUp','ArrowDown','ArrowLeft','ArrowRight',' '].includes(e.key))e.preventDefault();if(e.key==='Escape'&&running){paused=!paused;$('pause').textContent=paused?'▶':'Ⅱ';$('message').style.display=paused?'block':'none';if(paused)$('message').textContent='PAUSED'}});
window.addEventListener('keyup',e=>{keys[e.key]=false;keys[e.key.toLowerCase()]=false});
canvas.addEventListener('pointermove',e=>{const r=canvas.getBoundingClientRect();mouse.x=e.clientX-r.left;mouse.y=e.clientY-r.top;if(e.pointerType==='mouse')player.face=Math.atan2(mouse.y-player.y,mouse.x-player.x)});
canvas.addEventListener('pointerdown',e=>{if(e.pointerType==='mouse'){mouse.down=true;player.face=Math.atan2(mouse.y-player.y,mouse.x-player.x)}});
window.addEventListener('pointerup',()=>{mouse.down=false;});
const stick=$('stick'),nub=$('nub');
function joyMove(e){const r=stick.getBoundingClientRect(),cx=r.left+r.width/2,cy=r.top+r.height/2;let dx=e.clientX-cx,dy=e.clientY-cy,max=r.width*.34,d=Math.hypot(dx,dy);if(d>max){dx*=max/d;dy*=max/d}joy.x=dx/max;joy.y=dy/max;nub.style.left=`calc(50% + ${dx}px)`;nub.style.top=`calc(50% + ${dy}px)`;if(Math.hypot(joy.x,joy.y)>.1)player.face=Math.atan2(joy.y,joy.x)}
stick.addEventListener('pointerdown',e=>{joy.active=true;stick.setPointerCapture(e.pointerId);joyMove(e)});
stick.addEventListener('pointermove',e=>{if(joy.active)joyMove(e)});
function joyEnd(){joy.active=false;joy.x=joy.y=0;nub.style.left='50%';nub.style.top='50%'}
stick.addEventListener('pointerup',joyEnd);stick.addEventListener('pointercancel',joyEnd);
$('fire').addEventListener('pointerdown',e=>{touchFire=true;$('fire').setPointerCapture(e.pointerId)});$('fire').addEventListener('pointerup',()=>touchFire=false);$('fire').addEventListener('pointercancel',()=>touchFire=false);
window.addEventListener('resize',resize);
resize();player.x=W*.45;player.y=H*.61;requestAnimationFrame(loop);
})();
</script>
</body></html>'''
path = Path("/mnt/data/bashbit_apocalypse_survival.html")
path.write_text(html, encoding="utf-8")
print(f"Created playable game: {path} ({path.stat().st_size:,} bytes)")
1
1
17KB
17KB
123.0ms
296.0ms
123.0ms