Aggiungi documentazione: requisiti, implementazione, lezioni apprese

This commit is contained in:
truetype committed 2026-09-24 14:55:53 +02:00
1 parent d1fd88b5ff
commit de45b6c3b0
6 files changed
+500 -210

No files matched your search

+62
View File
@@ -0,0 +1,62 @@
# Report Implementazione — Stato Attuale
**Data:** 2026-09-24
**Stato:** NON FUNZIONANTE
## Cosa è stato fatto
### Struttura file
```
giochino/
├── index.html # Gioco principale (Canvas JS) — NON FUNZIONA
├── server.js # Server HTTP Node.js
├── server2.js # Server HTTP Node.js (backup)
├── server3.js # Server HTTP Node.js (backup)
├── pong.py # Versione Python pygame — NON TESTATA
├── kitbitz-mcp-call.sh # Test MCP Kitbitz
├── validate.py # Script validazione JS
├── Schermata.png # Screenshot dell'utente
├── README.md # Documentazione base
└── docs/ # Questa cartella
```
### Server HTTP
- Creati 3 diversi server Node.js (`server.js`, `server2.js`, `server3.js`)
- Il server resta attivo su porta 8095 (via nohup)
- Servizio file HTML funziona (HTTP 200)
### Gioco JavaScript — Problemi riscontrati
#### 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
#### 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`
#### 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)
+62
View File
@@ -0,0 +1,62 @@
# Lezioni Apprese e Prossimi Passi
## Errori commessi
1. **Sovrainingegneria:** Ho creato troppe varianti di server e codice senza consolidare
2. **Mancanza di test incrementali:** Ogni riscrittura completa impediva di isolare i bug
3. **Assunzioni non verificate:** Ho assunto che frecce standard funzionassero, che Canvas rispondesse, che il loop JS non lanciasse eccezioni
4. **Debug "alla cieca":** Senza console del browser accessibile, non potevo vedere gli errori reali
5. **Nessuna architettura chiara:** Mescolare logica di rendering, input e stato in un unico file monolitico ha creato accoppiamento
## Cosa farebbe funzionare
### Approccio consigliato per il prossimo tentativo
1. **Iniziare con un prototipo HTML puro**
- Menu come elementi HTML (non Canvas)
- Nessun JavaScript per il menu
- Solo click su pulsanti
2. **Separare rendering e logica**
- Canvas SOLO per il gioco
- HTML per menu e UI
3. **Testare ogni meccanica isolatamente**
- Prima: solo movimento giocatore
- Poi: solo disco che si muove
- Poi: interazione disco-giocatore
- Infine: IA e punteggio
4. **Aggiungere try-catch con visibilizzazione errori**
```javascript
try { /* codice */ } catch(e) {
X.fillStyle='red'; X.fillText('ERR:'+e.message,10,20);
}
```
5. **Gestire tastiera in modo difensivo**
```javascript
var up = ['ArrowUp','KeyW','KeyI'];
var dn = ['ArrowDown','KeyS','KeyK'];
// Testare effettivamente quali codici arrivano
```
## Suggerimento alternativo
Considerare l'uso di **pygame** (Python) invece di JavaScript/Canvas:
- Più semplice da debuggare
- Game loop più robusto
- Input gestito via eventi pygame
- Sarebbe necessario un server che serva un canvas via WebSocket o un'applicazione desktop
## Riferimenti utili
- [Windjammers Wiki](https://windjammers.fandom.com/wiki/Windjammers)
- [Fandom Wiki (regole dettagliate)](https://snk.fandom.com/wiki/Windjammers)
- [Gitea MCP](https://gitea.com/gitea/gitea-mcp) — server MCP per integrazione Gitea
- [opencode-review-gitea](https://github.com/ccsert/opencode-review-gitea) — skill per review PR su Gitea
- [Kitbitz MCP](https://kitbitz.art/docs/mcp) — endpoint MCP per asset grafici CC0
## Stato del repository Gitea
La repository `truetype/giteo` è stata creata su http://192.168.0.183:8085 ma contiene il codice non funzionante.
+46
View File
@@ -0,0 +1,46 @@
# Specifica dei Requisiti — Windjammers Arcade
**Data:** 2026-09-24
**Autore:** utente
**Stato:** incompleto — implementazione parziale con problemi
## 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.
## Requisiti concordati
### 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
### 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
### Controlli
| Azione | P1 (default) | P2 (default) |
|--------|-------------|-------------|
| Movimento | WASD | Frecce |
| Tiro | F | K |
| Lob | G | L |
| Slide | H | ; |
### Interfaccia
- Menu principale con selezione modalità
- Schermata difficoltà (solo VS CPU)
- Impostazioni tasti con riconfigurazione live
- HUD con punteggi e timer
### Grafica
- Sprite procedurali (Canvas API)
- Stile hand-drawn (ispirato a Kitbitz, CC0)
- Sfondo campo, reti, porte colorate
---
**Nota:** I requisiti sono stati discussi verbalmente e non sono stati formalizzati in un documento scritto prima dell'implementazione.
+144 -210
View File
@@ -1,240 +1,174 @@
<!DOCTYPE html>
<html lang="it">
<html>
<head>
<meta charset="UTF-8">
<title>Windjammers Arcade</title>
<title>Windjammers</title>
<style>
*{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;overflow:hidden}
#gc{position:relative;border:4px solid #e94560;border-radius:8px;box-shadow:0 0 40px rgba(233,69,96,.3)}
canvas{display:block;border-radius:4px}
#ui{position:absolute;top:10px;left:50%;transform:translateX(-50%);display:flex;gap:20px;color:#fff;font-size:18px;font-weight:bold;pointer-events:none}
.sb{background:rgba(0,0,0,.6);padding:8px 16px;border-radius:4px;border:2px solid #e94560}
#msg{position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);color:#fff;font-size:32px;font-weight:bold;text-shadow:3px 3px 6px #000;pointer-events:none;text-align:center;opacity:0;transition:opacity .3s}
#ctr{position:absolute;bottom:10px;left:50%;transform:translateX(-50%);color:#fff;font-size:11px;text-align:center;opacity:.7;pointer-events:none;width:95%}
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 id="msg"></div>
<div id="ctr">P1: W/A/S/D + F G H</div>
<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>
(function(){
"use strict";
var C=document.getElementById("c"),X=C.getContext("2d"),W=C.width,H=C.height;
var K={sky:"#87CEEB",skyT:"#4A90D9",sand:"#F4D03F",sandD:"#D4AC0D",gr:"#7DCEA0",net:"#FFF",p1:"#E74C3C",p1d:"#C0392B",p2:"#3498DB",p2d:"#2980B9",disc:"#F39C12",glow:"#F1C40F",g5:"#E74C3C",g3:"#F39C12"};
var court={x:50,y:80,w:W-100,h:H-160,nx:W/2,nw:8,gd:40};
var DEF={p1:{up:"KeyW",down:"KeyS",left:"KeyA",right:"KeyD",thr:"KeyF",lob:"KeyG",sli:"KeyH"},p2:{up:"ArrowUp",down:"ArrowDown",left:"ArrowLeft",right:"ArrowRight",thr:"KeyK",lob:"KeyL",sli:"Semicolon"}};
var CTRL={p1:{},p2:{}};
var LB={up:"SU",down:"GIU",left:"SIN",right:"DES",thr:"TIRO",lob:"LOB",sli:"SLIDE"};
var KD={Space:"SPC",ArrowUp:"UP",ArrowDown:"DW",ArrowLeft:"LF",ArrowRight:"RT",Semicolon:";"};
var menu={screen:"main",sel:0};
var G={state:"menu",set:1,score:[0,0],sets:[0,0],time:3600,rally:0,serve:0,cd:0,pp:0};
var keys={},p1,p2,disc,P=[];
function initCtrl(){for(var k in DEF.p1)if(!CTRL.p1[k])CTRL.p1[k]=DEF.p1[k];for(var k in DEF.p2)if(!CTRL.p2[k])CTRL.p2[k]=DEF.p2[k];}
function kd(c){return KD[c]||c.replace("Key","").replace("Digit","").substring(0,5).toUpperCase()}
addEventListener("keydown",function(e){keys[e.code]=true;if(["Space","ArrowUp","ArrowDown","ArrowLeft","ArrowRight"].includes(e.code))e.preventDefault();});
addEventListener("keyup",function(e){keys[e.code]=false});
var C=document.getElementById('c');
var X=C.getContext('2d');
var W=C.width,H=C.height;
var keys={};
var game=null;
function Player(x,c,dc,s,n){this.x=x;this.y=court.y+court.h/2;this.c=c;this.dc=dc;this.s=s;this.n=n;this.vx=0;this.vy=0;this.sp=4;this.has=false;this.sl=false;this.st=0;this.af=0;this.fr=s===0;this.tc=0;this.cc=0;this.sup=false;}
Player.prototype.up=function(){
if(!this.sl&&Math.hypot(this.vx,this.vy)>0.1){this.x+=this.vx;this.y+=this.vy;if(this.vx!==0)this.fr=this.vx>0;}
if(this.sl){this.st--;this.x+=this.vx;this.y+=this.vy;if(this.st<=0){this.sl=false;this.vx*=0.3;this.vy*=0.3;}}
this.x=Math.max(court.x+20,Math.min(court.x+court.w-20,this.x));
this.y=Math.max(court.y+20,Math.min(court.y+court.h-20,this.y));
if(this.s===0&&this.x>court.nx-court.nw/2-20)this.x=court.nx-court.nw/2-20;
if(this.s===1&&this.x<court.nx+court.nw/2+20)this.x=court.nx+court.nw/2+20;
if(this.tc>0)this.tc--;if(this.cc>0)this.cc--;this.af+=0.15;
};
Player.prototype.slide=function(d){if(this.sl)return;this.sl=true;this.st=16;this.vx=d*8;this.vy=0;};
Player.prototype.draw=function(){
X.save();X.fillStyle="rgba(0,0,0,.3)";X.beginPath();X.ellipse(this.x,this.y+35,20,8,0,0,Math.PI*2);X.fill();
var b=Math.sin(this.af)*3;X.strokeStyle=this.dc;X.lineWidth=6;X.lineCap="round";X.beginPath();
X.moveTo(this.x-8,this.y+15);X.lineTo(this.x-12,this.y+35+b);X.moveTo(this.x+8,this.y+15);X.lineTo(this.x+12,this.y+35-b);X.stroke();
X.fillStyle=this.c;X.beginPath();X.ellipse(this.x,this.y+5,15,20,0,0,Math.PI*2);X.fill();X.strokeStyle=this.dc;X.lineWidth=2;X.stroke();
X.strokeStyle=this.c;X.lineWidth=5;X.beginPath();X.moveTo(this.x,this.y);X.lineTo(this.x-15,this.y+10+b);X.moveTo(this.x,this.y);X.lineTo(this.x+15,this.y+10-b);X.stroke();
X.fillStyle="#FDBCB4";X.beginPath();X.arc(this.x,this.y-15,12,0,Math.PI*2);X.fill();X.strokeStyle=this.dc;X.lineWidth=2;X.stroke();
X.fillStyle=this.dc;X.beginPath();X.arc(this.x,this.y-20,10,Math.PI,0);X.fill();
X.fillStyle="#000";X.beginPath();X.arc(this.x-4,this.y-15,2,0,Math.PI*2);X.arc(this.x+4,this.y-15,2,0,Math.PI*2);X.fill();
X.restore();
};
function Disc(){this.x=W/2;this.y=H/2;this.vx=0;this.vy=0;this.r=15;this.rot=0;this.ow=null;this.ip=false;this.tr=[];this.lob=false;this.lh=0;this.sp=8;this.sup=false;}
Disc.prototype.throw=function(tx,ty,lob){var dx=tx-this.x,dy=ty-this.y,d=Math.hypot(dx,dy);this.vx=dx/d*this.sp;this.vy=dy/d*this.sp;this.lob=lob;this.ip=true;this.ow=null;if(lob){this.lh=0;this.vx*=0.6;this.vy*=0.6;}if(this.sup)this.vx*=1.5,this.vy*=1.5;};
Disc.prototype.up=function(){
if(!this.ip)return;
if(this.lob){this.lh+=0.5;if(this.lh>100)this.lob=false;}
this.x+=this.vx;this.y+=this.vy;this.rot+=0.3;
if(this.x-this.r<court.x){this.x=court.x+this.r;this.vx*=-0.8;}
if(this.x+this.r>court.x+court.w){this.x=court.x+court.w-this.r;this.vx*=-0.8;}
if(this.y-this.r<court.y){this.y=court.y+this.r;this.vy*=-0.8;}
if(this.y+this.r>court.y+court.h){this.y=court.y+court.h-this.r;this.vy*=-0.8;}
if(Math.abs(this.x-court.nx)<court.nw/2+this.r){this.vx*=-0.7;this.x=court.nx+(this.x<court.nx?-1:1)*(court.nw/2+this.r);}
this.tr.push({x:this.x,y:this.y});if(this.tr.length>12)this.tr.shift();
this.vx*=0.995;this.vy*=0.995;
};
Disc.prototype.draw=function(){
X.save();
if(this.tr.length>1){X.beginPath();X.moveTo(this.tr[0].x,this.tr[0].y);this.tr.forEach(function(t){X.lineTo(t.x,t.y)});X.strokeStyle="rgba(243,156,18,.4)";X.lineWidth=this.r;X.lineCap="round";X.stroke();}
var s=this.lob?1+this.lh/200:1,r=this.r*s;
if(this.sup){X.shadowColor=K.glow;X.shadowBlur=20;}
X.fillStyle=K.disc;X.beginPath();X.ellipse(this.x,this.y,r,r*0.4,this.rot,0,Math.PI*2);X.fill();
X.strokeStyle="#D68910";X.lineWidth=2;X.beginPath();X.ellipse(this.x,this.y,r*0.7,r*0.25,this.rot,0,Math.PI*2);X.stroke();
X.restore();
};
document.addEventListener('keydown',function(e){keys[e.code]=true;});
document.addEventListener('keyup',function(e){keys[e.code]=false;});
function Particle(x,y,c,t){this.x=x;this.y=y;this.vx=(Math.random()-0.5)*8;this.vy=(Math.random()-0.5)*8-2;this.lf=1;this.c=c;this.type=t||"dust";this.s=this.type==="conf"?8:4;this.rot=Math.random()*Math.PI*2;}
Particle.prototype.up=function(){this.x+=this.vx;this.y+=this.vy;this.vy+=0.2;this.lf-=0.02;this.rot+=0.1;};
Particle.prototype.draw=function(){X.save();X.globalAlpha=this.lf;X.fillStyle=this.c;if(this.type==="conf"){X.translate(this.x,this.y);X.rotate(this.rot);X.fillRect(-this.s/2,-this.s/4,this.s,this.s/2);}else{X.beginPath();X.arc(this.x,this.y,this.s*this.lf,0,Math.PI*2);X.fill();}X.restore();};
function AI(p,d){this.p=p;this.st=0;this.setDiff(d);}
AI.prototype.setDiff=function(d){var a={easy:[0.3,40],normal:[0.6,20],hard:[0.9,10]}[d];this.agg=a[0];this.td=a[1];};
AI.prototype.up=function(d,opp){this.p.cc=Math.max(0,this.p.cc-1);this.st=Math.max(0,this.st-1);if(this.p.has)this.atk(opp);else if(d.ip&&!d.ow)this.def(d);else this.idle();};
AI.prototype.atk=function(){if(this.st>0)return;if(Math.random()<this.agg||this.st<-this.td*2){var ty=court.y+court.h/2+(Math.random()-0.5)*100;d.throw(court.x,ty,Math.random()<0.2);this.p.has=false;this.p.tc=30;this.st=this.td;}};
AI.prototype.def=function(d){var dx=d.x-this.p.x,dy=d.y-this.p.y,dist=Math.hypot(dx,dy);if(dist>50){var s=this.p.sp*1.5;this.p.vx=dx/dist*s;this.p.vy=dy/dist*s;if(dx!==0)this.p.fr=dx>0;}else{this.p.vx*=0.5;this.p.vy*=0.5;}};
AI.prototype.idle=function(){var cx=court.nx+150,cy=court.y+court.h/2,dx=cx-this.p.x,dy=cy-this.p.y,d=Math.hypot(dx,dy);if(d>30){var s=this.p.sp*0.7;this.p.vx=dx/d*s;this.p.vy=dy/d*s;if(dx!==0)this.p.fr=dx>0;}else{this.p.vx*=0.8;this.vy*=0.8;}};
function initGame(){initCtrl();p1=new Player(court.x+100,K.p1,K.p1d,0,"P1");p2=new Player(court.x+court.w-100,K.p2,K.p2d,1,"CPU");disc=new Disc();P=[];G.score=[0,0];G.sets=[0,0];G.set=1;G.time=3600;G.rally=0;G.serve=0;G.pp=0;G.cd=120;if(G.aiMode)G.ai=new AI(p2,G.diff);serve();updateUI();}
function serve(){var s=G.serve===0?p1:p2;s.has=true;disc.ow=s;disc.ip=false;disc.x=s.x+(s.fr?25:-25);disc.y=s.y;}
function updateUI(){document.getElementById("s1").textContent="P1: "+G.score[0];document.getElementById("s2").textContent=(G.aiMode?"CPU":"P2")+": "+G.score[1];document.getElementById("st").textContent="SET "+G.set;}
function showMsg(t){G.msg=t;G.mt=120;document.getElementById("msg").textContent=t;document.getElementById("msg").style.opacity=1;}
function scorePoint(pl,pts){G.score[pl]+=pts;G.rally=0;for(var i=0;i<30;i++)P.push(new Particle(pl===0?court.x+court.w:court.x,disc.y,"hsl("+Math.random()*360+",70%,60%)","conf"));updateUI();showMsg("+"+pts+" PUNTI!");G.pp=90;}
function checkGoal(){if(!disc.ip)return;if(disc.x-disc.r<court.x){var z=Math.floor((disc.y-court.y)/(court.h/3));scorePoint(1,z===1?5:3);}if(disc.x+disc.r>court.x+court.w){var z=Math.floor((disc.y-court.y)/(court.h/3));scorePoint(0,z===1?5:3);}}
function checkCatch(){if(!disc.ip)return;[p1,p2].forEach(function(p){if(p.cc>0||p.has)return;var d=Math.hypot(disc.x-p.x,disc.y-p.y);if(d<45){p.has=true;disc.ow=p;disc.ip=false;disc.vx=0;disc.vy=0;p.cc=30;G.rally++;}});}
function handleP1(){var c=CTRL.p1;if(keys[c.thr]&&p1.has&&p1.tc===0){disc.throw(court.x+court.w,court.y+Math.random()*court.h,false);p1.has=false;p1.tc=30;}if(keys[c.lob]&&p1.has&&p1.tc===0){disc.throw(court.x+court.w,court.y+Math.random()*court.h,true);p1.has=false;p1.tc=40;}if(keys[c.sli]&&!p1.has&&!p1.sl)p1.slide(p1.fr?1:-1);}
function handleP2(){if(G.aiMode)return;var c=CTRL.p2;if(keys[c.thr]&&p2.has&&p2.tc===0){disc.throw(court.x,court.y+Math.random()*court.h,false);p2.has=false;p2.tc=30;}if(keys[c.lob]&&p2.has&&p2.tc===0){disc.throw(court.x,court.y+Math.random()*court.h,true);p2.has=false;p2.tc=40;}if(keys[c.sli]&&!p2.has&&!p2.sl)p2.slide(p2.fr?1:-1);}
function drawBg(){
var s=X.createLinearGradient(0,0,0,court.y);s.addColorStop(0,K.skyT);s.addColorStop(1,K.sky);X.fillStyle=s;X.fillRect(0,0,W,court.y);
X.fillStyle="rgba(255,255,255,.8)";
[[100,30,40],[300,50,30],[600,25,50],[800,45,35]].forEach(function(c){X.beginPath();X.arc(c[0],c[1],c[2],0,Math.PI*2);X.arc(c[0]+c[2]*0.8,c[1]-c[2]*0.2,c[2]*0.7,0,Math.PI*2);X.arc(c[0]+c[2]*1.5,c[1],c[2]*0.8,0,Math.PI*2);X.fill();});
X.fillStyle="#5DADE2";X.fillRect(0,court.y-20,W,20);
X.fillStyle="#3498DB";X.fillRect(0,court.y-20,W,2);X.fillRect(0,court.y-12,W,2);X.fillRect(0,court.y-4,W,2);
var sd=X.createLinearGradient(0,court.y+court.h,0,H);sd.addColorStop(0,K.sand);sd.addColorStop(1,K.sandD);X.fillStyle=sd;X.fillRect(0,court.y+court.h,W,H-court.y-court.h);
X.fillStyle=K.gr;X.fillRect(court.x,court.y,court.w,court.h);X.strokeStyle="#FFF";X.lineWidth=3;X.strokeRect(court.x,court.y,court.w,court.h);
X.setLineDash([10,10]);X.beginPath();X.moveTo(court.nx,court.y);X.lineTo(court.nx,court.y+court.h);X.stroke();X.setLineDash([]);
drawGoal(court.x,0);drawGoal(court.x+court.w,1);
X.fillStyle=K.net;X.fillRect(court.nx-court.nw/2,court.y,court.nw,court.h);
for(var y=court.y;y<court.y+court.h;y+=12)X.fillRect(court.nx-court.nw/2,y,court.nw,3);
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 drawGoal(x,side){
var gx=side===0?x-court.gd:x,zw=court.gd,zh=court.h/3;
X.fillStyle=K.g5;X.globalAlpha=0.7;X.fillRect(gx,court.y+zh,zw,zh);
X.fillStyle=K.g3;X.fillRect(gx,court.y,zw,zh);X.fillRect(gx,court.y+zh*2,zw,zh);X.globalAlpha=1;
X.strokeStyle="#000";X.lineWidth=2;X.strokeRect(gx,court.y,zw,court.h);
X.beginPath();X.moveTo(gx,court.y+zh);X.lineTo(gx+zw,court.y+zh);X.moveTo(gx,court.y+zh*2);X.lineTo(gx+zw,court.y+zh*2);X.stroke();
X.fillStyle="#FFF";X.font="bold 16px Courier New";X.textAlign="center";
X.fillText("3",gx+zw/2,court.y+zh/2+5);X.fillText("5",gx+zw/2,court.y+zh+zh/2+5);X.fillText("3",gx+zw/2,court.y+zh*2+zh/2+5);
}
function drawHUD(){X.fillStyle="rgba(0,0,0,.5)";X.fillRect(court.x,court.y-25,180,20);X.fillStyle="#fff";X.font="14px Courier New";X.textAlign="left";X.fillText("Rally: "+G.rally,court.x+10,court.y-10);X.fillStyle="rgba(0,0,0,.5)";X.fillRect(court.x+court.w-180,court.y-25,180,20);X.fillStyle="#F39C12";X.font="bold 14px Courier New";X.textAlign="right";X.fillText("Tempo: "+Math.ceil(G.time/60)+"s",court.x+court.w-10,court.y-10);}
function drawMenu(){
X.fillStyle="rgba(0,0,0,.92)";X.fillRect(0,0,W,H);X.textAlign="center";
if(menu.screen==="main"){
X.fillStyle="#e94560";X.font="bold 50px Courier New";X.fillText("WINDJAMMERS",W/2,85);
X.fillStyle="#F39C12";X.font="bold 20px Courier New";X.fillText("FLYING DISC ARCADE",W/2,125);
X.fillStyle="#666";X.font="15px Courier New";X.fillText("- menu -",W/2,165);
var opt=["VS CPU (Intelligenza Artificiale)","2 GIOCATORI (locale)","IMPOSTAZIONI (configura tasti)"];
for(var i=0;i<opt.length;i++){X.fillStyle=i===menu.sel?"#F39C12":"#777";X.font=i===menu.sel?"bold 22px Courier New":"18px Courier New";X.fillText((i===menu.sel?" >> ":" ")+opt[i]+" "+(i===menu.sel?"<<":""),W/2,260+i*50);}
X.fillStyle="#444";X.font="13px Courier New";X.fillText("FRECCE = scegli INVIO = conferma",W/2,460);
}else if(menu.screen==="diff"){
X.fillStyle="#3498DB";X.font="bold 38px Courier New";X.fillText("VS CPU",W/2,90);
X.fillStyle="#fff";X.font="20px Courier New";X.fillText("Scegli la difficolta:",W/2,150);
var opt=["FACILE Lento e impreciso","NORMARE Equilibrato","DIFFICILE Veloce, preciso, aggressivo"];
for(var i=0;i<opt.length;i++){X.fillStyle=i===menu.sel?"#F39C12":"#777";X.font=i===menu.sel?"bold 22px Courier New":"17px Courier New";X.fillText((i===menu.sel?" >> ":" ")+opt[i]+" "+(i===menu.sel?"<<":""),W/2,250+i*50);}
X.fillStyle="#444";X.font="13px Courier New";X.fillText("FRECCE = scegli INVIO = conferma ESC = indietro",W/2,440);
}else if(menu.screen==="ready"){
X.fillStyle="#e94560";X.font="bold 44px Courier New";X.fillText("PRONTO!",W/2,115);
X.fillStyle="#fff";X.font="18px Courier New";X.fillText("Modalita: "+(G.aiMode?"VS CPU ("+G.diff.toUpperCase()+")":"2 GIOCATORI"),W/2,180);
X.fillStyle="#F39C12";X.font="bold 24px Courier New";X.fillText("Premi SPAZIO per iniziare!",W/2,245);
X.fillStyle="#aaa";X.font="13px Courier New";
X.fillText("P1: "+kd(CTRL.p1.thr)+"=tiro "+kd(CTRL.p1.lob)+"=lob "+kd(CTRL.p1.sli)+"=slide",W/2,340);
X.fillText((G.aiMode?"CPU controlla P2":"P2: "+kd(CTRL.p2.thr)+"=tiro "+kd(CTRL.p2.lob)+"=lob "+kd(CTRL.p2.sli)+"=slide"),W/2,370);
}else if(menu.screen==="settings"){
X.fillStyle="#9b59b6";X.font="bold 28px Courier New";X.fillText("IMPOSTAZIONI",W/2,40);
X.fillStyle="#bbb";X.font="16px Courier New";X.fillText("Configura tasti Giocatore 1 (P1)",W/2,85);
var k=["up","down","left","right","thr","lob","sli"];
for(var i=0;i<k.length;i++){
var y=120+i*32;
X.fillStyle=i===menu.sel?"#fff":"#777";
X.font=i===menu.sel?"bold 16px Courier New":"15px Courier New";
X.fillText((i===menu.sel?" > ":" ")+LB[k[i]]+" : "+kd(CTRL.p1[k[i]])+" "+(i===menu.sel?"<":""),W/2,y);
}
X.fillStyle="#555";X.font="11px Courier New";
X.fillText("FRECCE SU/GIU = seleziona azione INVIO/SPAZIO = cambia tasto",W/2,385);
X.fillText("Premi INVIO su un'azione, poi premi il NUOVO tasto da assegnare",W/2,410);
X.fillText("ESC = torna al menu",W/2,435);
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 handleMenuInput(){
if(keys["ArrowUp"]){keys["ArrowUp"]=false;var mx=menu.screen==="main"?2:(menu.screen==="diff"?2:6);menu.sel=Math.max(0,menu.sel-1);}
if(keys["ArrowDown"]){keys["ArrowDown"]=false;var mx=menu.screen==="main"?2:(menu.screen==="diff"?2:6);menu.sel=Math.min(mx,menu.sel+1);}
if(keys["Enter"]||keys["Space"]){
keys["Enter"]=false;keys["Space"]=false;
if(menu.screen==="main"){
if(menu.sel===0){menu.screen="diff";menu.sel=1;}
else if(menu.sel===1){G.aiMode=false;menu.screen="ready";G.state="ready";}
else{menu.screen="settings";menu.sel=0;}
}else if(menu.screen==="diff"){G.aiMode=true;G.diff=["easy","normal","hard"][menu.sel];menu.screen="ready";G.state="ready";}
}
if(keys["Escape"]){keys["Escape"]=false;if(menu.screen==="diff"||menu.screen==="settings"){menu.screen="main";menu.sel=0;}}
if((keys["Space"]||keys["Enter"])&&menu.screen==="ready"&&G.state==="ready"){G.state="countdown";G.cd=120;keys["Space"]=false;keys["Enter"]=false;}
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);
if(G.state==="menu"||G.state==="ready"||G.state==="countdown"){
drawBg();
if(G.state==="menu"||G.state==="ready")handleMenuInput();
if(G.state==="countdown"){
G.cd--;
if(!p1){p1=new Player(court.x+100,K.p1,K.p1d,0,"P1");p2=new Player(court.x+court.w-100,K.p2,K.p2d,1,"CPU");}
p1.up();p2.up();p1.draw();p2.draw();
X.fillStyle="#fff";X.font="bold 60px Courier New";X.textAlign="center";X.fillText(Math.ceil(G.cd/60),W/2,H/2+20);
X.fillStyle="#F39C12";X.font="bold 20px Courier New";X.fillText("GET READY!",W/2,H/2-60);
if(G.cd<=0){G.state="playing";serve();}
}
drawMenu();
}else if(G.state==="playing"){
drawBg();
if(G.pp>0){G.pp--;}
if(G.pp<=0){
handleP1();handleP2();
if(G.aiMode)G.ai.up(disc,p1);
p1.up();p2.up();disc.up();
if(disc.ow){disc.x=disc.ow.x+(disc.ow.fr?25:-25);disc.y=disc.ow.y;}
checkCatch();checkGoal();G.time--;
if(G.time<=0){
if(G.score[0]>G.score[1]){G.sets[0]++;showMsg("P1 VINCE IL SET!");}
else if(G.score[1]>G.score[0]){G.sets[1]++;showMsg("CPU VINCE IL SET!");}
else{G.sets[0]++;G.sets[1]++;showMsg("PAREGGIO!");}
G.set++;G.score=[0,0];G.time=3600;
}
}
p1.draw();p2.draw();
if(disc.ip||disc.ow)disc.draw();
P=P.filter(function(p){return p.lf>0;});
P.forEach(function(p){p.up();p.draw();});
if(G.mt>0){G.mt--;if(G.mt<=0)document.getElementById("msg").style.opacity=0;}
drawHUD();
}else if(G.state==="end"){
drawBg();p1.draw();p2.draw();
X.fillStyle="rgba(0,0,0,.75)";X.fillRect(0,0,W,H);X.textAlign="center";
X.fillStyle="#e94560";X.font="bold 44px Courier New";
var w=G.sets[0]>G.sets[1]?"P1 VINCE!":(G.sets[1]>G.sets[0]?"CPU VINCE!":"PAREGGIO!");
X.fillText(w,W/2,H/2-20);
X.fillStyle="#fff";X.font="18px Courier New";X.fillText("Premi R per rigiocare o ESC per il menu",W/2,H/2+40);
if(keys["KeyR"]){keys["KeyR"]=false;initGame();G.state="countdown";G.cd=120;}
if(keys["Escape"]){keys["Escape"]=false;G.state="menu";menu.screen="main";menu.sel=0;p1.x=court.x+100;p2.x=court.x+court.w-100;}
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;
}
updateUI();
requestAnimationFrame(loop);
}
initCtrl();
loop();
})();
</script>
</body>
</html>
+166
View File
@@ -0,0 +1,166 @@
import pygame
import sys
import random
# Inizializzazione
pygame.init()
W, H = 900, 500
screen = pygame.display.set_mode((W, H))
pygame.display.set_caption("Windjammers Arcade")
clock = pygame.time.Clock()
font = pygame.font.Font(None, 36)
big_font = pygame.font.Font(None, 72)
# Colori
SKY = (135, 206, 235)
GRASS = (125, 206, 160)
WHITE = (255, 255, 255)
RED = (231, 76, 60)
BLUE = (52, 152, 219)
ORANGE = (243, 156, 18)
BLACK = (0, 0, 0)
# Giocatori
p1 = pygame.Rect(100, 200, 30, 80)
p2 = pygame.Rect(770, 200, 30, 80)
p1_score = 0
p2_score = 0
# Disco
disc = pygame.Rect(440, 240, 20, 20)
disc_vx = 0
disc_vy = 0
disc_owner = None # 'p1', 'p2', or None
disc_in_play = False
# Rete
net = pygame.Rect(445, 80, 10, 340)
def reset_disc():
global disc_owner, disc_in_play, disc_vx, disc_vy
disc.center = (450, 250)
disc_vx = 0
disc_vy = 0
disc_owner = 'p1' if random.random() < 0.5 else 'p2'
disc_in_play = False
reset_disc()
def throw_disc(player):
global disc_owner, disc_in_play, disc_vx, disc_vy
disc_owner = None
disc_in_play = True
target_x = 850 if player == 'p1' else 50
target_y = 100 + random.randint(0, 300)
dx = target_x - disc.centerx
dy = target_y - disc.centery
dist = max(1, (dx**2 + dy**2)**0.5)
disc_vx = dx / dist * 8
disc_vy = dy / dist * 8
# Game loop
running = True
mode = 'menu'
while running:
# Eventi
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
if event.type == pygame.KEYDOWN:
if event.key == pygame.K_SPACE and mode == 'menu':
mode = 'playing'
reset_disc()
if event.key == pygame.K_ESCAPE and mode == 'playing':
mode = 'menu'
if mode == 'menu':
# Menu
screen.fill((26, 26, 46))
title = big_font.render("WINDJAMMERS", True, RED)
subtitle = font.render("FLYING DISC ARCADE", True, ORANGE)
hint = font.render("Premi SPAZIO per iniziare", True, WHITE)
screen.blit(title, (W//2 - title.get_width()//2, 120))
screen.blit(subtitle, (W//2 - subtitle.get_width()//2, 200))
screen.blit(hint, (W//2 - hint.get_width()//2, 320))
elif mode == 'playing':
# Input P1
keys = pygame.key.get_pressed()
p1_speed = 5
if keys[pygame.K_w] and p1.top > 80: p1.y -= p1_speed
if keys[pygame.K_s] and p1.bottom < 420: p1.y += p1_speed
if keys[pygame.K_a] and p1.left > 50: p1.x -= p1_speed
if keys[pygame.K_d] and p1.right < 430: p1.x += p1_speed
if keys[pygame.K_f] and disc_owner == 'p1':
throw_disc('p1')
# AI P2
if disc_owner == 'p2' and random.random() < 0.02:
throw_disc('p2')
elif disc_in_play and disc_owner is None:
# Move toward disc
if disc.centerx < p2.centerx: p2.x -= 3
if disc.centerx > p2.centerx: p2.x += 3
if disc.centery < p2.centery: p2.y -= 3
if disc.centery > p2.centery: p2.y += 3
# Keep P2 in bounds
p2.clamp_ip(pygame.Rect(470, 80, 380, 340))
p1.clamp_ip(pygame.Rect(50, 80, 380, 340))
# Disco fisso al possessore
if disc_owner == 'p1':
disc.centerx = p1.right + 10
disc.centery = p1.centery
elif disc_owner == 'p2':
disc.centerx = p2.left - 10
disc.centery = p2.centery
elif disc_in_play:
disc.x += disc_vx
disc.y += disc_vy
# Rimbalzi
if disc.top < 80 or disc.bottom > 420: disc_vy *= -0.8
if disc.left < 50 or disc.right > 850: disc_vx *= -0.8
if net.colliderect(disc): disc_vx *= -0.7
# Cattura
if p1.colliderect(disc) and disc_owner is None:
disc_owner = 'p1'
disc_in_play = False
elif p2.colliderect(disc) and disc_owner is None:
disc_owner = 'p2'
disc_in_play = False
# Gol
if disc.left < 50:
p2_score += 3
reset_disc()
elif disc.right > 850:
p1_score += 3
reset_disc()
# Disegno
screen.fill(SKY)
# Campo
pygame.draw.rect(screen, GRASS, (50, 80, 800, 340))
pygame.draw.rect(screen, WHITE, (50, 80, 800, 340), 3)
# Rete
pygame.draw.rect(screen, WHITE, net)
# Porte
pygame.draw.rect(screen, RED, (20, 180, 30, 140))
pygame.draw.rect(screen, BLUE, (850, 180, 30, 140))
# Giocatori
pygame.draw.rect(screen, RED, p1)
pygame.draw.rect(screen, BLUE, p2)
pygame.draw.ellipse(screen, ORANGE, disc)
# Punteggi
s1 = font.render(f"P1: {p1_score}", True, WHITE)
s2 = font.render(f"CPU: {p2_score}", True, WHITE)
screen.blit(s1, (200, 20))
screen.blit(s2, (650, 20))
# Istruzioni
hint = font.render("WASD=muovi F=tiro ESC=menu", True, (150,150,150))
screen.blit(hint, (W//2 - hint.get_width()//2, 460))
pygame.display.flip()
clock.tick(60)
pygame.quit()
+20
View File
@@ -0,0 +1,20 @@
const http = require('http');
const fs = require('fs');
const path = require('path');
const PORT = 8095;
const ROOT = '/home/codex/giochino';
const MIME = { '.html': 'text/html', '.js': 'application/javascript', '.css': 'text/css', '.png': 'image/png' };
const server = http.createServer((req, res) => {
let filePath = path.join(ROOT, req.url === '/' ? 'index.html' : req.url);
const ext = path.extname(filePath);
fs.readFile(filePath, (err, data) => {
if (err) { res.writeHead(404); res.end('404'); return; }
res.writeHead(200, { 'Content-Type': MIME[ext] || 'text/plain' });
res.end(data);
});
});
server.listen(PORT, '0.0.0.0', () => console.log(`Server: http://0.0.0.0:${PORT}/`));