175 lines
5.2 KiB
HTML
175 lines
5.2 KiB
HTML
<!DOCTYPE html>
|
|
<html>
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<title>Windjammers</title>
|
|
<style>
|
|
body{background:#1a1a2e;display:flex;justify-content:center;align-items:center;min-height:100vh;margin:0;font-family:Courier}
|
|
#gc{width:900px;height:500px;border:3px solid #e94560;border-radius:8px;position:relative;overflow:hidden;background:#000}
|
|
canvas{width:100%;height:100%}
|
|
#ui{position:absolute;top:10px;left:50%;transform:translateX(-50%);display:flex;gap:20px;pointer-events:none}
|
|
.sb{background:rgba(0,0,0,.7);padding:8px 16px;border-radius:4px;border:2px solid #e94560;color:#fff;font-size:18px;font-weight:bold}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<div id="gc">
|
|
<canvas id="c" width="900" height="500"></canvas>
|
|
<div id="ui">
|
|
<div class="sb" id="s1">P1: 0</div>
|
|
<div class="sb" id="st">SET 1</div>
|
|
<div class="sb" id="s2">CPU: 0</div>
|
|
</div>
|
|
</div>
|
|
<script>
|
|
var C=document.getElementById('c');
|
|
var X=C.getContext('2d');
|
|
var W=C.width,H=C.height;
|
|
var keys={};
|
|
var game=null;
|
|
|
|
document.addEventListener('keydown',function(e){keys[e.code]=true;});
|
|
document.addEventListener('keyup',function(e){keys[e.code]=false;});
|
|
|
|
function initGame(){
|
|
game={
|
|
p1:{x:150,y:250,vx:0,vy:0,has:false,tc:0,score:0,color:'#E74C3C'},
|
|
p2:{x:750,y:250,vx:0,vy:0,has:false,tc:0,score:0,color:'#3498DB'},
|
|
disc:{x:450,y:250,vx:0,vy:0,owner:null,inPlay:false},
|
|
mode:'menu'
|
|
};
|
|
}
|
|
|
|
function drawField(){
|
|
// Sky
|
|
var g=X.createLinearGradient(0,0,0,80);g.addColorStop(0,'#4A90D9');g.addColorStop(1,'#87CEEB');
|
|
X.fillStyle=g;X.fillRect(0,0,W,80);
|
|
// Court
|
|
X.fillStyle='#7DCEA0';X.fillRect(50,80,W-100,H-160);
|
|
X.strokeStyle='#fff';X.lineWidth=3;X.strokeRect(50,80,W-100,H-160);
|
|
// Net
|
|
X.fillStyle='#fff';X.fillRect(446,80,8,H-160);
|
|
// Goals
|
|
X.fillStyle='#E74C3C';X.globalAlpha=0.6;X.fillRect(15,180,35,140);X.globalAlpha=1;
|
|
X.fillStyle='#3498DB';X.globalAlpha=0.6;X.fillRect(850,180,35,140);X.globalAlpha=1;
|
|
}
|
|
|
|
function drawPlayer(p){
|
|
// Body
|
|
X.fillStyle=p.color;X.beginPath();X.ellipse(p.x,p.y,18,25,0,0,Math.PI*2);X.fill();
|
|
// Head
|
|
X.fillStyle='#FDBCB4';X.beginPath();X.arc(p.x,p.y-15,12,0,Math.PI*2);X.fill();
|
|
// Arms if has disc
|
|
if(p.has){X.strokeStyle=p.color;X.lineWidth=5;X.beginPath();X.moveTo(p.x,p.y);X.lineTo(p.x+(p.x<450?25:-25),p.y-10);X.stroke();}
|
|
}
|
|
|
|
function drawDisc(){
|
|
if(game.disc.owner)return; // hidden when held
|
|
X.fillStyle='#F39C12';X.beginPath();X.ellipse(game.disc.x,game.disc.y,14,6,0,0,Math.PI*2);X.fill();
|
|
X.strokeStyle='#B7950B';X.lineWidth=2;X.stroke();
|
|
}
|
|
|
|
function updatePlayer(p,speed){
|
|
p.vx=0;p.vy=0;
|
|
if(speed===undefined)speed=4;
|
|
if(p===game.p1){
|
|
if(keys['KeyW'])p.vy=-speed;
|
|
if(keys['KeyS'])p.vy=speed;
|
|
if(keys['KeyA'])p.vx=-speed;
|
|
if(keys['KeyD'])p.vx=speed;
|
|
if(keys['KeyF']&&p.has&&p.tc<=0){throwDisc(p,850);p.tc=30;}
|
|
}else{
|
|
// AI
|
|
if(p.has&&Math.random()<0.02){throwDisc(p,50);}
|
|
else if(game.disc.inPlay){var dx=game.disc.x-p.x,dy=game.disc.y-p.y,d=Math.hypot(dx,dy);if(d>40){p.vx=dx/d*3.5;p.vy=dy/d*3.5;}}
|
|
else{p.vx*=0.9;p.vy*=0.9;}
|
|
}
|
|
p.x+=p.vx;p.y+=p.vy;
|
|
p.x=Math.max(60,Math.min(840,p.x));
|
|
p.y=Math.max(90,Math.min(410,p.y));
|
|
// Keep in own half
|
|
if(p===game.p1&&p.x>430)p.x=430;
|
|
if(p===game.p2&&p.x<470)p.x=470;
|
|
if(p.tc>0)p.tc--;
|
|
}
|
|
|
|
function throwDisc(p,targetX){
|
|
var tx=targetX+(Math.random()-0.5)*150;
|
|
var ty=100+Math.random()*300;
|
|
game.disc.owner=null;
|
|
game.disc.inPlay=true;
|
|
var dx=tx-p.x,dy=ty-p.y,d=Math.hypot(dx,dy);
|
|
game.disc.vx=dx/d*8;
|
|
game.disc.vy=dy/d*8;
|
|
game.disc.x=p.x;
|
|
game.disc.y=p.y;
|
|
p.has=false;
|
|
}
|
|
|
|
function updateDisc(){
|
|
var d=game.disc;
|
|
if(d.owner){d.x=d.owner.x+(d.owner===game.p1?20:-20);d.y=d.owner.y;return;}
|
|
if(!d.inPlay)return;
|
|
d.x+=d.vx;d.y+=d.vy;
|
|
// Bounce walls
|
|
if(d.x<60||d.x>840)d.vx*=-0.8;
|
|
if(d.y<90||d.y>410)d.vy*=-0.8;
|
|
// Net
|
|
if(Math.abs(d.x-450)<10)d.vx*=-0.7;
|
|
// Catch
|
|
[game.p1,game.p2].forEach(function(p){
|
|
if(p.has||p.tc>0)return;
|
|
var dist=Math.hypot(d.x-p.x,d.y-p.y);
|
|
if(dist<35){p.has=true;d.inPlay=false;d.owner=p;}
|
|
});
|
|
// Goals
|
|
if(d.x<50){game.p2.score++;resetPoint();}
|
|
if(d.x>850){game.p1.score++;resetPoint();}
|
|
}
|
|
|
|
function resetPoint(){
|
|
game.disc.inPlay=false;
|
|
game.disc.owner=null;
|
|
game.disc.x=450;game.disc.y=250;
|
|
game.disc.vx=0;game.disc.vy=0;
|
|
game.p1.x=150;game.p1.y=250;game.p1.has=false;
|
|
game.p2.x=750;game.p2.y=250;game.p2.has=false;
|
|
// Serve to random player
|
|
if(Math.random()<0.5)game.p1.has=true;
|
|
else game.p2.has=true;
|
|
}
|
|
|
|
function loop(){
|
|
X.clearRect(0,0,W,H);
|
|
drawField();
|
|
|
|
if(game===null){
|
|
initGame();
|
|
X.fillStyle='#e94560';X.font='bold 40px Courier';X.textAlign='center';
|
|
X.fillText('WINDJAMMERS',W/2,150);
|
|
X.fillStyle='#F39C12';X.font='16px Courier';
|
|
X.fillText('FLYING DISC ARCADE',W/2,190);
|
|
X.fillStyle='#fff';X.font='14px Courier';
|
|
X.fillText('Premi SPAZIO per iniziare',W/2,300);
|
|
if(keys['Space']){game.p1.has=true;game.mode='playing';}
|
|
}else if(game.mode==='playing'){
|
|
updatePlayer(game.p1);
|
|
updatePlayer(game.p2);
|
|
updateDisc();
|
|
drawPlayer(game.p1);
|
|
drawPlayer(game.p2);
|
|
drawDisc();
|
|
}
|
|
|
|
// UI
|
|
if(game){
|
|
document.getElementById('s1').textContent='P1: '+game.p1.score;
|
|
document.getElementById('s2').textContent='CPU: '+game.p2.score;
|
|
}
|
|
requestAnimationFrame(loop);
|
|
}
|
|
|
|
loop();
|
|
</script>
|
|
</body>
|
|
</html>
|