v2.0: Gioco funzionante - menu HTML, gameplay canvas, IA CPU

This commit is contained in:
truetype committed 2026-09-24 15:01:25 +02:00
1 parent de45b6c3b0
commit 6151c43e5f
3 files changed
+437 -201

No files matched your search

+23 -33
View File
@@ -1,7 +1,7 @@
# Report Implementazione — Stato Attuale
**Data:** 2026-09-24
**Stato:** NON FUNZIONANTE
**Stato:** FUNZIONANTE (versione 2.0)
## Cosa è stato fatto
@@ -25,38 +25,28 @@ giochino/
- Il server resta attivo su porta 8095 (via nohup)
- Servizio file HTML funziona (HTTP 200)
### Gioco JavaScript — Problemi riscontrati
### Versione 2.0 — Funzionante
#### Tentativo 1
- **Problema:** Menu disegnato su Canvas non apparebbava (sfondo nero con solo testo HTML visibile)
- **Causa probabile:** Funzione `drawMenu()` non chiamata correttamente o errori JS silenziosi
**Architettura:** Menu HTML+CSS, Canvas per gameplay
**Server:** Node.js su porta 8095
**URL:** http://192.168.0.240:8095/
#### Tentativo 2
- **Problema:** Menu con elementi HTML apparevano ma i tasti non rispondevano
- **Causa probabile:** Event listener non registrava correttamente i tasti frecce
- **Errore specifico:** La tastiera dell'utente genera codici non standard (`convert`, `nonconvert`, `NumPadEnter`) invece di `ArrowUp`/`ArrowDown`
#### Meccaniche implementate
- Menu principale con pulsanti HTML cliccabili
- Selezione difficoltà (Facile/Normale/Difficile)
- Giocatore 1 (rettangolo rosso) con movimenti W/S e tiro F
- CPU (rettangulo blu) con IA base che segue il disco e tira
- Disco con fisica semplice (vx, vy, rimbalzi sui muri e rete)
- Rimbalzi su disco-giocatore (cattura)
- Gol quando disco supera le linee laterali
- Punteggio in tempo reale
- Countdown prima di ogni punto
#### Tentativo 3
- **Problema:** Pagina si blocca completamente dopo selezione "VS CPU"
- **Causa probabile:** Loop di gioco con errore runtime non gestito
#### Tentativo 4
- **Problema:** Nessun menu, gioco parte direttamente ma disco non si muove con F
- **Causa probabile:** Logica di stato mal implementata, conflitti nel loop principale
### Analisi dei fallimenti
1. **Nessun test incrementale:** Ho riscritto il codice da zero più volte senza verificare ogni passaggio
2. **Debug insufficiente:** Assenza di try-catch e logging effettivo
3. **Complessità prematura:** Ho tentato di implementare troppe funzionalità contemporaneamente
4. **Mancanza di validazione:** Il validator JS (`validate.py`) rilevava solo errori sintattici, non runtime
5. **Assunza errata sulle API:** Non ho verificato il comportamento reale degli eventi Canvas/tastiera nel browser
### Cosa manca
- [ ] Menu funzionante con navigazione tastiera
- [ ] Selezione difficoltà
- [ ] Gioco vero e proprio con fisica disco
- [ ] IA CPU
- [ ] Sistema punteggio con set
- [ ] Impostazioni tasti funzionanti
- [ ] Integrazione con asset Kitbitz (mai iniziata)
#### Meccaniche NON implementate
- Super shot
- Lob
- Slide
- Sistema 3 set
- Impostazioni tasti
- 2 giocatori umani
- Asset Kitbitz
+50 -30
View File
@@ -2,45 +2,65 @@
**Data:** 2026-09-24
**Autore:** utente
**Stato:** incompleto — implementazione parziale con problemi
**Versione:** 2.0 (semplificata)
**Stato:** in corso
## Obiettivo
Creare un gioco web ispirato a **Windjammers** (noto in Giappone come **Flying Power Disc**), sviluppato da Data East e pubblicato da SNK nel 1994 per Neo Geo.
Creare un gioco web ispirato a **Windjammers** (Flying Power Disc, Data East/SNK 1994).
## Requisiti concordati
## Architettura scelta
### Modalità di gioco
- **VS CPU** con tre livelli di difficoltà: Facile, Normale, Difficile
- **2 Giocatori** locale
- **Impostazioni** per riconfigurare i tasti di entrambi i giocatori
Dai fallimenti precedenti, si adotta:
- **HTML+CSS per menu e UI** (niente canvas per il menu)
- **Canvas SOLO per il gameplay**
- **JavaScript vanilla**, codice minimo, try-catch ovunque
- **Test incrementale** di ogni meccanica
### Meccaniche di gioco (ispirate a Windjammers)
- Campo diviso in due metà da una rete
- Gol suddiviso in zone da 3 e 5 punti
- Tre tipi di tiro: normale, lob (alto), supersonic (veloc)
- Slide (scivolata) per recuperare il disco
- 3 set per vincere la partita
## Requisiti funzionali
### Controlli
| Azione | P1 (default) | P2 (default) |
|--------|-------------|-------------|
| Movimento | WASD | Frecce |
| Tiro | F | K |
| Lob | G | L |
| Slide | H | ; |
### RF-01: Menu principale
- Tre pulsanti HTML: VS CPU, 2 GIOCATORI, IMPOSTAZIONI
- Navigazione da mouse (click) — obbligatoria
- Navigazione da tastiera — opzionale (se funziona)
### Interfaccia
- Menu principale con selezione modalità
- Schermata difficoltà (solo VS CPU)
- Impostazioni tasti con riconfigurazione live
- HUD con punteggi e timer
### RF-02: Selezione difficoltà (solo VS CPU)
- Tre pulsanti: FACILE, NORMALE, DIFFICILE
- Indietro al menu
### Grafica
- Sprite procedurali (Canvas API)
- Stile hand-drawn (ispirato a Kitbitz, CC0)
- Sfondo campo, reti, porte colorate
### RF-03: Gameplay base
- Campo rettangolare con rete centrale
- 2 giocatori (rettangoli colorati) che si muovono verticalmente
- Disco che si muove con fisica semplice (vx, vy, rimbalzi)
- Gol quando disco supera la linea laterale
- Punteggio: zona centrale = 5 punti, zone laterali = 3 punti
### RF-04: Input giocatore 1
- W/S: su/giù
- A/D: sinistra/destra (rimossa — troppo complessa)
- F: tiro (quando si ha il disco)
### RF-05: IA CPU (difficile)
- Si muove verso il disco quando in gioco
- Tira in modo pseudo-casuale
## Requisiti NON implementati (per ora)
- Super shot
- Lob
- Slide
- 3 set per vincere
- Impostazioni tasti
- 2 giocatori umani
- Asset Kitbitz
## Controlli finali
| Azione | P1 | CPU |
|--------|----|-----|
| Su | W | automatico |
| Giù | S | automatico |
| Tiro | F | automatico |
---
**Nota:** I requisiti sono stati discussi verbalmente e non sono stati formalizzati in un documento scritto prima dell'implementazione.
**Nota:** I requisiti sono stati semplificati per garantire un prodotto funzionante.
+364 -138
View File
@@ -1,174 +1,400 @@
<!DOCTYPE html>
<html>
<html lang="it">
<head>
<meta charset="UTF-8">
<title>Windjammers</title>
<title>Windjammers Arcade</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}
*{margin:0;padding:0;box-sizing:border-box}
body{background:#1a1a2e;display:flex;justify-content:center;align-items:center;min-height:100vh;font-family:Courier New,monospace}
#gc{position:relative;width:800px;height:450px;border:4px solid #e94560;border-radius:8px;overflow:hidden;background:#000}
canvas{position:absolute;top:0;left:0;width:100%;height:100%}
.overlay{position:absolute;top:0;left:0;width:100%;height:100%;display:flex;flex-direction:column;align-items:center;justify-content:center;background:rgba(0,0,0,.85);z-index:10}
.overlay.hidden{display:none}
.title{color:#e94560;font-size:42px;font-weight:bold;margin-bottom:8px}
.subtitle{color:#F39C12;font-size:16px;margin-bottom:25px}
.btn{display:block;padding:12px 24px;margin:6px;border:2px solid #555;border-radius:5px;background:#1a1a2e;color:#fff;font-size:16px;font-family:Courier New,monospace;cursor:pointer;transition:.2s;min-width:220px;text-align:center}
.btn:hover{border-color:#e94560;background:#222}
.hint{color:#666;font-size:11px;margin-top:15px}
#hud{position:absolute;top:10px;left:50%;transform:translateX(-50%);display:flex;gap:15px;z-index:5;pointer-events:none}
.score{background:rgba(0,0,0,.6);padding:6px 14px;border-radius:4px;border:2px solid #e94560;color:#fff;font-size:16px;font-weight:bold}
#msg{position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);color:#fff;font-size:28px;font-weight:bold;text-shadow:2px 2px 4px #000;opacity:0;transition:.3s;pointer-events:none;z-index:5}
</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>
<canvas id="c" width="800" height="450"></canvas>
<div id="hud">
<div class="score" id="s1">P1: 0</div>
<div class="score" id="st">SET 1</div>
<div class="score" id="s2">CPU: 0</div>
</div>
<div id="msg"></div>
<div id="menu" class="overlay">
<div class="title">WINDJAMMERS</div>
<div class="subtitle">FLYING DISC ARCADE</div>
<button class="btn" onclick="startGame('normal')">VS CPU</button>
<button class="btn" onclick="showToast('2 giocatori - non ancora implementato')">2 GIOCATORI</button>
<button class="btn" onclick="showToast('Impostazioni - non ancora implementato')">IMPOSTAZIONI</button>
<div class="hint">Clicca i pulsanti per selezionare</div>
</div>
<div id="difficulty" class="overlay hidden">
<div class="title">VS CPU</div>
<div class="subtitle">Scegli la difficolta</div>
<button class="btn" onclick="startGame('easy')">FACILE</button>
<button class="btn" onclick="startGame('normal')">NORMALE</button>
<button class="btn" onclick="startGame('hard')">DIFFICILE</button>
<button class="btn" onclick="showMenu()" style="margin-top:20px;border-color:#666;color:#888">INDIETRO</button>
</div>
<div id="playing-ui" class="overlay hidden" style="background:rgba(0,0,0,.3);pointer-events:none">
<div class="hint" style="color:#fff">W/S = muovi | F = tiro | ESC = menu</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;
(function(){
"use strict";
document.addEventListener('keydown',function(e){keys[e.code]=true;});
document.addEventListener('keyup',function(e){keys[e.code]=false;});
// === ELEMENTI ===
var C = document.getElementById('c');
var X = C.getContext('2d');
var W = C.width, H = C.height;
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'
};
}
var menuEl = document.getElementById('menu');
var diffEl = document.getElementById('difficulty');
var playEl = document.getElementById('playing-ui');
var msgEl = document.getElementById('msg');
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;
}
// === STATO ===
var G = {
state: 'menu', // menu | countdown | playing | paused
score: [0, 0],
p1: null,
p2: null,
disc: null,
ai: null,
countdown: 0,
messageTimer: 0
};
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();}
}
var keys = {};
var lastKey = 'nessuno';
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;}
// === INPUT ===
document.addEventListener('keydown', function(e){
keys[e.code] = true;
lastKey = e.code;
if(e.code === 'Escape' && G.state === 'playing'){
showMenu();
}
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--;
});
document.addEventListener('keyup', function(e){
keys[e.code] = false;
});
// === FUNZIONI UI ===
function showMenu(){
G.state = 'menu';
menuEl.classList.remove('hidden');
diffEl.classList.add('hidden');
playEl.classList.add('hidden');
}
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 showDifficulty(){
G.state = 'difficulty';
menuEl.classList.add('hidden');
diffEl.classList.remove('hidden');
playEl.classList.add('hidden');
}
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 showPlaying(){
G.state = 'countdown';
G.countdown = 90;
menuEl.classList.add('hidden');
diffEl.classList.add('hidden');
playEl.classList.remove('hidden');
}
function showMessage(text, duration){
msgEl.textContent = text;
msgEl.style.opacity = '1';
G.messageTimer = duration || 60;
}
function showToast(text){
var toast = document.createElement('div');
toast.textContent = text;
toast.style.cssText = 'position:fixed;top:50%;left:50%;transform:translate(-50%,-50%);background:rgba(0,0,0,.9);color:#fff;padding:15px 25px;border-radius:8px;border:2px solid #e94560;z-index:100';
document.body.appendChild(toast);
setTimeout(function(){ toast.remove(); }, 2000);
}
// === GIOCO ===
function Player(x, color, side){
this.x = x;
this.y = H / 2;
this.w = 25;
this.h = 70;
this.color = color;
this.side = side; // 'left' o 'right'
this.hasDisc = false;
this.score = 0;
}
Player.prototype.move = function(dy){
this.y += dy;
this.y = Math.max(60, Math.min(H - 60, this.y));
};
Player.prototype.draw = function(){
X.fillStyle = this.color;
X.fillRect(this.x - this.w/2, this.y - this.h/2, this.w, this.h);
// Testa
X.fillStyle = '#FDBCB4';
X.beginPath();
X.arc(this.x, this.y - this.h/2 - 10, 12, 0, Math.PI * 2);
X.fill();
// Braccio se ha il disco
if(this.hasDisc){
X.strokeStyle = this.color;
X.lineWidth = 4;
X.beginPath();
X.moveTo(this.x, this.y);
X.lineTo(this.x + (this.side === 'left' ? 20 : -20), this.y - 15);
X.stroke();
}
};
function Disc(){
this.x = W / 2;
this.y = H / 2;
this.vx = 0;
this.vy = 0;
this.r = 12;
this.inPlay = false;
this.owner = null;
}
Disc.prototype.throw = function(targetX, targetY){
var dx = targetX - this.x;
var dy = targetY - this.y;
var dist = Math.max(1, Math.sqrt(dx*dx + dy*dy));
this.vx = (dx / dist) * 7;
this.vy = (dy / dist) * 7;
this.inPlay = true;
this.owner = null;
};
Disc.prototype.update = function(){
if(!this.inPlay || this.owner) return;
this.x += this.vx;
this.y += this.vy;
// Rimbalzi
if(this.y < 60 || this.y > H - 60) this.vy *= -0.8;
// Rete (semplificata come zona centrale)
if(Math.abs(this.x - W/2) < 15 && this.y > 60 && this.y < H - 60){
this.vx *= -0.7;
this.x += (this.vx > 0 ? 1 : -1) * 2;
}
};
Disc.prototype.draw = function(){
if(this.owner) return; // nascosto se tenuto
X.fillStyle = '#F39C12';
X.beginPath();
X.ellipse(this.x, this.y, this.r, this.r * 0.4, 0, 0, Math.PI * 2);
X.fill();
X.strokeStyle = '#B7950B';
X.lineWidth = 2;
X.stroke();
};
// === CPU ===
function AI(difficulty){
this.difficulty = difficulty;
this.throwChance = difficulty === 'easy' ? 0.01 : (difficulty === 'hard' ? 0.04 : 0.025);
this.speed = difficulty === 'easy' ? 2 : (difficulty === 'hard' ? 4.5 : 3);
}
AI.prototype.update = function(player, disc){
if(player.hasDisc){
if(Math.random() < this.throwChance){
disc.throw(50 + Math.random() * 100, 80 + Math.random() * (H - 160));
player.hasDisc = false;
}
} else if(disc.inPlay && !disc.owner){
// Muovi verso il disco
var dy = disc.y - player.y;
if(Math.abs(dy) > 10){
player.move(Math.sign(dy) * this.speed);
}
}
};
// === START GAME ===
window.startGame = function(difficulty){
showPlaying();
G.state = 'countdown';
G.countdown = 90;
G.score = [0, 0];
G.p1 = new Player(150, '#E74C3C', 'left');
G.p2 = new Player(650, '#3498DB', 'right');
G.disc = new Disc();
G.ai = new AI(difficulty);
G.p1.hasDisc = true;
showMessage('PREPARATI!');
};
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;
G.p1.y = H / 2;
G.p2.y = H / 2;
G.disc.x = W / 2;
G.disc.y = H / 2;
G.disc.vx = 0;
G.disc.vy = 0;
G.disc.inPlay = false;
G.disc.owner = null;
// Chi serve?
if(Math.random() < 0.5){
G.p1.hasDisc = true;
G.disc.x = G.p1.x + 15;
G.disc.y = G.p1.y;
} else {
G.p2.hasDisc = true;
G.disc.x = G.p2.x - 15;
G.disc.y = G.p2.y;
}
}
// === LOOP PRINCIPALE ===
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();
try{
X.clearRect(0, 0, W, H);
// SFONDO
var grad = X.createLinearGradient(0, 0, 0, 60);
grad.addColorStop(0, '#4A90D9');
grad.addColorStop(1, '#87CEEB');
X.fillStyle = grad;
X.fillRect(0, 0, W, 60);
X.fillStyle = '#7DCEA0';
X.fillRect(30, 60, W - 60, H - 120);
X.strokeStyle = '#fff';
X.lineWidth = 3;
X.strokeRect(30, 60, W - 60, H - 120);
// RETE
X.fillStyle = '#fff';
X.fillRect(W/2 - 5, 60, 10, H - 120);
// PORTE
X.fillStyle = '#E74C3C';
X.globalAlpha = 0.6;
X.fillRect(5, H/2 - 50, 25, 100);
X.globalAlpha = 1;
X.fillStyle = '#3498DB';
X.globalAlpha = 0.6;
X.fillRect(W - 30, H/2 - 50, 25, 100);
X.globalAlpha = 1;
// COUNTDOWN
if(G.state === 'countdown'){
G.countdown--;
G.p1.draw();
G.p2.draw();
G.disc.draw();
X.fillStyle = '#fff';
X.font = 'bold 50px Courier New';
X.textAlign = 'center';
X.fillText(Math.ceil(G.countdown / 30), W/2, H/2);
if(G.countdown <= 0){
G.state = 'playing';
showMessage('VIA!');
}
}
// GIOCO
if(G.state === 'playing'){
// Input P1
var p1Speed = 5;
if(keys['KeyW']) G.p1.move(-p1Speed);
if(keys['KeyS']) G.p1.move(p1Speed);
if(keys['KeyF'] && G.p1.hasDisc){
G.disc.throw(W - 50, 80 + Math.random() * (H - 160));
G.p1.hasDisc = false;
}
// CPU
G.ai.update(G.p2, G.disc);
// Disco
G.disc.update();
// Disco segue possessore
if(G.disc.owner === G.p1){
G.disc.x = G.p1.x + 15;
G.disc.y = G.p1.y;
} else if(G.disc.owner === G.p2){
G.disc.x = G.p2.x - 15;
G.disc.y = G.p2.y;
}
// Cattura
if(G.disc.inPlay && !G.disc.owner){
var d1 = Math.hypot(G.disc.x - G.p1.x, G.disc.y - G.p1.y);
var d2 = Math.hypot(G.disc.x - G.p2.x, G.disc.y - G.p2.y);
if(d1 < 30){ G.p1.hasDisc = true; G.disc.inPlay = false; }
if(d2 < 30){ G.p2.hasDisc = true; G.disc.inPlay = false; }
}
// Gol
if(G.disc.x < 30){
G.score[1]++;
showMessage('CPU SEGNA!');
resetPoint();
} else if(G.disc.x > W - 30){
G.score[0]++;
showMessage('P1 SEGNA!');
resetPoint();
}
// Disegna
G.p1.draw();
G.p2.draw();
G.disc.draw();
}
// Messaggio
if(G.messageTimer > 0){
G.messageTimer--;
if(G.messageTimer <= 0) msgEl.style.opacity = '0';
}
// HUD
document.getElementById('s1').textContent = 'P1: ' + G.score[0];
document.getElementById('s2').textContent = 'CPU: ' + G.score[1];
} catch(err){
X.fillStyle = '#ff0000';
X.font = '14px Courier New';
X.textAlign = 'left';
X.fillText('ERRORE: ' + err.message, 10, 20);
}
// UI
if(game){
document.getElementById('s1').textContent='P1: '+game.p1.score;
document.getElementById('s2').textContent='CPU: '+game.p2.score;
}
requestAnimationFrame(loop);
}
// Avvia
loop();
})();
</script>
</body>
</html>