| import streamlit as st |
| import streamlit.components.v1 as components |
|
|
| st.set_page_config(page_title="Void Dash", page_icon="⚡", layout="wide") |
|
|
| st.markdown(""" |
| <style> |
| body, .stApp { background:#05070f !important; } |
| #MainMenu, footer, header { visibility:hidden; } |
| .block-container { padding:0.5rem 1rem; } |
| h1 { color:#a99ff7; font-family:monospace; text-align:center; margin:0; } |
| p { color:#7a7f94; text-align:center; font-family:monospace; font-size:13px; } |
| </style> |
| """, unsafe_allow_html=True) |
|
|
| st.markdown("<h1>⚡ VOID DASH</h1>", unsafe_allow_html=True) |
| st.markdown("<p>Tap / Space = jump | Hold = glide | Grab cyan boost | Dodge alien bullets!</p>", unsafe_allow_html=True) |
|
|
| GAME_HTML = """ |
| <!DOCTYPE html> |
| <html> |
| <head> |
| <style> |
| * { margin:0; padding:0; box-sizing:border-box; } |
| body { background:#05070f; display:flex; flex-direction:column; align-items:center; } |
| canvas { display:block; border-radius:12px; cursor:pointer; } |
| #hud { |
| display:flex; justify-content:space-between; align-items:center; |
| width:600px; padding:6px 10px; font-family:monospace; font-size:13px; |
| color:#ccc; background:#0a0c14; border-radius:0 0 10px 10px; |
| } |
| #lives-bar span { display:inline-block; width:10px; height:10px; border-radius:50%; background:#f76464; margin:0 2px; } |
| #shield-bar { width:70px; height:6px; background:#222; border-radius:3px; overflow:hidden; display:inline-block; vertical-align:middle; margin-left:4px; } |
| #shield-fill { height:100%; background:#3ecfcf; width:0%; border-radius:3px; } |
| #wrap { position:relative; } |
| #msg { |
| position:absolute; top:50%; left:50%; transform:translate(-50%,-50%); |
| color:#fff; text-align:center; pointer-events:none; min-width:220px; |
| font-family:monospace; |
| } |
| #msg h2 { font-size:22px; font-weight:500; margin:0 0 6px; } |
| #msg p { font-size:12px; color:#888; margin:2px 0; } |
| .tag { display:inline-block; font-size:11px; padding:2px 8px; border-radius:10px; margin:2px; } |
| </style> |
| </head> |
| <body> |
| <div id="wrap"> |
| <canvas id="gc" width="600" height="360"></canvas> |
| <div id="msg"> |
| <h2 style="color:#a99ff7">VOID DASH</h2> |
| <p>Tap / Space = jump | Hold = glide</p> |
| <p style="margin-top:8px;"> |
| <span class="tag" style="background:#1a3a1a;color:#3ecf8e">BOOST = speed + shield</span> |
| <span class="tag" style="background:#3a1a1a;color:#f76464">ALIENS fire bullets!</span> |
| </p> |
| <p style="margin-top:6px;color:#7c6af7;font-size:13px;">Tap to start</p> |
| </div> |
| </div> |
| <div id="hud"> |
| <span>Score: <b id="sc">0</b> | Best: <b id="hi">0</b></span> |
| <span> |
| Lives: <span id="lives-bar"></span> |
| Shield: <span id="shield-bar"><span id="shield-fill"></span></span> |
| </span> |
| <span id="boost-txt" style="color:#3ecf8e;font-size:12px;min-width:60px;text-align:right;"></span> |
| </div> |
| |
| <script> |
| const C=document.getElementById('gc'), ctx=C.getContext('2d'); |
| const W=C.width, H=C.height; |
| const GRAV=0.38,JUMP=-7.2,GLIDE=0.14,PIPE_W=44,GAP=128,PIPE_SPD=2.8,PIPE_INT=185; |
| const MAX_LIVES=3; |
| let state='idle',frame=0,score=0,best=0,held=false; |
| let bird,pipes,enemies,bullets,particles,powerups; |
| let lives,shield,boostTimer,invTimer; |
| |
| function initLivesUI(){ |
| const b=document.getElementById('lives-bar'); b.innerHTML=''; |
| for(let i=0;i<MAX_LIVES;i++) b.appendChild(Object.assign(document.createElement('span'),{id:'life'+i})); |
| } |
| function updateLivesUI(){ for(let i=0;i<MAX_LIVES;i++){ const s=document.getElementById('life'+i); if(s) s.style.background=i<lives?'#f76464':'#333'; } } |
| function updateShieldUI(){ document.getElementById('shield-fill').style.width=Math.round(shield)+'%'; } |
| function updateBoostUI(){ document.getElementById('boost-txt').textContent=boostTimer>0?'BOOST '+Math.ceil(boostTimer/60)+'s':''; } |
| |
| function init(){ |
| bird={x:110,y:H/2,vy:0,r:10,trail:[]}; |
| pipes=[];enemies=[];bullets=[];particles=[];powerups=[]; |
| frame=0;score=0;lives=MAX_LIVES;shield=0;boostTimer=0;invTimer=0; |
| document.getElementById('sc').textContent=0; |
| updateLivesUI();updateShieldUI();updateBoostUI(); |
| } |
| |
| function jump(){ |
| if(state==='idle'||state==='dead'){state='play';document.getElementById('msg').style.display='none';init();} |
| if(state==='play') bird.vy=JUMP; |
| held=true; |
| } |
| function release(){ held=false; } |
| C.addEventListener('mousedown',jump); C.addEventListener('mouseup',release); |
| C.addEventListener('touchstart',e=>{e.preventDefault();jump();},{passive:false}); |
| C.addEventListener('touchend',e=>{e.preventDefault();release();},{passive:false}); |
| window.addEventListener('keydown',e=>{if(e.code==='Space'){e.preventDefault();jump();}}); |
| window.addEventListener('keyup',e=>{if(e.code==='Space')release();}); |
| |
| function rnd(a,b){return a+Math.random()*(b-a);} |
| function hsl(h,s,l){return `hsl(${h},${s}%,${l}%)`;} |
| |
| function spawnPipe(){ const top=55+Math.random()*(H-GAP-95); pipes.push({x:W+10,top,scored:false}); } |
| function spawnEnemy(){ enemies.push({x:W+20,y:rnd(30,H-30),vy:rnd(-0.6,0.6),r:14,shootTimer:rnd(40,100),hp:2,frame:0}); } |
| function spawnPowerup(){ powerups.push({x:W+10,y:rnd(50,H-50),type:Math.random()<0.6?'boost':'heart',r:12,bob:Math.random()*Math.PI*2}); } |
| function spawnParticles(x,y,color,n=8){ for(let i=0;i<n;i++){ const a=Math.random()*Math.PI*2,spd=rnd(1.5,4.5); particles.push({x,y,vx:Math.cos(a)*spd,vy:Math.sin(a)*spd,life:1,color}); } } |
| |
| function drawBird(b,t){ |
| for(let i=0;i<b.trail.length;i++){ |
| const pt=b.trail[i],a=(i/b.trail.length)*0.3; |
| ctx.beginPath();ctx.arc(pt.x,pt.y,b.r*(i/b.trail.length)*0.7,0,Math.PI*2); |
| ctx.fillStyle=`hsla(${(t*1.5+i*5)%360},90%,65%,${a})`;ctx.fill(); |
| } |
| if(boostTimer>0){ctx.beginPath();ctx.arc(b.x,b.y,b.r*2.2,0,Math.PI*2);ctx.fillStyle=`rgba(62,207,207,${0.15+0.1*Math.sin(t*0.3)})`;ctx.fill();} |
| if(invTimer>0&&Math.floor(invTimer/4)%2===0) return; |
| ctx.beginPath();ctx.arc(b.x,b.y,b.r,0,Math.PI*2);ctx.fillStyle=boostTimer>0?'#3ecfcf':'#fff';ctx.fill(); |
| ctx.beginPath();ctx.arc(b.x-3,b.y-3,b.r*0.38,0,Math.PI*2);ctx.fillStyle='rgba(0,0,0,0.25)';ctx.fill(); |
| } |
| |
| function drawEnemy(e,t){ |
| e.frame++; |
| const bob=Math.sin(e.frame*0.05)*3,bx=e.x,by=e.y+bob; |
| ctx.save();ctx.translate(bx,by); |
| ctx.beginPath();ctx.ellipse(0,0,e.r*1.3,e.r,0,0,Math.PI*2); |
| ctx.fillStyle=hsl((t*0.8)%360,80,35);ctx.fill(); |
| ctx.strokeStyle=hsl((t*0.8+30)%360,90,60);ctx.lineWidth=1.5;ctx.stroke(); |
| ctx.fillStyle=hsl((t*1.2)%360,100,70); |
| for(let i=-1;i<=1;i+=2){ctx.beginPath();ctx.arc(i*5,-3,3,0,Math.PI*2);ctx.fill();} |
| ctx.strokeStyle='#f76464';ctx.lineWidth=1; |
| for(let i=0;i<3;i++){ctx.beginPath();ctx.moveTo(-e.r*1.3+i*e.r*0.7,-e.r);ctx.lineTo(-e.r*1.3+i*e.r*0.7+3,-e.r-6);ctx.stroke();} |
| ctx.restore(); |
| for(let h=0;h<2;h++){ctx.fillStyle=h<e.hp?'#f76464':'#333';ctx.fillRect(e.x-10+h*12,e.y+bob-e.r-10,10,4);} |
| } |
| |
| function drawPowerup(p,t){ |
| p.bob+=0.06;const y=p.y+Math.sin(p.bob)*5; |
| ctx.save();ctx.translate(p.x,y); |
| if(p.type==='boost'){ |
| ctx.beginPath();ctx.moveTo(0,-p.r);ctx.lineTo(p.r*0.8,p.r*0.5);ctx.lineTo(-p.r*0.8,p.r*0.5);ctx.closePath(); |
| ctx.fillStyle='rgba(62,207,207,0.9)';ctx.fill();ctx.strokeStyle='#fff';ctx.lineWidth=1;ctx.stroke(); |
| ctx.fillStyle='#fff';ctx.font='bold 9px monospace';ctx.textAlign='center';ctx.fillText('B',0,4); |
| } else { |
| ctx.beginPath();ctx.arc(0,0,p.r,0,Math.PI*2);ctx.fillStyle='rgba(247,100,100,0.85)';ctx.fill(); |
| ctx.strokeStyle='#fff';ctx.lineWidth=1;ctx.stroke(); |
| ctx.fillStyle='#fff';ctx.font='bold 11px monospace';ctx.textAlign='center';ctx.fillText('+',0,4); |
| } |
| ctx.restore(); |
| } |
| |
| function drawBullet(bl){ |
| ctx.beginPath();ctx.ellipse(bl.x,bl.y,6,3,Math.atan2(bl.vy,bl.vx),0,Math.PI*2); |
| ctx.fillStyle='#f7c948';ctx.fill(); |
| ctx.beginPath();ctx.arc(bl.x,bl.y,2,0,Math.PI*2);ctx.fillStyle='#fff';ctx.fill(); |
| } |
| |
| function drawPipe(p,t){ |
| const g1=ctx.createLinearGradient(p.x,0,p.x+PIPE_W,0); |
| g1.addColorStop(0,hsl((t*0.6)%360,60,25));g1.addColorStop(1,hsl((t*0.6+40)%360,60,40)); |
| ctx.fillStyle=g1; |
| ctx.beginPath();ctx.roundRect(p.x,0,PIPE_W,p.top,[0,0,8,8]);ctx.fill(); |
| ctx.beginPath();ctx.roundRect(p.x,p.top+GAP,PIPE_W,H-p.top-GAP,[8,8,0,0]);ctx.fill(); |
| ctx.strokeStyle=hsl((t*1.1)%360,90,60);ctx.lineWidth=1.2;ctx.stroke(); |
| } |
| |
| function drawGrid(t){ |
| ctx.strokeStyle='rgba(80,60,200,0.06)';ctx.lineWidth=0.5; |
| const spd=boostTimer>0?PIPE_SPD*2:PIPE_SPD,off=(t*spd*0.35)%40; |
| for(let x=-off;x<W;x+=40){ctx.beginPath();ctx.moveTo(x,0);ctx.lineTo(x,H);ctx.stroke();} |
| for(let y=0;y<H;y+=40){ctx.beginPath();ctx.moveTo(0,y);ctx.lineTo(W,y);ctx.stroke();} |
| } |
| function drawStars(t){ |
| for(let i=0;i<55;i++){ |
| const sx=((i*137+t*0.25)%W),sy=((i*97)%H),a=0.15+0.25*Math.sin(t*0.04+i); |
| ctx.fillStyle=`rgba(255,255,255,${a})`;ctx.fillRect(sx,sy,1.5,1.5); |
| } |
| } |
| function drawParticles(){ |
| for(let i=particles.length-1;i>=0;i--){ |
| const p=particles[i]; |
| ctx.globalAlpha=p.life;ctx.beginPath();ctx.arc(p.x,p.y,3*p.life,0,Math.PI*2); |
| ctx.fillStyle=p.color;ctx.fill();ctx.globalAlpha=1; |
| p.x+=p.vx;p.y+=p.vy;p.vx*=0.92;p.vy*=0.92;p.life-=0.04; |
| if(p.life<=0) particles.splice(i,1); |
| } |
| } |
| |
| function circle(ax,ay,ar,bx,by,br){return Math.hypot(ax-bx,ay-by)<ar+br;} |
| function collidesPipe(b,p){return b.x+b.r>p.x&&b.x-b.r<p.x+PIPE_W&&(b.y-b.r<p.top||b.y+b.r>p.top+GAP);} |
| |
| function takeDamage(x,y){ |
| if(invTimer>0) return; |
| if(shield>=30){shield=Math.max(0,shield-30);updateShieldUI();spawnParticles(bird.x,bird.y,'#3ecfcf',6);return;} |
| lives--;updateLivesUI();invTimer=90;spawnParticles(x,y,'#f76464',10); |
| if(lives<=0) die(); |
| } |
| |
| function die(){ |
| state='dead'; |
| const msg=document.getElementById('msg');msg.style.display=''; |
| msg.innerHTML=`<h2 style="color:#f76464">DESTROYED</h2><p>Score: ${score} | Best: ${best}</p><p style="margin-top:6px;color:#7c6af7;font-size:13px;">Tap / Space to retry</p>`; |
| } |
| |
| function loop(){ |
| requestAnimationFrame(loop); |
| const t=frame; |
| ctx.clearRect(0,0,W,H);ctx.fillStyle='#05070f';ctx.fillRect(0,0,W,H); |
| drawGrid(t);drawStars(t); |
| |
| if(state==='idle'){ |
| const iy=H/2+Math.sin(t*0.06)*12; |
| const g=ctx.createRadialGradient(110,iy,1,110,iy,18); |
| g.addColorStop(0,hsl(t*2%360,90,65));g.addColorStop(1,'transparent'); |
| ctx.beginPath();ctx.arc(110,iy,18,0,Math.PI*2);ctx.fillStyle=g;ctx.fill(); |
| ctx.beginPath();ctx.arc(110,iy,10,0,Math.PI*2);ctx.fillStyle='#fff';ctx.fill(); |
| frame++;return; |
| } |
| |
| if(state==='play'){ |
| frame++; |
| const spd=boostTimer>0?PIPE_SPD*1.8:PIPE_SPD; |
| if(boostTimer>0){boostTimer--;if(frame%60===0||boostTimer===0) updateBoostUI();} |
| if(invTimer>0) invTimer--; |
| |
| bird.vy+=held?GRAV-GLIDE:GRAV; |
| bird.vy=Math.max(-10,Math.min(bird.vy,12)); |
| bird.y+=bird.vy; |
| bird.trail.push({x:bird.x,y:bird.y}); |
| if(bird.trail.length>16) bird.trail.shift(); |
| |
| if(frame%PIPE_INT===0) spawnPipe(); |
| if(frame%220===0) spawnEnemy(); |
| if(frame%300===0) spawnPowerup(); |
| |
| for(let i=pipes.length-1;i>=0;i--){ |
| pipes[i].x-=spd; |
| if(!pipes[i].scored&&pipes[i].x+PIPE_W<bird.x){ |
| pipes[i].scored=true;score++; |
| document.getElementById('sc').textContent=score; |
| if(score>best){best=score;document.getElementById('hi').textContent=best;} |
| } |
| if(pipes[i].x+PIPE_W<-10){pipes.splice(i,1);continue;} |
| drawPipe(pipes[i],t); |
| if(collidesPipe(bird,pipes[i])) takeDamage(bird.x,bird.y); |
| } |
| |
| for(let i=enemies.length-1;i>=0;i--){ |
| const e=enemies[i]; |
| e.x-=spd*0.55;e.y+=e.vy; |
| if(e.y<20||e.y>H-20) e.vy*=-1; |
| e.shootTimer--; |
| if(e.shootTimer<=0){ |
| const dx=bird.x-e.x,dy=bird.y-e.y,dist=Math.hypot(dx,dy),s2=4.5; |
| bullets.push({x:e.x-e.r,y:e.y,vx:(dx/dist)*s2,vy:(dy/dist)*s2}); |
| e.shootTimer=rnd(60,110);spawnParticles(e.x,e.y,'#f7c948',3); |
| } |
| if(e.x<-30){enemies.splice(i,1);continue;} |
| drawEnemy(e,t); |
| if(circle(bird.x,bird.y,bird.r,e.x,e.y,e.r*1.2)) takeDamage(e.x,e.y); |
| } |
| |
| for(let i=bullets.length-1;i>=0;i--){ |
| const bl=bullets[i];bl.x+=bl.vx;bl.y+=bl.vy; |
| if(bl.x<-10||bl.x>W+10||bl.y<-10||bl.y>H+10){bullets.splice(i,1);continue;} |
| drawBullet(bl); |
| if(circle(bird.x,bird.y,bird.r,bl.x,bl.y,5)){bullets.splice(i,1);takeDamage(bl.x,bl.y);} |
| } |
| |
| for(let i=powerups.length-1;i>=0;i--){ |
| const p=powerups[i];p.x-=spd*0.5; |
| if(p.x<-20){powerups.splice(i,1);continue;} |
| drawPowerup(p,t); |
| if(circle(bird.x,bird.y,bird.r,p.x,p.y+Math.sin(p.bob)*5,p.r)){ |
| if(p.type==='boost'){boostTimer=300;shield=Math.min(100,shield+40);spawnParticles(p.x,p.y,'#3ecfcf',12);updateShieldUI();updateBoostUI();} |
| else{lives=Math.min(MAX_LIVES,lives+1);spawnParticles(p.x,p.y,'#f76464',10);updateLivesUI();} |
| powerups.splice(i,1); |
| } |
| } |
| |
| drawParticles();drawBird(bird,t); |
| |
| if(bird.y-bird.r<0||bird.y+bird.r>H){takeDamage(bird.x,bird.y);bird.vy*=-0.5;bird.y=Math.max(bird.r,Math.min(H-bird.r,bird.y));} |
| |
| ctx.fillStyle=`hsla(${t%360},80%,80%,0.85)`; |
| ctx.font='500 26px monospace';ctx.textAlign='center'; |
| ctx.fillText(score,W/2,40);ctx.textAlign='left'; |
| |
| if(boostTimer>0){ctx.fillStyle='rgba(62,207,207,0.7)';ctx.font='bold 11px monospace';ctx.textAlign='right';ctx.fillText('BOOST',W-12,20);ctx.textAlign='left';} |
| } |
| } |
| |
| initLivesUI();init();loop(); |
| </script> |
| </body> |
| </html> |
| """ |
|
|
| components.html(GAME_HTML, height=430, scrolling=False) |
|
|
| st.markdown(""" |
| <div style="text-align:center;margin-top:8px;"> |
| <span style="background:#1a3a1a;color:#3ecf8e;padding:3px 10px;border-radius:10px;font-size:12px;font-family:monospace;margin:4px;"> |
| Cyan triangle = BOOST (speed + shield) |
| </span> |
| <span style="background:#3a1a1a;color:#f76464;padding:3px 10px;border-radius:10px;font-size:12px;font-family:monospace;margin:4px;"> |
| Red circle = +1 Life |
| </span> |
| <span style="background:#1a1a3a;color:#a99ff7;padding:3px 10px;border-radius:10px;font-size:12px;font-family:monospace;margin:4px;"> |
| Aliens fire aimed bullets! |
| </span> |
| </div> |
| """, unsafe_allow_html=True) |