MarcosAutuori commited on
Commit
5d69ca6
·
verified ·
1 Parent(s): 9265e1c

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +88 -42
app.py CHANGED
@@ -13,7 +13,7 @@ import struct
13
  import math
14
  import os
15
 
16
- # Configuration and Data
17
  GRID_NUMBERS = [
18
  10, 12, 14, 15, 16, 18, 20, 21,
19
  24, 25, 27, 28, 30, 32, 35, 36,
@@ -23,25 +23,35 @@ GRID_NUMBERS = [
23
  KEYBOARD_KEYS = [2, 3, 4, 5, 6, 7, 8, 9, 10]
24
  MAX_TIME = 150
25
 
26
- # --- AUDIO GENERATION (Same logic as your original script) ---
27
- def generate_wav(filename, freq, duration=0.1, volume=0.3):
28
- sample_rate = 44100
29
- n_samples = int(sample_rate * duration)
30
- with wave.open(filename, 'w') as f:
31
- f.setnchannels(1)
32
- f.setsampwidth(2)
33
- f.setframerate(sample_rate)
34
- for i in range(n_samples):
35
- val = int(volume * 32767.0 * math.sin(2.0 * math.pi * freq * i / sample_rate))
36
- data = struct.pack('<h', val)
37
- f.writeframesraw(data)
38
-
39
- # Pre-generate sound files
40
- if not os.path.exists("sounds"): os.makedirs("sounds")
41
- generate_wav("sounds/click.wav", 440, 0.05)
42
- generate_wav("sounds/correct.wav", 880, 0.2)
43
- generate_wav("sounds/wrong.wav", 220, 0.3)
44
-
 
 
 
 
 
 
 
 
 
 
45
  def get_empty_state():
46
  return {
47
  "solved": [False] * 32,
@@ -62,10 +72,10 @@ def format_grid_button(idx, solved_list, selected_idx):
62
 
63
  def handle_game_logic(state, action_type, value=None):
64
  if state["game_over"]:
65
- return state, "Game Over!", None
66
 
67
- feedback = "Select a number..."
68
- sound = "sounds/click.wav" # Default sound
69
 
70
  if action_type == "select_target":
71
  state["selected_idx"] = value
@@ -75,46 +85,65 @@ def handle_game_logic(state, action_type, value=None):
75
  elif action_type == "select_factor":
76
  if state["selected_idx"] is None:
77
  feedback = "⚠️ Select a grid number first!"
 
78
  else:
79
  state["current_factors"].append(int(value))
 
80
  if len(state["current_factors"]) == 2:
81
  a, b = state["current_factors"]
82
  target = GRID_NUMBERS[state["selected_idx"]]
83
 
84
  if a * b == target:
85
  state["solved"][state["selected_idx"]] = True
86
- feedback = f"✔️ {a} x {b} = {target}!"
87
- sound = "sounds/correct.wav"
88
  state["selected_idx"] = None
89
  else:
90
- feedback = f"❌ {a}* {b} = {a*b} (Wrong for {target})."
91
- sound = "sounds/wrong.wav"
92
  state["current_factors"] = []
 
 
93
 
 
94
  elapsed = int(time.time() - state["start_time"])
95
  state["score"] = max(0, MAX_TIME - elapsed)
96
 
97
  if all(state["solved"]):
98
  state["game_over"] = True
99
- feedback = "🏆 VICTORY!"
100
 
101
- return state, feedback, sound
102
 
 
103
  custom_css = """
104
- .grid-btn { min-width: 80px !important; height: 80px !important; font-size: 20px !important; }
105
- .kb-btn { min-width: 60px !important; height: 50px !important; }
 
 
 
 
 
 
 
 
106
  """
107
 
108
  with gr.Blocks(title="Autuori - Factorization Game", css=custom_css) as demo:
109
  state = gr.State(get_empty_state())
110
 
111
- # Hidden audio component for sound effects
112
  audio_player = gr.Audio(interactive=False, visible=False, autoplay=True)
113
 
114
- gr.Markdown("# 🎮 Interactive Factorization v1.4 EN\n**Developed by Marcos Autuori**")
 
 
 
 
115
 
116
  with gr.Row():
117
  with gr.Column(scale=4):
 
118
  grid_buttons = []
119
  for r in range(4):
120
  with gr.Row():
@@ -124,16 +153,29 @@ with gr.Blocks(title="Autuori - Factorization Game", css=custom_css) as demo:
124
  grid_buttons.append(btn)
125
 
126
  with gr.Column(scale=1):
127
- status_box = gr.Textbox(label="Status", value="Select a blue number", interactive=False)
128
- score_display = gr.Number(label="Score", value=MAX_TIME, interactive=False)
129
- keyboard_btns = [gr.Button(str(val), variant="secondary", elem_classes="kb-btn") for val in KEYBOARD_KEYS]
130
- reset_btn = gr.Button("Restart", variant="stop")
131
-
 
 
 
 
 
 
 
 
 
 
 
 
132
  def update_ui(s):
133
  updates = [format_grid_button(i, s["solved"], s["selected_idx"]) for i in range(32)]
134
  return updates + [s["score"]]
135
 
136
- # Grid Click Events
 
137
  for i, btn in enumerate(grid_buttons):
138
  btn.click(
139
  fn=lambda s, idx=i: handle_game_logic(s, "select_target", idx),
@@ -141,7 +183,7 @@ with gr.Blocks(title="Autuori - Factorization Game", css=custom_css) as demo:
141
  outputs=[state, status_box, audio_player]
142
  ).then(fn=update_ui, inputs=[state], outputs=grid_buttons + [score_display])
143
 
144
- # Keyboard Click Events
145
  for kb_btn in keyboard_btns:
146
  kb_btn.click(
147
  fn=lambda s, v=kb_btn.value: handle_game_logic(s, "select_factor", v),
@@ -149,12 +191,16 @@ with gr.Blocks(title="Autuori - Factorization Game", css=custom_css) as demo:
149
  outputs=[state, status_box, audio_player]
150
  ).then(fn=update_ui, inputs=[state], outputs=grid_buttons + [score_display])
151
 
 
152
  def reset_all():
153
  new_state = get_empty_state()
154
  updates = [gr.update(value=str(num), variant="secondary", interactive=True) for num in GRID_NUMBERS]
155
- return [new_state, "Game Restarted!", None] + updates + [MAX_TIME]
156
 
157
- reset_btn.click(fn=reset_all, outputs=[state, status_box, audio_player] + grid_buttons + [score_display])
 
 
 
158
 
159
  if __name__ == "__main__":
160
  demo.launch()
 
13
  import math
14
  import os
15
 
16
+ # --- CONFIGURATION & DATA ---
17
  GRID_NUMBERS = [
18
  10, 12, 14, 15, 16, 18, 20, 21,
19
  24, 25, 27, 28, 30, 32, 35, 36,
 
23
  KEYBOARD_KEYS = [2, 3, 4, 5, 6, 7, 8, 9, 10]
24
  MAX_TIME = 150
25
 
26
+ # --- AUDIO GENERATION ENGINE ---
27
+ def ensure_sounds():
28
+ if not os.path.exists("sounds"):
29
+ os.makedirs("sounds")
30
+
31
+ # Frequency mapping (Hz)
32
+ sound_map = {
33
+ "sounds/click.wav": 440, # A4
34
+ "sounds/correct.wav": 880, # A5
35
+ "sounds/wrong.wav": 220 # A3
36
+ }
37
+
38
+ for filename, freq in sound_map.items():
39
+ if not os.path.exists(filename):
40
+ sample_rate = 44100
41
+ duration = 0.15 if "click" not in filename else 0.05
42
+ volume = 0.3
43
+ n_samples = int(sample_rate * duration)
44
+ with wave.open(filename, 'w') as f:
45
+ f.setnchannels(1)
46
+ f.setsampwidth(2)
47
+ f.setframerate(sample_rate)
48
+ for i in range(n_samples):
49
+ val = int(volume * 32767.0 * math.sin(2.0 * math.pi * freq * i / sample_rate))
50
+ f.writeframesraw(struct.pack('<h', val))
51
+
52
+ ensure_sounds()
53
+
54
+ # --- GAME LOGIC ---
55
  def get_empty_state():
56
  return {
57
  "solved": [False] * 32,
 
72
 
73
  def handle_game_logic(state, action_type, value=None):
74
  if state["game_over"]:
75
+ return state, "Game Over! Please restart.", None
76
 
77
+ feedback = "Select a target number..."
78
+ sound_to_play = "sounds/click.wav"
79
 
80
  if action_type == "select_target":
81
  state["selected_idx"] = value
 
85
  elif action_type == "select_factor":
86
  if state["selected_idx"] is None:
87
  feedback = "⚠️ Select a grid number first!"
88
+ sound_to_play = "sounds/wrong.wav"
89
  else:
90
  state["current_factors"].append(int(value))
91
+
92
  if len(state["current_factors"]) == 2:
93
  a, b = state["current_factors"]
94
  target = GRID_NUMBERS[state["selected_idx"]]
95
 
96
  if a * b == target:
97
  state["solved"][state["selected_idx"]] = True
98
+ feedback = f"✔️ {a} x {b} = {target}! Correct!"
99
+ sound_to_play = "sounds/correct.wav"
100
  state["selected_idx"] = None
101
  else:
102
+ feedback = f"❌ {a} x {b} = {a*b} (Incorrect)."
103
+ sound_to_play = "sounds/wrong.wav"
104
  state["current_factors"] = []
105
+ else:
106
+ feedback = f"First factor: {value}. Pick the second one!"
107
 
108
+ # Score calculation
109
  elapsed = int(time.time() - state["start_time"])
110
  state["score"] = max(0, MAX_TIME - elapsed)
111
 
112
  if all(state["solved"]):
113
  state["game_over"] = True
114
+ feedback = f"🏆 VICTORY! Final Score: {state['score']}"
115
 
116
+ return state, feedback, sound_to_play
117
 
118
+ # --- INTERFACE DESIGN ---
119
  custom_css = """
120
+ .grid-btn {
121
+ min-width: 80px !important;
122
+ height: 80px !important;
123
+ font-size: 20px !important;
124
+ margin: 2px !important;
125
+ }
126
+ .kb-btn {
127
+ min-width: 60px !important;
128
+ height: 50px !important;
129
+ }
130
  """
131
 
132
  with gr.Blocks(title="Autuori - Factorization Game", css=custom_css) as demo:
133
  state = gr.State(get_empty_state())
134
 
135
+ # Hidden audio component (The core of the sound system)
136
  audio_player = gr.Audio(interactive=False, visible=False, autoplay=True)
137
 
138
+ gr.Markdown(f"""
139
+ # 🎮 Interactive Factorization v1.4 EN
140
+ **Developer:** Marcos Autuori
141
+ *Instructions: Select a blue number from the grid, then pick two factors from the keyboard.*
142
+ """)
143
 
144
  with gr.Row():
145
  with gr.Column(scale=4):
146
+ # 4x8 Grid Layout
147
  grid_buttons = []
148
  for r in range(4):
149
  with gr.Row():
 
153
  grid_buttons.append(btn)
154
 
155
  with gr.Column(scale=1):
156
+ # Status & Score Panel
157
+ status_box = gr.Textbox(label="Status", value="Select a blue target", interactive=False)
158
+ score_display = gr.Number(label="Score (Time)", value=MAX_TIME, interactive=False)
159
+
160
+ gr.Markdown("### Factors (2-10)")
161
+ keyboard_btns = []
162
+ # Keyboard arranged in rows
163
+ for r_kb in range(3):
164
+ with gr.Row():
165
+ for c_kb in range(3):
166
+ val = KEYBOARD_KEYS[r_kb * 3 + c_kb]
167
+ kb_btn = gr.Button(str(val), variant="secondary", elem_classes="kb-btn")
168
+ keyboard_btns.append(kb_btn)
169
+
170
+ reset_btn = gr.Button("Restart Game", variant="stop")
171
+
172
+ # Helper function to sync UI
173
  def update_ui(s):
174
  updates = [format_grid_button(i, s["solved"], s["selected_idx"]) for i in range(32)]
175
  return updates + [s["score"]]
176
 
177
+ # --- EVENT MAPPING ---
178
+ # Grid Clicks
179
  for i, btn in enumerate(grid_buttons):
180
  btn.click(
181
  fn=lambda s, idx=i: handle_game_logic(s, "select_target", idx),
 
183
  outputs=[state, status_box, audio_player]
184
  ).then(fn=update_ui, inputs=[state], outputs=grid_buttons + [score_display])
185
 
186
+ # Keyboard Clicks
187
  for kb_btn in keyboard_btns:
188
  kb_btn.click(
189
  fn=lambda s, v=kb_btn.value: handle_game_logic(s, "select_factor", v),
 
191
  outputs=[state, status_box, audio_player]
192
  ).then(fn=update_ui, inputs=[state], outputs=grid_buttons + [score_display])
193
 
194
+ # Reset Action
195
  def reset_all():
196
  new_state = get_empty_state()
197
  updates = [gr.update(value=str(num), variant="secondary", interactive=True) for num in GRID_NUMBERS]
198
+ return [new_state, "New Game Started!", None] + updates + [MAX_TIME]
199
 
200
+ reset_btn.click(
201
+ fn=reset_all,
202
+ outputs=[state, status_box, audio_player] + grid_buttons + [score_display]
203
+ )
204
 
205
  if __name__ == "__main__":
206
  demo.launch()