11import pgzrun
22import polars as pl
3- import random
3+ import os
4+ from pgzero .keyboard import keys
45
56# ───────────────── CONFIG ─────────────────
67TITLE = "Cyber Quiz Polars Edition"
78WIDTH = 900
89HEIGHT = 600
910TEMPO_DOMANDA = 15
1011NOME_FILE_CSV = "domande.csv"
12+ NOME_FILE_RISPOSTE = "risposte.csv"
1113
1214# ───────────────── COLORI ─────────────────
1315COLOR_BG = (10 , 10 , 25 )
1416COLOR_CARD = (30 , 30 , 50 )
15- COLOR_ACCENT = (0 , 255 , 200 )
17+ COLOR_ACCENT = (0 , 255 , 200 )
1618COLOR_DANGER = (255 , 45 , 85 )
1719COLOR_SHADOW = (5 , 5 , 15 )
1820TEXT_MAIN = (255 , 255 , 255 )
2729game_over = False
2830mouse_pos = (0 , 0 )
2931
32+ # Nickname / iniziale stato di inserimento
33+ entering_name = True
34+ nome_utente = ""
35+
3036# Box
3137question_box = Rect (50 , 90 , 800 , 150 )
3238answer_boxes = [Rect (50 , 300 , 380 , 110 ), Rect (470 , 300 , 380 , 110 ),
@@ -39,21 +45,21 @@ def carica_e_mischia():
3945 try :
4046 # Leggiamo il CSV con Polars
4147 df = pl .read_csv (NOME_FILE_CSV )
42-
43- # Rimescoliamo l'intero DataFrame.
44- # shuffle=True con fraction=1.0 ci restituisce tutto il DF in ordine casuale
48+
49+ # Rimescoliamo l'intero DataFrame. shuffle=True con fraction=1.0
4550 df_shuffled = df .sample (fraction = 1.0 , shuffle = True )
46-
47- # Convertiamo in una lista di dizionari per facilitare l'estrazione nel gioco
51+
52+ # Convertiamo in una lista di dizionari
4853 lista_domande = df_shuffled .to_dicts ()
4954 contatore_totale = len (lista_domande )
50-
55+
5156 except Exception as e :
5257 print (f"Errore nel caricamento dati: { e } " )
5358
59+
5460def prossima_domanda ():
5561 global domanda_corrente , indice_domande , secondi_mancanti , game_over
56-
62+
5763 if len (lista_domande ) > 0 :
5864 indice_domande += 1
5965 # .pop(0) prende la prima domanda e la RIMUOVE dalla lista -> Mai ripetizioni
@@ -62,65 +68,170 @@ def prossima_domanda():
6268 else :
6369 game_over = True
6470
71+
72+ # ───────────────── SALVATAGGIO RISPOSTE ─────────────────
73+ def init_file_risposte ():
74+ """Crea il file risposte con header se non esiste."""
75+ if not os .path .exists (NOME_FILE_RISPOSTE ):
76+ with open (NOME_FILE_RISPOSTE , mode = 'w' , newline = '' , encoding = 'utf-8' ) as f :
77+ writer = csv .writer (f )
78+ writer .writerow (["nome_utente" , "id_domanda" , "numero_risposta_fornita" , "tempo_risposta" ])
79+
80+
81+ def salva_risposta (nome , id_dom , numero_risposta , tempo_risposta ):
82+ init_file_risposte ()
83+ with open (NOME_FILE_RISPOSTE , mode = 'a' , newline = '' , encoding = 'utf-8' ) as f :
84+ writer = csv .writer (f )
85+ writer .writerow ([nome , id_dom , numero_risposta , tempo_risposta ])
86+
87+
6588# ───────────────── DISEGNO ─────────────────
6689def draw_styled_rect (rect , color ):
67- screen .draw .filled_rect (Rect (rect .x + 4 , rect .y + 4 , rect .w , rect .h ), COLOR_SHADOW )
90+ screen .draw .filled_rect (Rect (rect .x + 4 , rect .y + 4 , rect .w , rect .h ), COLOR_SHADOW )
6891 screen .draw .filled_rect (rect , color )
6992
93+
7094def draw ():
7195 screen .fill (COLOR_BG )
96+
97+ # Se siamo nella fase di inserimento nickname
98+ if entering_name :
99+ screen .draw .text ("Benvenuto a Cyber Quiz!" , center = (WIDTH // 2 , 120 ), fontsize = 48 , color = COLOR_ACCENT )
100+ screen .draw .text ("Inserisci il tuo nickname e premi ENTER per iniziare:" , center = (WIDTH // 2 , 180 ), fontsize = 28 , color = TEXT_MAIN )
101+
102+ # box input
103+ input_box = Rect (WIDTH // 2 - 300 , 230 , 600 , 60 )
104+ draw_styled_rect (input_box , COLOR_CARD )
105+ display_name = nome_utente if nome_utente != "" else "(digita qui...)"
106+ screen .draw .text (display_name , center = input_box .center , fontsize = 36 , color = TEXT_MAIN )
107+ return
108+
72109 if game_over :
73- screen .draw .text (f"SESSIONE FINITA\n Punteggio: { punteggio } /{ contatore_totale } " ,
74- center = (WIDTH // 2 , HEIGHT // 2 ), fontsize = 50 , color = COLOR_ACCENT )
110+ screen .draw .text (f"SESSIONE FINITA\n Punteggio: { punteggio } /{ contatore_totale } " ,
111+ center = (WIDTH // 2 , HEIGHT // 2 ), fontsize = 50 , color = COLOR_ACCENT )
75112 return
76113
77114 # Info
78- screen .draw .text (f"DOMANDA { indice_domande } /{ contatore_totale } " , (50 , 20 ), color = COLOR_ACCENT , fontsize = 25 )
79- screen .draw .text (f"PUNTI: { punteggio } " , (WIDTH - 150 , 20 ), color = TEXT_MAIN , fontsize = 25 )
115+ screen .draw .text (f"GIOCATORE: { nome_utente } " , (50 , 20 ), color = COLOR_ACCENT , fontsize = 22 )
116+ screen .draw .text (f"DOMANDA { indice_domande } /{ contatore_totale } " , (50 , 50 ), color = COLOR_ACCENT , fontsize = 22 )
117+ screen .draw .text (f"PUNTI: { punteggio } " , (WIDTH - 150 , 20 ), color = TEXT_MAIN , fontsize = 25 )
80118
81119 # Box Domanda
82120 draw_styled_rect (question_box , COLOR_CARD )
83- screen .draw .textbox (domanda_corrente ['domanda' ], question_box .inflate (- 40 ,- 40 ), color = TEXT_MAIN )
121+ if domanda_corrente :
122+ screen .draw .textbox (str (domanda_corrente .get ('domanda' , '' )) , question_box .inflate (- 40 , - 40 ), color = TEXT_MAIN )
123+ else :
124+ screen .draw .textbox ("Caricamento..." , question_box .inflate (- 40 , - 40 ), color = TEXT_MAIN )
84125
85126 # Timer Progressivo
86- percent = secondi_mancanti / TEMPO_DOMANDA
127+ percent = secondi_mancanti / TEMPO_DOMANDA if TEMPO_DOMANDA > 0 else 0
87128 screen .draw .filled_rect (timer_bar_box , COLOR_SHADOW )
88129 screen .draw .filled_rect (Rect (timer_bar_box .x , timer_bar_box .y , int (timer_bar_box .w * percent ), timer_bar_box .h ), COLOR_ACCENT )
89130
90131 # Risposte
91132 for i in range (4 ):
92133 box = answer_boxes [i ]
93- chiave = f"risposta{ i + 1 } "
134+ chiave = f"risposta{ i + 1 } "
94135 is_hover = box .collidepoint (mouse_pos )
95136 draw_styled_rect (box , (60 , 60 , 90 ) if is_hover else COLOR_CARD )
96- screen .draw .textbox (str (domanda_corrente [chiave ]), box .inflate (- 20 , - 20 ), color = TEXT_MAIN )
137+ if domanda_corrente :
138+ screen .draw .textbox (str (domanda_corrente .get (chiave , '' )), box .inflate (- 20 , - 20 ), color = TEXT_MAIN )
139+
97140
98141# ───────────────── INPUT & TIMER ─────────────────
99142def on_mouse_move (pos ):
100143 global mouse_pos
101144 mouse_pos = pos
102145
146+
103147def on_mouse_down (pos ):
104148 global punteggio
105- if game_over : return
149+ if entering_name :
150+ return
151+ if game_over :
152+ return
106153
107154 for i , box in enumerate (answer_boxes ):
108155 if box .collidepoint (pos ):
109- # Polars può leggere i numeri come int, quindi forziamo a stringa per il confronto
110- if str (i + 1 ) == str (domanda_corrente ['corretta' ]):
111- punteggio += 1
156+ # tempo di risposta = tempo passato dalla visualizzazione (tempo iniziale - secondi_mancanti)
157+ tempo_risposta = TEMPO_DOMANDA - secondi_mancanti
158+ # Troviamo un id per la domanda: preferiamo campi 'id_domanda' o 'id', altrimenti usiamo l'indice attuale
159+ id_dom = None
160+ if domanda_corrente is not None :
161+ id_dom = domanda_corrente .get ('id_domanda' ) if 'id_domanda' in domanda_corrente else domanda_corrente .get ('id' )
162+ if id_dom is None :
163+ id_dom = indice_domande
164+
165+ numero_risposta = i + 1
166+
167+ # Salviamo la risposta nel CSV
168+ salva_risposta (nome_utente , id_dom , numero_risposta , tempo_risposta )
169+
170+ # Controlliamo correttezza (forziamo a stringa)
171+ try :
172+ if str (numero_risposta ) == str (domanda_corrente .get ('corretta' )):
173+ punteggio += 1
174+ except Exception :
175+ pass
176+
112177 prossima_domanda ()
113178
179+
180+ def on_key_down (key ):
181+ global nome_utente , entering_name , game_over
182+
183+ # Durante l'inserimento del nickname
184+ if entering_name :
185+ # BACKSPACE
186+ if key == keys .BACKSPACE :
187+ nome_utente = nome_utente [:- 1 ]
188+ return
189+ # ENTER -> iniziare se nome non vuoto
190+ if key == keys .RETURN or key == keys .KP_ENTER :
191+ if nome_utente .strip () != "" :
192+ start_game ()
193+ return
194+ # SOLO caratteri stampabili (singolo carattere nel nome della key)
195+ try :
196+ ch = key .name
197+ except Exception :
198+ ch = None
199+ if ch and len (ch ) == 1 :
200+ nome_utente += ch
201+ return
202+
114203def tick ():
115204 global secondi_mancanti
116- if not game_over :
205+ if not game_over and not entering_name :
117206 if secondi_mancanti > 0 :
118207 secondi_mancanti -= 1
119208 else :
209+ # se finisce il tempo, salviamo una risposta vuota/timeout
210+ # id domanda
211+ id_dom = None
212+ if domanda_corrente is not None :
213+ id_dom = domanda_corrente .get ('id_domanda' ) if 'id_domanda' in domanda_corrente else domanda_corrente .get ('id' )
214+ if id_dom is None :
215+ id_dom = indice_domande
216+ # numero_risposta 0 per timeout
217+ salva_risposta (nome_utente , id_dom , 0 , TEMPO_DOMANDA )
120218 prossima_domanda ()
121219
122- # AVVIO
123- carica_e_mischia ()
124- prossima_domanda ()
125- clock .schedule_interval (tick , 1.0 )
126- pgzrun .go ()
220+
221+ # ───────────────── CONTROLLO START ─────────────────
222+
223+ def start_game ():
224+ global entering_name , nome_utente
225+ entering_name = False
226+ # Carichiamo e avviamo
227+ carica_e_mischia ()
228+ prossima_domanda ()
229+ # Avviamo il tick (se non già avviato). È sicuro chiamarlo più volte su pgzero: evitiamo duplicati
230+ try :
231+ clock .schedule_interval (tick , 1.0 )
232+ except Exception :
233+ pass
234+
235+ # AVVIO: non carichiamo automaticamente le domande, aspettiamo il nickname
236+ init_file_risposte ()
237+ pgzrun .go ()
0 commit comments