VOIDDASH / src /streamlit_app.py
dwishank's picture
Update src/streamlit_app.py
e60ea2c verified
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 &nbsp;|&nbsp; Hold = glide &nbsp;|&nbsp; Grab cyan boost &nbsp;|&nbsp; 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 &nbsp;|&nbsp; 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> &nbsp;|&nbsp; Best: <b id="hi">0</b></span>
<span>
Lives: <span id="lives-bar"></span>
&nbsp; 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} &nbsp;|&nbsp; 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)