sunbal7 commited on
Commit
63fdfdb
ยท
verified ยท
1 Parent(s): b65a5e5

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +598 -474
app.py CHANGED
@@ -1,23 +1,26 @@
1
- # app.py - StoryCoder: Learn Coding Through Games & Stories
2
  import streamlit as st
3
  import os
4
  import time
5
  import random
6
- import base64
7
- from PIL import Image
8
- import io
9
- import requests
10
- import json
11
- import pygame
12
- import gtts
13
- from io import BytesIO
14
  import numpy as np
15
  import pandas as pd
16
  import plotly.express as px
 
 
 
 
 
 
 
 
 
 
 
17
 
18
  # Configure Streamlit page
19
  st.set_page_config(
20
- page_title="StoryCoder - Learn Coding Through Games",
21
  page_icon="๐ŸŽฎ",
22
  layout="wide",
23
  initial_sidebar_state="expanded"
@@ -30,53 +33,52 @@ st.markdown("""
30
 
31
  :root {
32
  --primary: #7E57C2;
33
- --secondary: #9575CD;
34
  --accent: #D1C4E9;
35
  --dark: #4527A0;
36
  --light: #F3E5F5;
37
- --grey: #ECEFF1;
38
  }
39
 
40
  body {
41
- background: linear-gradient(135deg, var(--grey) 0%, var(--light) 100%);
42
  font-family: 'Comic Neue', cursive;
 
43
  }
44
 
45
  .stApp {
46
  background: url('https://www.transparenttextures.com/patterns/cartographer.png');
47
- background-color: #fafafa;
48
- }
49
-
50
- .header {
51
- color: var(--dark);
52
- text-shadow: 2px 2px 4px rgba(0,0,0,0.1);
53
- font-family: 'Fredoka One', cursive;
54
  }
55
 
56
  .story-box {
57
  background-color: white;
58
  border-radius: 20px;
59
  padding: 25px;
60
- box-shadow: 0 8px 16px rgba(123, 31, 162, 0.15);
61
  border: 3px solid var(--accent);
62
  margin-bottom: 25px;
63
- background: linear-gradient(145deg, #ffffff, var(--grey));
 
 
 
 
 
64
  }
65
 
66
  .concept-card {
67
- background: linear-gradient(145deg, #ffffff, var(--light));
68
  border-radius: 15px;
69
- padding: 15px;
70
- margin: 10px 0;
71
- border-left: 5px solid var(--secondary);
72
- box-shadow: 0 4px 8px rgba(0,0,0,0.05);
73
  }
74
 
75
  .stButton>button {
76
  background: linear-gradient(45deg, var(--primary), var(--secondary));
77
  color: white;
78
- border-radius: 12px;
79
- padding: 10px 24px;
80
  font-weight: bold;
81
  font-size: 18px;
82
  border: none;
@@ -86,117 +88,136 @@ st.markdown("""
86
 
87
  .stButton>button:hover {
88
  transform: scale(1.05);
89
- box-shadow: 0 6px 12px rgba(123, 31, 162, 0.25);
90
  }
91
 
92
- .game-container {
93
- background: linear-gradient(135deg, var(--accent), #EDE7F6);
94
- border-radius: 20px;
95
- padding: 25px;
96
- box-shadow: 0 8px 32px rgba(123, 31, 162, 0.2);
97
- margin-bottom: 25px;
98
- text-align: center;
99
  }
100
 
101
- .game-board {
102
- display: grid;
103
- grid-template-columns: repeat(5, 1fr);
104
  gap: 10px;
105
- margin: 20px auto;
106
- max-width: 500px;
107
  }
108
 
109
- .game-cell {
110
- background: white;
111
- border-radius: 10px;
112
- height: 80px;
113
- display: flex;
114
- align-items: center;
115
- justify-content: center;
116
- font-size: 24px;
117
  cursor: pointer;
118
- box-shadow: 0 4px 8px rgba(0,0,0,0.1);
 
 
119
  transition: all 0.3s;
120
  }
121
 
122
- .game-cell:hover {
123
- transform: translateY(-5px);
124
- box-shadow: 0 6px 12px rgba(0,0,0,0.15);
125
  }
126
 
127
- .game-cell.selected {
128
- background: var(--secondary);
129
  color: white;
 
130
  }
131
 
132
- .explanation-box {
133
- background: linear-gradient(135deg, var(--light), #E1BEE7);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
134
  border-radius: 15px;
135
- padding: 20px;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
136
  margin: 20px 0;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
137
  border-left: 5px solid var(--primary);
138
- box-shadow: 0 4px 12px rgba(0,0,0,0.1);
139
  }
140
 
141
  .explanation-header {
142
  color: var(--dark);
143
  display: flex;
144
  align-items: center;
145
- gap: 10px;
146
- font-family: 'Fredoka One', cursive;
147
  }
148
 
149
  .code-block {
150
- background: #2c3e50;
151
- color: #ecf0f1;
152
- padding: 15px;
153
  border-radius: 10px;
 
154
  font-family: 'Courier New', monospace;
155
  margin: 15px 0;
156
  overflow-x: auto;
157
  }
158
 
159
- .tabs {
160
- display: flex;
161
- gap: 10px;
162
- margin-bottom: 20px;
163
- overflow-x: auto;
164
- }
165
-
166
- .tab {
167
- padding: 10px 20px;
168
- background-color: var(--accent);
169
- border-radius: 10px;
170
- cursor: pointer;
171
  font-weight: bold;
172
- white-space: nowrap;
173
- font-family: 'Fredoka One', cursive;
174
- }
175
-
176
- .tab.active {
177
- background: linear-gradient(45deg, var(--primary), var(--dark));
178
- color: white;
179
- }
180
-
181
- @media (max-width: 768px) {
182
- .tabs {
183
- flex-wrap: wrap;
184
- }
185
-
186
- .game-board {
187
- grid-template-columns: repeat(3, 1fr);
188
- }
189
  }
190
  </style>
191
  """, unsafe_allow_html=True)
192
 
193
- # Concept database with detailed explanations
194
  CONCEPTS = {
195
  "loop": {
196
  "name": "Loop",
197
  "emoji": "๐Ÿ”„",
198
  "description": "Loops repeat actions multiple times",
199
- "example": "for i in range(5):\n print('Hello!')\n jump()",
200
  "color": "#7E57C2",
201
  "explanation": "A loop is like a magic spell that makes something happen again and again. In programming, we use loops when we want to repeat an action multiple times without writing the same code over and over."
202
  },
@@ -204,82 +225,68 @@ CONCEPTS = {
204
  "name": "Conditional",
205
  "emoji": "โ“",
206
  "description": "Conditionals make decisions in code",
207
- "example": "if is_sunny:\n play_outside()\nelse:\n stay_inside()",
208
- "color": "#9575CD",
209
  "explanation": "Conditionals are like crossroads in a story where you choose which path to take. In programming, we use 'if' statements to make decisions based on certain conditions, just like choosing what to wear based on the weather."
210
  },
211
  "function": {
212
  "name": "Function",
213
  "emoji": "โœจ",
214
  "description": "Functions are reusable blocks of code",
215
- "example": "def cast_spell():\n print('Abracadabra!')\n create_magic()\n\ncast_spell()",
216
- "color": "#D1C4E9",
217
  "explanation": "Functions are like magic spells you can create and reuse whenever you need them. They help us organize code into reusable blocks so we don't have to write the same thing multiple times."
218
  },
219
  "variable": {
220
  "name": "Variable",
221
  "emoji": "๐Ÿ“ฆ",
222
  "description": "Variables store information",
223
- "example": "score = 10\nplayer_name = 'Wizard'",
224
- "color": "#B39DDB",
225
  "explanation": "Variables are like labeled boxes where you can store information. They help us remember values and use them later in our code, just like remembering a character's name in a story."
 
 
 
 
 
 
 
 
226
  }
227
  }
228
 
229
- # Game configurations
230
- GAME_CONFIGS = {
231
  "loop": {
232
- "title": "Loop Adventure",
233
- "description": "Help the wizard collect stars by repeating actions!",
234
- "instructions": "Click the stars in order. Complete 3 rounds to win!",
235
- "emoji": "๐Ÿ”„"
236
  },
237
  "conditional": {
238
- "title": "Decision Maze",
239
- "description": "Guide the robot through the maze by making choices!",
240
- "instructions": "Choose the correct path based on the conditions.",
241
- "emoji": "โ“"
242
  },
243
  "function": {
244
- "title": "Magic Function",
245
- "description": "Create spells by defining functions!",
246
- "instructions": "Combine magic elements to create powerful functions.",
247
- "emoji": "โœจ"
248
  }
249
  }
250
 
251
- # Initialize pygame for audio
252
- pygame.mixer.init()
253
-
254
- def text_to_speech(text, filename="explanation.mp3"):
255
- """Convert text to speech using gTTS"""
256
- try:
257
- tts = gtts.gTTS(text=text, lang='en')
258
- tts.save(filename)
259
- return filename
260
- except Exception as e:
261
- st.error(f"Audio creation error: {str(e)}")
262
- return None
263
-
264
- def play_audio(file_path):
265
- """Play audio using pygame"""
266
- try:
267
- pygame.mixer.music.load(file_path)
268
- pygame.mixer.music.play()
269
- return True
270
- except:
271
- return False
272
-
273
- def autoplay_audio(file_path):
274
- """Create an audio autoplay element for Streamlit"""
275
- audio_bytes = open(file_path, "rb").read()
276
- b64 = base64.b64encode(audio_bytes).decode()
277
- md = f"""
278
- <audio autoplay>
279
- <source src="data:audio/mp3;base64,{b64}" type="audio/mp3">
280
- </audio>
281
- """
282
- st.markdown(md, unsafe_allow_html=True)
283
 
284
  def analyze_story(story):
285
  """Analyze story and identify programming concepts"""
@@ -291,7 +298,7 @@ def analyze_story(story):
291
  detected_concepts.append("loop")
292
 
293
  # Check for conditionals
294
- if any(word in story_lower for word in ["if", "when", "unless", "whether", "choose"]):
295
  detected_concepts.append("conditional")
296
 
297
  # Check for functions
@@ -299,196 +306,310 @@ def analyze_story(story):
299
  detected_concepts.append("function")
300
 
301
  # Check for variables
302
- if any(word in story_lower for word in ["is", "has", "set to", "value", "store"]):
303
  detected_concepts.append("variable")
304
 
 
 
 
 
305
  return list(set(detected_concepts))
306
 
307
- def create_story_image(story, concept):
308
- """Create a story image using DiceBear avatars"""
309
- try:
310
- # Create avatar based on story theme
311
- theme = "lorelei" if "dragon" in story.lower() else "pixel-art"
312
- url = f"https://api.dicebear.com/7.x/{theme}/png?seed={story[:10]}"
313
- response = requests.get(url)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
314
 
315
- if response.status_code == 200:
316
- return BytesIO(response.content)
317
- return None
318
- except:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
319
  return None
320
 
321
- def create_concept_explanation(story, concept):
322
- """Create a detailed explanation of the programming concept"""
 
 
 
 
 
 
 
 
 
 
 
 
323
  concept_info = CONCEPTS.get(concept, {})
 
324
 
325
  explanation = f"""
326
- โœจ Let's learn about {concept_info.get('name', 'programming')} through your story!
327
 
328
  Your story: "{story[:100]}..."
329
 
330
- This story shows the concept of: {concept_info.get('name', 'Programming')}
331
 
332
  {concept_info.get('explanation', 'This is a fundamental programming concept.')}
333
 
334
- In programming, we use {concept_info.get('name', 'this concept')} like this:
335
- ```
336
- {concept_info.get('example', 'Example code will appear here')}
337
- ```
 
 
 
 
 
 
338
 
339
- In your game, you'll:
340
- - Practice using {concept_info.get('name', 'this concept')}
341
- - See how it works in real code
342
- - Create your own version of the concept
 
343
 
344
- Ready to play and learn? Let's go!
345
  """
346
 
347
  return explanation
348
 
349
- def create_game_state(concept):
350
- """Initialize game state based on concept"""
351
- game_state = {
352
- "concept": concept,
353
- "score": 0,
354
- "level": 1,
355
- "completed": False
356
- }
357
 
358
- if concept == "loop":
359
- game_state["cells"] = [""] * 9
360
- game_state["sequence"] = random.sample(range(9), 3)
361
- game_state["current_step"] = 0
362
- game_state["max_steps"] = 3
363
-
364
- elif concept == "conditional":
365
- game_state["paths"] = [
366
- {"condition": "sunny", "result": "park"},
367
- {"condition": "rainy", "result": "home"},
368
- {"condition": "windy", "result": "kite"}
369
- ]
370
- game_state["current_condition"] = random.choice(["sunny", "rainy", "windy"])
371
-
372
- elif concept == "function":
373
- game_state["spell_components"] = ["โœจ", "๐Ÿ”ฅ", "๐Ÿ’ง", "๐ŸŒช๏ธ", "๐ŸŒฑ"]
374
- game_state["current_spell"] = []
375
- game_state["target_spell"] = random.sample(game_state["spell_components"], 3)
376
-
377
- return game_state
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
378
 
379
- def render_game(game_state):
380
- """Render the game UI based on game state"""
381
- concept = game_state["concept"]
382
- config = GAME_CONFIGS.get(concept, {})
383
-
384
- with st.container():
385
- st.markdown(f"<h2 style='text-align:center; color:{CONCEPTS[concept]['color']};'>{config.get('title', 'Programming Game')}</h2>", unsafe_allow_html=True)
386
- st.markdown(f"<h3 style='text-align:center;'>{config.get('description', 'Learn programming through play!')}</h3>", unsafe_allow_html=True)
387
-
388
- # Score and level display
389
- col1, col2 = st.columns(2)
390
- with col1:
391
- st.markdown(f"<div style='background:{CONCEPTS[concept]['color']}; color:white; padding:10px; border-radius:10px; text-align:center;'>๐ŸŒŸ Score: {game_state['score']}</div>", unsafe_allow_html=True)
392
- with col2:
393
- st.markdown(f"<div style='background:{CONCEPTS[concept]['color']}; color:white; padding:10px; border-radius:10px; text-align:center;'>๐Ÿ“ˆ Level: {game_state['level']}</div>", unsafe_allow_html=True)
394
-
395
- st.divider()
 
 
 
 
 
 
 
 
 
396
 
397
- # Game-specific rendering
398
- if concept == "loop":
399
- st.markdown(f"<h4 style='text-align:center;'>{config.get('instructions', 'Complete the sequence!')}</h4>", unsafe_allow_html=True)
400
-
401
- # Display sequence to remember
402
- seq_emojis = " โ†’ ".join(["โญ" for _ in game_state["sequence"]])
403
- st.markdown(f"<h3 style='text-align:center;'>{seq_emojis}</h3>", unsafe_allow_html=True)
404
-
405
- # Game board
406
- st.markdown("<div class='game-board'>", unsafe_allow_html=True)
407
- cols = st.columns(3)
408
- for i in range(9):
409
- with cols[i % 3]:
410
- if st.button("โญ" if i in game_state["sequence"] else "โœจ",
411
- key=f"cell_{i}",
412
- use_container_width=True,
413
- disabled=game_state["completed"]):
414
- if i == game_state["sequence"][game_state["current_step"]]:
415
- game_state["current_step"] += 1
416
- game_state["score"] += 10
417
-
418
- if game_state["current_step"] >= len(game_state["sequence"]):
419
- game_state["completed"] = True
420
- game_state["score"] += 50
421
- else:
422
- game_state["score"] = max(0, game_state["score"] - 5)
423
- st.markdown("</div>", unsafe_allow_html=True)
424
-
425
- elif concept == "conditional":
426
- st.markdown(f"<h4 style='text-align:center;'>It's {game_state['current_condition']} today! Where should we go?</h4>", unsafe_allow_html=True)
427
 
428
- for path in game_state["paths"]:
429
- if st.button(f"If {path['condition']} โ†’ Go to {path['result']}",
430
- key=f"path_{path['condition']}",
431
- use_container_width=True,
432
- disabled=game_state["completed"]):
433
- if path["condition"] == game_state["current_condition"]:
434
- game_state["score"] += 20
435
- game_state["completed"] = True
436
- else:
437
- game_state["score"] = max(0, game_state["score"] - 5)
438
-
439
- elif concept == "function":
440
- st.markdown(f"<h4 style='text-align:center;'>Create this spell: {' '.join(game_state['target_spell'])}</h4>", unsafe_allow_html=True)
441
-
442
- # Current spell
443
- current_spell = " ".join(game_state["current_spell"]) if game_state["current_spell"] else "Empty"
444
- st.markdown(f"<h5 style='text-align:center;'>Your Spell: {current_spell}</h5>", unsafe_allow_html=True)
445
-
446
- # Spell components
447
- st.markdown("<div style='display:flex; justify-content:center; gap:10px; margin:20px;'>", unsafe_allow_html=True)
448
- for component in game_state["spell_components"]:
449
- if st.button(component, key=f"comp_{component}"):
450
- if len(game_state["current_spell"]) < 3:
451
- game_state["current_spell"].append(component)
452
- st.markdown("</div>", unsafe_allow_html=True)
453
 
454
- # Spell actions
455
- col1, col2 = st.columns(2)
456
- with col1:
457
- if st.button("Cast Spell", use_container_width=True, disabled=len(game_state["current_spell"]) == 0):
458
- if game_state["current_spell"] == game_state["target_spell"]:
459
- game_state["score"] += 30
460
- game_state["completed"] = True
461
- else:
462
- game_state["score"] = max(0, game_state["score"] - 5)
463
- with col2:
464
- if st.button("Clear Spell", use_container_width=True, disabled=len(game_state["current_spell"]) == 0):
465
- game_state["current_spell"] = []
466
-
467
- # Game completion
468
- if game_state["completed"]:
469
- st.balloons()
470
- st.success(f"๐ŸŽ‰ Level Complete! Final Score: {game_state['score']}")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
471
 
472
- if st.button("Play Again", use_container_width=True):
473
- game_state.update(create_game_state(concept))
474
- game_state["level"] += 1
475
-
476
- return game_state
 
 
 
 
 
 
 
 
 
477
 
478
  def main():
479
  """Main application function"""
480
- st.title("๐ŸŽฎ StoryCoder: Learn Coding Through Games & Stories!")
481
- st.subheader("Create a story, play a game, and discover coding secrets with voice explanations!")
482
 
483
  # Initialize session state
484
  if 'story' not in st.session_state:
485
  st.session_state.story = ""
486
  if 'concepts' not in st.session_state:
487
  st.session_state.concepts = []
488
- if 'story_image' not in st.session_state:
489
- st.session_state.story_image = None
490
- if 'game_state' not in st.session_state:
491
- st.session_state.game_state = None
492
  if 'concept_explanation' not in st.session_state:
493
  st.session_state.concept_explanation = ""
494
  if 'audio_file' not in st.session_state:
@@ -501,34 +622,38 @@ def main():
501
  # Create tabs
502
  tabs = st.empty()
503
  tab_cols = st.columns(5)
504
- tab_names = ["๐Ÿ“– Story", "๐ŸŽฎ Game", "๐ŸŽ“ Learn", "๐Ÿ’ป Code", "๐Ÿ”„ Reset"]
505
-
506
- for i, tab_name in enumerate(tab_names):
507
- with tab_cols[i]:
508
- if st.button(tab_name, use_container_width=True):
509
- if tab_name == "๐Ÿ”„ Reset":
510
- st.session_state.story = ""
511
- st.session_state.concepts = []
512
- st.session_state.story_image = None
513
- st.session_state.game_state = None
514
- st.session_state.concept_explanation = ""
515
- st.session_state.audio_file = None
516
- st.session_state.explanation_audio = None
517
- st.session_state.active_tab = "story"
518
- else:
519
- st.session_state.active_tab = tab_name.split()[0].lower()
 
 
 
 
 
520
 
521
  # Story creation tab
522
  if st.session_state.active_tab == "story":
523
  with st.container():
524
- st.markdown("<div class='story-box'>", unsafe_allow_html=True)
525
  st.header("๐Ÿ“– Create Your Story")
526
- st.write("Write a short story (2-3 sentences) and I'll turn it into a coding game!")
527
 
528
  story = st.text_area(
529
  "Your story:",
530
- height=150,
531
- placeholder="Once upon a time, a rabbit hopped 3 times to reach a magic carrot...",
532
  value=st.session_state.story,
533
  key="story_input"
534
  )
@@ -541,22 +666,26 @@ def main():
541
  with st.spinner("๐Ÿง  Analyzing your story for coding concepts..."):
542
  st.session_state.concepts = analyze_story(story)
543
 
544
- # Get the main concept
545
- main_concept = st.session_state.concepts[0] if st.session_state.concepts else "loop"
546
-
547
- with st.spinner("๐ŸŽจ Creating story image..."):
548
- st.session_state.story_image = create_story_image(story, main_concept)
549
 
550
- with st.spinner("๐ŸŽฎ Creating your game..."):
551
- st.session_state.game_state = create_game_state(main_concept)
 
552
 
553
  with st.spinner("๐Ÿง  Generating concept explanation..."):
554
- st.session_state.concept_explanation = create_concept_explanation(story, main_concept)
 
 
 
555
 
556
- with st.spinner("๐Ÿ”Š Creating audio explanation..."):
557
- audio_file = text_to_speech(st.session_state.concept_explanation)
558
- if audio_file:
559
- st.session_state.explanation_audio = audio_file
560
 
561
  st.session_state.active_tab = "game"
562
  st.rerun()
@@ -567,177 +696,172 @@ def main():
567
  with col1:
568
  st.caption("Loop Example")
569
  st.code('"A dragon breathes fire 5 times at the castle"', language="text")
570
- st.image("https://api.dicebear.com/7.x/lorelei/png?seed=dragon",
571
- use_column_width=True,
572
- caption="Dragon Story Character")
573
  with col2:
574
  st.caption("Conditional Example")
575
  st.code('"If it rains, the cat stays inside, else it goes out"', language="text")
576
- st.image("https://api.dicebear.com/7.x/lorelei/png?seed=cat",
577
- use_column_width=True,
578
- caption="Cat Story Character")
579
  with col3:
580
  st.caption("Function Example")
581
  st.code('"A wizard casts a spell to make flowers grow"', language="text")
582
- st.image("https://api.dicebear.com/7.x/lorelei/png?seed=wizard",
583
- use_column_width=True,
584
- caption="Wizard Story Character")
585
- st.markdown("</div>", unsafe_allow_html=True)
586
 
587
  # Game tab
588
  elif st.session_state.active_tab == "game":
 
 
589
  if not st.session_state.story:
590
  st.warning("Please create a story first!")
591
  st.session_state.active_tab = "story"
592
  st.rerun()
593
 
594
- with st.container():
595
- st.markdown("<div class='story-box'>", unsafe_allow_html=True)
596
- st.header("๐ŸŽฎ Your Coding Game")
 
 
 
 
 
 
 
 
 
 
 
 
 
597
 
598
- # Display story image
599
- if st.session_state.story_image:
600
- st.image(st.session_state.story_image, use_column_width=True)
 
 
 
 
 
 
601
 
602
- # Display game
603
- if st.session_state.game_state:
604
- st.session_state.game_state = render_game(st.session_state.game_state)
 
 
 
 
 
 
 
605
 
606
- # Play explanation audio
607
  if st.session_state.explanation_audio:
608
- st.subheader("๐Ÿ”Š Concept Explanation")
 
 
 
 
609
  if st.button("โ–ถ๏ธ Play Explanation", use_container_width=True):
610
- if play_audio(st.session_state.explanation_audio):
611
- st.success("Playing audio explanation...")
612
- else:
613
- autoplay_audio(st.session_state.explanation_audio)
614
-
615
- st.markdown("</div>", unsafe_allow_html=True)
616
 
617
- # Learn tab
618
- elif st.session_state.active_tab == "learn":
619
- if not st.session_state.story:
620
- st.warning("Please create a story first!")
621
- st.session_state.active_tab = "story"
622
- st.rerun()
623
 
624
- with st.container():
625
- st.markdown("<div class='story-box'>", unsafe_allow_html=True)
626
- st.header("๐ŸŽ“ Learn Programming Concepts")
627
-
628
- if st.session_state.concepts:
629
- main_concept = st.session_state.concepts[0]
630
- concept_info = CONCEPTS.get(main_concept, {})
631
-
632
- # Concept explanation
633
- st.markdown(f"""
634
- <div class="explanation-box">
635
- <div class="explanation-header">
636
- <span style="font-size:36px;">{concept_info.get('emoji', 'โœจ')}</span>
637
- <h2>Understanding {concept_info.get('name', 'Programming')}</h2>
638
- </div>
639
- <p>{concept_info.get('explanation', 'This is a fundamental programming concept.')}</p>
640
-
641
- <h3>How It Works in Code</h3>
642
- <div class="code-block">
643
- {concept_info.get('example', 'Example code will appear here')}
644
  </div>
645
-
646
- <h3>Real-World Example</h3>
647
- <p>In your story: "{st.session_state.story[:100]}..."</p>
648
-
649
- <h3>How You Used It in the Game</h3>
650
- <p>{st.session_state.concept_explanation.split('In your game, you\'ll:')[-1]}</p>
651
- </div>
652
- """, unsafe_allow_html=True)
653
-
654
- # Play explanation audio
655
- if st.session_state.explanation_audio:
656
- st.subheader("๐Ÿ”Š Full Audio Explanation")
657
- if st.button("โ–ถ๏ธ Play Full Explanation", use_container_width=True):
658
- if play_audio(st.session_state.explanation_audio):
659
- st.success("Playing audio explanation...")
660
- else:
661
- autoplay_audio(st.session_state.explanation_audio)
662
- else:
663
- st.warning("No concepts detected in your story!")
664
 
665
- st.markdown("</div>", unsafe_allow_html=True)
 
666
 
667
- # Code tab
668
- elif st.session_state.active_tab == "code":
669
- if not st.session_state.story:
670
- st.warning("Please create a story first!")
 
 
671
  st.session_state.active_tab = "story"
672
  st.rerun()
673
 
674
- with st.container():
675
- st.markdown("<div class='story-box'>", unsafe_allow_html=True)
676
- st.header("๐Ÿ’ป See the Code Behind Your Game")
 
 
677
 
678
- if st.session_state.concepts:
679
- main_concept = st.session_state.concepts[0]
680
- concept_info = CONCEPTS.get(main_concept, {})
681
-
682
- st.subheader(f"{concept_info.get('name', 'Programming')} Concept in Action")
683
-
684
- st.markdown("""
685
- <div class="code-block">
686
- # Python code for your game
687
- import game_engine
688
-
689
- def main():
690
- # Initialize game based on player's story
691
- game = game_engine.create_game(story)
692
-
693
- # Game loop
694
- while game.running:
695
- # Handle player input
696
- handle_events()
697
-
698
- # Update game state
699
- update_game()
700
-
701
- # Render everything
702
- render_screen()
703
-
704
- # Game completion
705
- show_results()
706
- </div>
707
- """, unsafe_allow_html=True)
708
-
709
- st.markdown(f"""
710
- <h3>How the {concept_info.get('name', 'Programming')} Concept is Implemented</h3>
711
- <div class="code-block">
712
- # {concept_info.get('name', 'Programming')} implementation
713
- {concept_info.get('example', 'Example code will appear here')}
714
- </div>
715
- """, unsafe_allow_html=True)
716
-
717
- st.info("๐Ÿ’ก To create games like this:")
718
- st.markdown("""
719
- 1. Learn Python programming
720
- 2. Explore game libraries like Pygame
721
- 3. Practice by modifying simple games
722
- 4. Join coding communities for kids
723
- """)
724
-
725
- st.markdown("""
726
- <div class="explanation-box">
727
- <h3>Next Steps in Your Coding Journey</h3>
728
- <p>You've taken your first step in game development! To continue:</p>
729
- <ul>
730
- <li>Try creating your own simple game with Scratch</li>
731
- <li>Explore Python game tutorials online</li>
732
- <li>Join a coding club at your school</li>
733
- <li>Attend a game development workshop</li>
734
- </ul>
735
  </div>
736
- """, unsafe_allow_html=True)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
737
  else:
738
- st.warning("No concepts detected in your story!")
 
 
739
 
740
- st.markdown("</div>", unsafe_allow_html=True)
 
 
 
 
 
 
 
 
741
 
742
  if __name__ == "__main__":
743
  main()
 
1
+ # app.py - Learn Python Through Interactive Games
2
  import streamlit as st
3
  import os
4
  import time
5
  import random
 
 
 
 
 
 
 
 
6
  import numpy as np
7
  import pandas as pd
8
  import plotly.express as px
9
+ import plotly.graph_objects as go
10
+ from gtts import gTTS
11
+ import base64
12
+ import json
13
+ import requests
14
+ from io import BytesIO
15
+ import pygame
16
+ import sys
17
+ import math
18
+ from PIL import Image, ImageDraw
19
+ import io
20
 
21
  # Configure Streamlit page
22
  st.set_page_config(
23
+ page_title="StoryCoder - Learn Python Through Games",
24
  page_icon="๐ŸŽฎ",
25
  layout="wide",
26
  initial_sidebar_state="expanded"
 
33
 
34
  :root {
35
  --primary: #7E57C2;
36
+ --secondary: #B39DDB;
37
  --accent: #D1C4E9;
38
  --dark: #4527A0;
39
  --light: #F3E5F5;
40
+ --neutral: #E0E0E0;
41
  }
42
 
43
  body {
44
+ background: linear-gradient(135deg, var(--light) 0%, #EDE7F6 100%);
45
  font-family: 'Comic Neue', cursive;
46
+ color: #333;
47
  }
48
 
49
  .stApp {
50
  background: url('https://www.transparenttextures.com/patterns/cartographer.png');
 
 
 
 
 
 
 
51
  }
52
 
53
  .story-box {
54
  background-color: white;
55
  border-radius: 20px;
56
  padding: 25px;
57
+ box-shadow: 0 8px 16px rgba(69, 39, 160, 0.15);
58
  border: 3px solid var(--accent);
59
  margin-bottom: 25px;
60
+ }
61
+
62
+ .header {
63
+ color: var(--dark);
64
+ text-shadow: 2px 2px 4px rgba(0,0,0,0.1);
65
+ font-family: 'Fredoka One', cursive;
66
  }
67
 
68
  .concept-card {
69
+ background: linear-gradient(145deg, #ffffff, #f5f5f5);
70
  border-radius: 15px;
71
+ padding: 20px;
72
+ margin: 15px 0;
73
+ border-left: 5px solid var(--primary);
74
+ box-shadow: 0 4px 12px rgba(126, 87, 194, 0.1);
75
  }
76
 
77
  .stButton>button {
78
  background: linear-gradient(45deg, var(--primary), var(--secondary));
79
  color: white;
80
+ border-radius: 20px;
81
+ padding: 12px 28px;
82
  font-weight: bold;
83
  font-size: 18px;
84
  border: none;
 
88
 
89
  .stButton>button:hover {
90
  transform: scale(1.05);
91
+ box-shadow: 0 6px 12px rgba(126, 87, 194, 0.2);
92
  }
93
 
94
+ .stTextInput>div>div>input {
95
+ border-radius: 15px;
96
+ padding: 14px;
97
+ border: 2px solid var(--accent);
98
+ font-size: 16px;
 
 
99
  }
100
 
101
+ .tabs {
102
+ display: flex;
 
103
  gap: 10px;
104
+ margin-bottom: 20px;
105
+ overflow-x: auto;
106
  }
107
 
108
+ .tab {
109
+ padding: 12px 24px;
110
+ background-color: var(--accent);
111
+ border-radius: 15px;
 
 
 
 
112
  cursor: pointer;
113
+ font-weight: bold;
114
+ white-space: nowrap;
115
+ font-family: 'Fredoka One', cursive;
116
  transition: all 0.3s;
117
  }
118
 
119
+ .tab:hover {
120
+ background-color: var(--secondary);
 
121
  }
122
 
123
+ .tab.active {
124
+ background-color: var(--primary);
125
  color: white;
126
+ box-shadow: 0 4px 8px rgba(0,0,0,0.1);
127
  }
128
 
129
+ @media (max-width: 768px) {
130
+ .tabs {
131
+ flex-wrap: wrap;
132
+ }
133
+ }
134
+
135
+ .game-container {
136
+ background-color: #f9f5ff;
137
+ border-radius: 20px;
138
+ padding: 25px;
139
+ box-shadow: 0 8px 32px rgba(126, 87, 194, 0.15);
140
+ margin-bottom: 30px;
141
+ position: relative;
142
+ overflow: hidden;
143
+ border: 3px solid var(--accent);
144
+ }
145
+
146
+ .game-canvas {
147
  border-radius: 15px;
148
+ overflow: hidden;
149
+ margin: 0 auto;
150
+ display: block;
151
+ max-width: 100%;
152
+ background: #fff;
153
+ box-shadow: 0 6px 16px rgba(0,0,0,0.1);
154
+ }
155
+
156
+ .character {
157
+ font-size: 48px;
158
+ text-align: center;
159
+ margin: 10px 0;
160
+ }
161
+
162
+ .audio-player {
163
+ width: 100%;
164
  margin: 20px 0;
165
+ border-radius: 20px;
166
+ background: #f0f8ff;
167
+ padding: 15px;
168
+ }
169
+
170
+ .game-preview {
171
+ max-width: 100%;
172
+ border-radius: 15px;
173
+ box-shadow: 0 8px 24px rgba(0,0,0,0.15);
174
+ border: 3px solid var(--accent);
175
+ }
176
+
177
+ .explanation-box {
178
+ background: linear-gradient(135deg, #f3e5f5, #e8daf0);
179
+ border-radius: 20px;
180
+ padding: 25px;
181
+ margin: 25px 0;
182
  border-left: 5px solid var(--primary);
183
+ box-shadow: 0 6px 20px rgba(0,0,0,0.08);
184
  }
185
 
186
  .explanation-header {
187
  color: var(--dark);
188
  display: flex;
189
  align-items: center;
190
+ gap: 15px;
191
+ margin-bottom: 15px;
192
  }
193
 
194
  .code-block {
195
+ background: #2d2d2d;
196
+ color: #f8f8f2;
 
197
  border-radius: 10px;
198
+ padding: 15px;
199
  font-family: 'Courier New', monospace;
200
  margin: 15px 0;
201
  overflow-x: auto;
202
  }
203
 
204
+ .concept-highlight {
205
+ background: linear-gradient(120deg, #e0d6f0, #d1c4e9);
206
+ padding: 5px 10px;
207
+ border-radius: 8px;
 
 
 
 
 
 
 
 
208
  font-weight: bold;
209
+ color: var(--dark);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
210
  }
211
  </style>
212
  """, unsafe_allow_html=True)
213
 
214
+ # Concept database
215
  CONCEPTS = {
216
  "loop": {
217
  "name": "Loop",
218
  "emoji": "๐Ÿ”„",
219
  "description": "Loops repeat actions multiple times",
220
+ "example": "for i in range(5):\n print('Hello!')",
221
  "color": "#7E57C2",
222
  "explanation": "A loop is like a magic spell that makes something happen again and again. In programming, we use loops when we want to repeat an action multiple times without writing the same code over and over."
223
  },
 
225
  "name": "Conditional",
226
  "emoji": "โ“",
227
  "description": "Conditionals make decisions in code",
228
+ "example": "if sunny:\n go_outside()\nelse:\n stay_inside()",
229
+ "color": "#5E35B1",
230
  "explanation": "Conditionals are like crossroads in a story where you choose which path to take. In programming, we use 'if' statements to make decisions based on certain conditions, just like choosing what to wear based on the weather."
231
  },
232
  "function": {
233
  "name": "Function",
234
  "emoji": "โœจ",
235
  "description": "Functions are reusable blocks of code",
236
+ "example": "def greet(name):\n print(f'Hello {name}!')",
237
+ "color": "#4527A0",
238
  "explanation": "Functions are like magic spells you can create and reuse whenever you need them. They help us organize code into reusable blocks so we don't have to write the same thing multiple times."
239
  },
240
  "variable": {
241
  "name": "Variable",
242
  "emoji": "๐Ÿ“ฆ",
243
  "description": "Variables store information",
244
+ "example": "score = 10\nplayer = 'Alex'",
245
+ "color": "#7B1FA2",
246
  "explanation": "Variables are like labeled boxes where you can store information. They help us remember values and use them later in our code, just like remembering a character's name in a story."
247
+ },
248
+ "list": {
249
+ "name": "List",
250
+ "emoji": "๐Ÿ“",
251
+ "description": "Lists store collections of items",
252
+ "example": "fruits = ['apple', 'banana', 'orange']",
253
+ "color": "#6A1B9A",
254
+ "explanation": "Lists are like magical scrolls that can hold multiple items. In programming, we use lists to store collections of related things, like a wizard's inventory of potions."
255
  }
256
  }
257
 
258
+ # Game templates
259
+ GAME_TEMPLATES = {
260
  "loop": {
261
+ "name": "Loop Adventure",
262
+ "description": "Help the character repeat actions multiple times to achieve a goal",
263
+ "color": "#7E57C2",
264
+ "instructions": "Press the action button multiple times to complete the task!"
265
  },
266
  "conditional": {
267
+ "name": "Decision Quest",
268
+ "description": "Make choices based on conditions to progress through the story",
269
+ "color": "#5E35B1",
270
+ "instructions": "Choose the correct path based on the conditions you see!"
271
  },
272
  "function": {
273
+ "name": "Magic Function",
274
+ "description": "Create and use reusable actions to solve challenges",
275
+ "color": "#4527A0",
276
+ "instructions": "Create your function then use it whenever needed!"
277
  }
278
  }
279
 
280
+ # Game assets
281
+ GAME_ASSETS = {
282
+ "player": "๐Ÿ‘ฆ",
283
+ "obstacle": "๐ŸŒต",
284
+ "goal": "๐Ÿ†",
285
+ "enemy": "๐Ÿ‰",
286
+ "item": "โญ",
287
+ "path": "๐ŸŸฉ",
288
+ "wall": "โฌ›"
289
+ }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
290
 
291
  def analyze_story(story):
292
  """Analyze story and identify programming concepts"""
 
298
  detected_concepts.append("loop")
299
 
300
  # Check for conditionals
301
+ if any(word in story_lower for word in ["if", "when", "unless", "whether"]):
302
  detected_concepts.append("conditional")
303
 
304
  # Check for functions
 
306
  detected_concepts.append("function")
307
 
308
  # Check for variables
309
+ if any(word in story_lower for word in ["is", "has", "set to", "value"]):
310
  detected_concepts.append("variable")
311
 
312
+ # Check for lists
313
+ if any(word in story_lower for word in ["and", "many", "several", "collection", "items"]):
314
+ detected_concepts.append("list")
315
+
316
  return list(set(detected_concepts))
317
 
318
+ def extract_count_from_story(story):
319
+ """Extract a number from the story to use in games"""
320
+ for word in story.split():
321
+ if word.isdigit():
322
+ return min(int(word), 10)
323
+ return 3 # Default value
324
+
325
+ def create_game_preview(concept):
326
+ """Create a preview image for the game"""
327
+ width, height = 400, 300
328
+ img = Image.new('RGB', (width, height), color=(243, 229, 245))
329
+ draw = ImageDraw.Draw(img)
330
+
331
+ # Draw game elements based on concept
332
+ if concept == "loop":
333
+ # Draw a path with repeated obstacles
334
+ for i in range(5):
335
+ x = 80 + i * 60
336
+ y = 150
337
+ draw.rectangle([x, y, x+30, y+30], fill=(126, 87, 194))
338
+ draw.text((x+10, y+5), GAME_ASSETS["obstacle"], fill=(255, 255, 255), font_size=20)
339
 
340
+ # Draw player and goal
341
+ draw.text((50, 150), GAME_ASSETS["player"], fill=(69, 39, 160), font_size=30)
342
+ draw.text((350, 150), GAME_ASSETS["goal"], fill=(255, 193, 7), font_size=30)
343
+
344
+ # Draw title
345
+ draw.text((100, 50), "Loop Adventure", fill=(69, 39, 160), font_size=25)
346
+
347
+ elif concept == "conditional":
348
+ # Draw two paths
349
+ draw.rectangle([100, 100, 180, 180], fill=(93, 64, 155))
350
+ draw.rectangle([220, 100, 300, 180], fill=(93, 64, 155))
351
+
352
+ # Draw weather symbols
353
+ draw.text((130, 110), "โ˜€๏ธ", fill=(255, 255, 0), font_size=30)
354
+ draw.text((250, 110), "๐ŸŒง๏ธ", fill=(100, 181, 246), font_size=30)
355
+
356
+ # Draw items
357
+ draw.text((130, 150), "๐Ÿ˜Ž", fill=(255, 255, 255), font_size=30)
358
+ draw.text((250, 150), "โ˜‚๏ธ", fill=(255, 255, 255), font_size=30)
359
+
360
+ # Draw title
361
+ draw.text((120, 50), "Decision Quest", fill=(69, 39, 160), font_size=25)
362
+
363
+ else: # function
364
+ # Draw magic wand and effects
365
+ draw.text((100, 150), "๐Ÿช„", fill=(126, 87, 194), font_size=40)
366
+
367
+ # Draw magic effects
368
+ for i in range(3):
369
+ x = 180 + i * 60
370
+ y = 150
371
+ draw.ellipse([x, y, x+40, y+40], fill=(179, 157, 219))
372
+ draw.text((x+10, y+5), "โœจ", fill=(255, 255, 255), font_size=30)
373
+
374
+ # Draw title
375
+ draw.text((130, 50), "Magic Function", fill=(69, 39, 160), font_size=25)
376
+
377
+ # Convert to bytes
378
+ buf = io.BytesIO()
379
+ img.save(buf, format='PNG')
380
+ buf.seek(0)
381
+ return buf
382
+
383
+ def text_to_speech(text, filename="explanation.wav"):
384
+ """Convert text to speech using gTTS"""
385
+ try:
386
+ tts = gTTS(text=text, lang='en')
387
+ tts.save(filename)
388
+ return filename
389
+ except Exception as e:
390
+ st.error(f"Audio creation error: {str(e)}")
391
  return None
392
 
393
+ def autoplay_audio(file_path):
394
+ """Autoplay audio in Streamlit"""
395
+ with open(file_path, "rb") as f:
396
+ data = f.read()
397
+ b64 = base64.b64encode(data).decode()
398
+ md = f"""
399
+ <audio autoplay>
400
+ <source src="data:audio/wav;base64,{b64}" type="audio/wav">
401
+ </audio>
402
+ """
403
+ st.markdown(md, unsafe_allow_html=True)
404
+
405
+ def generate_concept_explanation(story, concept):
406
+ """Generate a detailed explanation of the programming concept with examples"""
407
  concept_info = CONCEPTS.get(concept, {})
408
+ count = extract_count_from_story(story)
409
 
410
  explanation = f"""
411
+ Let me explain what's happening in your story and how it relates to programming!
412
 
413
  Your story: "{story[:100]}..."
414
 
415
+ This story demonstrates the programming concept of: <span class="concept-highlight">{concept_info.get('name', 'Programming')}</span>
416
 
417
  {concept_info.get('explanation', 'This is a fundamental programming concept.')}
418
 
419
+ In your game:
420
+ - You'll see how {concept_info.get('name', 'this concept')} works in action
421
+ - The game is designed around the idea from your story
422
+
423
+ How it works in code:
424
+ We use {concept_info.get('name', 'this concept')} like this:
425
+ """
426
+
427
+ # Add code example
428
+ explanation += f"""<div class="code-block">{concept_info.get('example', 'Example code will appear here')}</div>"""
429
 
430
+ explanation += f"""
431
+ In real life, you might use this concept when:
432
+ - Creating games with repeating actions (loops)
433
+ - Making decisions in apps (conditionals)
434
+ - Building reusable components (functions)
435
 
436
+ As you play the game, think about how the concept is being used!
437
  """
438
 
439
  return explanation
440
 
441
+ def create_loop_game(story):
442
+ """Create a loop-based game"""
443
+ actions_needed = extract_count_from_story(story)
 
 
 
 
 
444
 
445
+ # Initialize session state for game
446
+ if 'loop_count' not in st.session_state:
447
+ st.session_state.loop_count = 0
448
+ st.session_state.game_complete = False
449
+
450
+ # Game layout
451
+ st.subheader("๐Ÿ”„ Loop Adventure Game")
452
+ st.write(f"**Story:** {story[:100]}{'...' if len(story) > 100 else ''}")
453
+ st.write("**Instructions:** Press the action button repeatedly to complete the task!")
454
+
455
+ # Create game grid
456
+ grid_html = "<div style='display: flex; justify-content: center; margin: 20px 0;'>"
457
+
458
+ # Player position
459
+ player_pos = min(st.session_state.loop_count, actions_needed)
460
+ player_offset = player_pos * (100 // actions_needed)
461
+
462
+ # Draw game path
463
+ grid_html += f"<div style='position: relative; width: 400px; height: 100px; background: #EDE7F6; border-radius: 10px; border: 2px solid {CONCEPTS['loop']['color']}'>"
464
+
465
+ # Draw path
466
+ grid_html += f"<div style='position: absolute; top: 40px; left: 0; width: 100%; height: 20px; background: {CONCEPTS['loop']['color']}; border-radius: 10px;'></div>"
467
+
468
+ # Draw obstacles
469
+ for i in range(actions_needed):
470
+ grid_html += f"<div style='position: absolute; top: 30px; left: {i * (400 // actions_needed)}px; font-size: 24px;'>๐ŸŒต</div>"
471
+
472
+ # Draw player
473
+ grid_html += f"<div style='position: absolute; top: 20px; left: {player_offset}px; font-size: 36px;'>๐Ÿ‘ฆ</div>"
474
+
475
+ # Draw goal
476
+ grid_html += f"<div style='position: absolute; top: 20px; left: 380px; font-size: 36px;'>๐Ÿ†</div>"
477
+
478
+ grid_html += "</div></div>"
479
+
480
+ st.markdown(grid_html, unsafe_allow_html=True)
481
+
482
+ # Action button
483
+ if st.session_state.game_complete:
484
+ st.success("๐ŸŽ‰ You completed the game! Great job using loops!")
485
+ if st.button("Play Again", key="loop_restart"):
486
+ st.session_state.loop_count = 0
487
+ st.session_state.game_complete = False
488
+ st.rerun()
489
+ else:
490
+ if st.button(f"Perform Action ({st.session_state.loop_count}/{actions_needed})", key="loop_action"):
491
+ st.session_state.loop_count += 1
492
+ if st.session_state.loop_count >= actions_needed:
493
+ st.session_state.game_complete = True
494
+ st.rerun()
495
 
496
+ def create_conditional_game(story):
497
+ """Create a conditional-based game"""
498
+ # Initialize session state for game
499
+ if 'conditional_choice' not in st.session_state:
500
+ st.session_state.conditional_choice = None
501
+ st.session_state.game_complete = False
502
+ st.session_state.weather = random.choice(["sunny", "rainy"])
503
+
504
+ # Game layout
505
+ st.subheader("โ“ Decision Quest Game")
506
+ st.write(f"**Story:** {story[:100]}{'...' if len(story) > 100 else ''}")
507
+ st.write("**Instructions:** Choose the correct item based on the weather condition!")
508
+
509
+ # Display weather
510
+ col1, col2 = st.columns([1, 2])
511
+ with col1:
512
+ st.subheader("Current Weather:")
513
+ if st.session_state.weather == "sunny":
514
+ st.markdown("<div style='font-size: 48px; text-align: center;'>โ˜€๏ธ</div>", unsafe_allow_html=True)
515
+ st.write("It's a sunny day!")
516
+ else:
517
+ st.markdown("<div style='font-size: 48px; text-align: center;'>๐ŸŒง๏ธ</div>", unsafe_allow_html=True)
518
+ st.write("It's a rainy day!")
519
+
520
+ with col2:
521
+ st.subheader("Choose Your Item:")
522
 
523
+ if not st.session_state.game_complete:
524
+ if st.button("๐Ÿ˜Ž Sunglasses", key="sunglasses", use_container_width=True):
525
+ st.session_state.conditional_choice = "sunglasses"
526
+ st.session_state.game_complete = True
527
+ st.rerun()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
528
 
529
+ if st.button("โ˜‚๏ธ Umbrella", key="umbrella", use_container_width=True):
530
+ st.session_state.conditional_choice = "umbrella"
531
+ st.session_state.game_complete = True
532
+ st.rerun()
533
+ else:
534
+ if st.session_state.weather == "sunny" and st.session_state.conditional_choice == "sunglasses":
535
+ st.success("๐ŸŽ‰ Correct choice! You wore sunglasses on a sunny day!")
536
+ elif st.session_state.weather == "rainy" and st.session_state.conditional_choice == "umbrella":
537
+ st.success("๐ŸŽ‰ Correct choice! You took an umbrella on a rainy day!")
538
+ else:
539
+ st.error("โŒ Oops! That wasn't the right choice for this weather.")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
540
 
541
+ if st.button("Play Again", key="conditional_restart"):
542
+ st.session_state.conditional_choice = None
543
+ st.session_state.game_complete = False
544
+ st.session_state.weather = random.choice(["sunny", "rainy"])
545
+ st.rerun()
546
+
547
+ def create_function_game(story):
548
+ """Create a function-based game"""
549
+ actions_needed = extract_count_from_story(story)
550
+
551
+ # Initialize session state for game
552
+ if 'function_created' not in st.session_state:
553
+ st.session_state.function_created = False
554
+ st.session_state.function_used = 0
555
+ st.session_state.game_complete = False
556
+
557
+ # Game layout
558
+ st.subheader("โœจ Magic Function Game")
559
+ st.write(f"**Story:** {story[:100]}{'...' if len(story) > 100 else ''}")
560
+ st.write("**Instructions:** Create your function then use it to solve the challenge!")
561
+
562
+ # Game area
563
+ col1, col2 = st.columns(2)
564
+
565
+ with col1:
566
+ st.subheader("Your Function")
567
+ if not st.session_state.function_created:
568
+ st.write("Create your magic function:")
569
+ if st.button("โœจ Create Magic Function", key="create_func", use_container_width=True):
570
+ st.session_state.function_created = True
571
+ st.rerun()
572
+ else:
573
+ st.success("Function created!")
574
+ st.code("def magic_spell():\n print('โœจ Magic happens!')", language="python")
575
+
576
+ with col2:
577
+ st.subheader("Cast Your Spell")
578
+ if st.session_state.function_created:
579
+ st.write(f"Use your function to complete the task ({st.session_state.function_used}/{actions_needed}):")
580
+ if st.button("๐Ÿ”ฎ Cast Spell", key="cast_spell", use_container_width=True):
581
+ st.session_state.function_used += 1
582
+ if st.session_state.function_used >= actions_needed:
583
+ st.session_state.game_complete = True
584
+ st.rerun()
585
 
586
+ # Visual effects
587
+ effect_html = "<div style='text-align: center; margin-top: 20px;'>"
588
+ for i in range(st.session_state.function_used):
589
+ effect_html += "<span style='font-size: 36px; margin: 5px;'>โœจ</span>"
590
+ effect_html += "</div>"
591
+ st.markdown(effect_html, unsafe_allow_html=True)
592
+
593
+ if st.session_state.game_complete:
594
+ st.success("๐ŸŽ‰ You completed the game by reusing your function! Great job!")
595
+ if st.button("Play Again", key="function_restart"):
596
+ st.session_state.function_created = False
597
+ st.session_state.function_used = 0
598
+ st.session_state.game_complete = False
599
+ st.rerun()
600
 
601
  def main():
602
  """Main application function"""
603
+ st.title("๐ŸŽฎ StoryCoder - Learn Python Through Games!")
604
+ st.subheader("Turn your story into a game and discover coding secrets with interactive gameplay!")
605
 
606
  # Initialize session state
607
  if 'story' not in st.session_state:
608
  st.session_state.story = ""
609
  if 'concepts' not in st.session_state:
610
  st.session_state.concepts = []
611
+ if 'game_type' not in st.session_state:
612
+ st.session_state.game_type = None
 
 
613
  if 'concept_explanation' not in st.session_state:
614
  st.session_state.concept_explanation = ""
615
  if 'audio_file' not in st.session_state:
 
622
  # Create tabs
623
  tabs = st.empty()
624
  tab_cols = st.columns(5)
625
+ with tab_cols[0]:
626
+ if st.button("๐Ÿ“– Create Story"):
627
+ st.session_state.active_tab = "story"
628
+ with tab_cols[1]:
629
+ if st.button("๐ŸŽฎ Play Game"):
630
+ st.session_state.active_tab = "game"
631
+ with tab_cols[2]:
632
+ if st.button("๐Ÿ” Concepts"):
633
+ st.session_state.active_tab = "concepts"
634
+ with tab_cols[3]:
635
+ if st.button("๐Ÿ’ก Explain"):
636
+ st.session_state.active_tab = "explain"
637
+ with tab_cols[4]:
638
+ if st.button("๐Ÿ”„ Reset"):
639
+ st.session_state.story = ""
640
+ st.session_state.concepts = []
641
+ st.session_state.game_type = None
642
+ st.session_state.concept_explanation = ""
643
+ st.session_state.audio_file = None
644
+ st.session_state.explanation_audio = None
645
+ st.session_state.active_tab = "story"
646
 
647
  # Story creation tab
648
  if st.session_state.active_tab == "story":
649
  with st.container():
 
650
  st.header("๐Ÿ“– Create Your Story")
651
+ st.write("Write a short story (2-5 sentences) and I'll turn it into an interactive game!")
652
 
653
  story = st.text_area(
654
  "Your story:",
655
+ height=200,
656
+ placeholder="Once upon a time, a rabbit hopped 3 times to reach a carrot...",
657
  value=st.session_state.story,
658
  key="story_input"
659
  )
 
666
  with st.spinner("๐Ÿง  Analyzing your story for coding concepts..."):
667
  st.session_state.concepts = analyze_story(story)
668
 
669
+ if not st.session_state.concepts:
670
+ st.warning("No coding concepts detected! Using a default game.")
671
+ st.session_state.game_type = "loop"
672
+ else:
673
+ st.session_state.game_type = st.session_state.concepts[0]
674
 
675
+ with st.spinner("๐ŸŽฎ Creating your interactive game..."):
676
+ # Just a delay for effect
677
+ time.sleep(1)
678
 
679
  with st.spinner("๐Ÿง  Generating concept explanation..."):
680
+ st.session_state.concept_explanation = generate_concept_explanation(
681
+ story,
682
+ st.session_state.game_type
683
+ )
684
 
685
+ with st.spinner("๐Ÿ”Š Creating audio narration..."):
686
+ st.session_state.audio_file = text_to_speech(
687
+ f"Your story: {story}. Let's play a game to learn programming!"
688
+ )
689
 
690
  st.session_state.active_tab = "game"
691
  st.rerun()
 
696
  with col1:
697
  st.caption("Loop Example")
698
  st.code('"A dragon breathes fire 5 times at the castle"', language="text")
699
+ st.image(create_game_preview("loop"),
700
+ use_container_width=True,
701
+ caption="Loop Adventure Game Preview")
702
  with col2:
703
  st.caption("Conditional Example")
704
  st.code('"If it rains, the cat stays inside, else it goes out"', language="text")
705
+ st.image(create_game_preview("conditional"),
706
+ use_container_width=True,
707
+ caption="Decision Quest Game Preview")
708
  with col3:
709
  st.caption("Function Example")
710
  st.code('"A wizard casts a spell to make flowers grow"', language="text")
711
+ st.image(create_game_preview("function"),
712
+ use_container_width=True,
713
+ caption="Magic Function Game Preview")
 
714
 
715
  # Game tab
716
  elif st.session_state.active_tab == "game":
717
+ st.header(f"๐ŸŽฎ Your Story Game: {GAME_TEMPLATES.get(st.session_state.game_type, {}).get('name', 'Coding Adventure')}")
718
+
719
  if not st.session_state.story:
720
  st.warning("Please create a story first!")
721
  st.session_state.active_tab = "story"
722
  st.rerun()
723
 
724
+ # Display game based on concept
725
+ if st.session_state.game_type == "loop":
726
+ create_loop_game(st.session_state.story)
727
+ elif st.session_state.game_type == "conditional":
728
+ create_conditional_game(st.session_state.story)
729
+ elif st.session_state.game_type == "function":
730
+ create_function_game(st.session_state.story)
731
+ else:
732
+ # Default to loop game
733
+ create_loop_game(st.session_state.story)
734
+
735
+ # Concept explanation section
736
+ if st.session_state.concept_explanation:
737
+ st.subheader("๐ŸŽ“ What You're Learning")
738
+ concept = st.session_state.game_type
739
+ details = CONCEPTS.get(concept, {})
740
 
741
+ st.markdown(f"""
742
+ <div class="explanation-box">
743
+ <div class="explanation-header">
744
+ <span style="font-size:36px;">{details.get('emoji', 'โœจ')}</span>
745
+ <h3>Learning {details.get('name', 'Programming')} Concept</h3>
746
+ </div>
747
+ <p>{st.session_state.concept_explanation}</p>
748
+ </div>
749
+ """, unsafe_allow_html=True)
750
 
751
+ # Generate explanation audio if not already generated
752
+ if not st.session_state.explanation_audio:
753
+ with st.spinner("๐Ÿ”Š Creating concept explanation audio..."):
754
+ st.session_state.explanation_audio = text_to_speech(
755
+ st.session_state.concept_explanation.replace('<span class="concept-highlight">', '')
756
+ .replace('</span>', '')
757
+ .replace('<div class="code-block">', '')
758
+ .replace('</div>', ''),
759
+ "concept_explanation.wav"
760
+ )
761
 
 
762
  if st.session_state.explanation_audio:
763
+ st.markdown("### ๐Ÿ”Š Concept Explanation")
764
+ with open(st.session_state.explanation_audio, "rb") as f:
765
+ audio_bytes = f.read()
766
+ st.audio(audio_bytes, format='audio/wav')
767
+
768
  if st.button("โ–ถ๏ธ Play Explanation", use_container_width=True):
769
+ autoplay_audio(st.session_state.explanation_audio)
 
 
 
 
 
770
 
771
+ # Concepts tab
772
+ elif st.session_state.active_tab == "concepts":
773
+ st.header("๐Ÿ” Coding Concepts in Your Story")
 
 
 
774
 
775
+ if not st.session_state.concepts:
776
+ st.warning("No concepts detected in your story! Try adding words like '3 times', 'if', or 'make'.")
777
+ else:
778
+ st.subheader("We used these programming concepts in your game:")
779
+ for concept in st.session_state.concepts:
780
+ if concept in CONCEPTS:
781
+ details = CONCEPTS[concept]
782
+ st.markdown(f"""
783
+ <div class="concept-card">
784
+ <div style="display:flex; align-items:center; gap:15px;">
785
+ <span style="font-size:36px;">{details['emoji']}</span>
786
+ <h3 style="color:{details['color']};">{details['name']}</h3>
787
+ </div>
788
+ <p>{details['description']}</p>
789
+ <div class="code-block">{details['example']}</div>
790
+ <p><strong>Explanation:</strong> {details.get('explanation', 'Learn how this concept works!')}</p>
 
 
 
 
791
  </div>
792
+ """, unsafe_allow_html=True)
793
+
794
+ # Play explanation if available
795
+ if st.session_state.explanation_audio:
796
+ st.subheader("๐Ÿ”Š Concept Explanation")
797
+ with open(st.session_state.explanation_audio, "rb") as f:
798
+ audio_bytes = f.read()
799
+ st.audio(audio_bytes, format='audio/wav')
 
 
 
 
 
 
 
 
 
 
 
800
 
801
+ if st.button("โ–ถ๏ธ Play Full Explanation", use_container_width=True):
802
+ autoplay_audio(st.session_state.explanation_audio)
803
 
804
+ # Explanation tab
805
+ elif st.session_state.active_tab == "explain":
806
+ st.header("๐Ÿ’ก Interactive Concept Explanation")
807
+
808
+ if not st.session_state.concept_explanation:
809
+ st.warning("Please create a story first to generate explanations!")
810
  st.session_state.active_tab = "story"
811
  st.rerun()
812
 
813
+ st.subheader("๐Ÿง  Deep Dive into Programming Concepts")
814
+
815
+ if st.session_state.concepts:
816
+ concept = st.session_state.concepts[0]
817
+ details = CONCEPTS.get(concept, {})
818
 
819
+ st.markdown(f"""
820
+ <div class="explanation-box">
821
+ <div class="explanation-header">
822
+ <span style="font-size:36px;">{details.get('emoji', 'โœจ')}</span>
823
+ <h3>Understanding {details.get('name', 'Programming')} Concept</h3>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
824
  </div>
825
+ <p>{st.session_state.concept_explanation}</p>
826
+ </div>
827
+ """, unsafe_allow_html=True)
828
+
829
+ # Visual example
830
+ st.subheader("๐ŸŽฎ How It Works in Your Game")
831
+ if concept == "loop":
832
+ st.image("https://media.giphy.com/media/3o7abKhOpu0NwenH3O/giphy.gif",
833
+ use_column_width=True, caption="Loop Concept in Action")
834
+ elif concept == "conditional":
835
+ st.image("https://media.giphy.com/media/l0HlG8vJXW0X5yX4s/giphy.gif",
836
+ use_column_width=True, caption="Conditional Concept in Action")
837
+ else:
838
+ st.image("https://media.giphy.com/media/3o7TKsQ8UQ4l4LhGz6/giphy.gif",
839
+ use_column_width=True, caption="Function Concept in Action")
840
+
841
+ # Real-world examples
842
+ st.subheader("๐ŸŒ Real-World Examples")
843
+ if concept == "loop":
844
+ st.write("- Video games that have repeating levels or enemies")
845
+ st.write("- Apps that process multiple items (like photos in a gallery)")
846
+ st.write("- Animations that need to run continuously")
847
+ elif concept == "conditional":
848
+ st.write("- Weather apps that show different outfits based on temperature")
849
+ st.write("- Games that change difficulty based on player skill")
850
+ st.write("- Apps that display different content based on user preferences")
851
  else:
852
+ st.write("- Calculator apps with reusable operations")
853
+ st.write("- Games with special moves that can be used repeatedly")
854
+ st.write("- Websites with reusable components like buttons and menus")
855
 
856
+ # Play explanation audio
857
+ if st.session_state.explanation_audio:
858
+ st.subheader("๐Ÿ”Š Listen to the Explanation")
859
+ with open(st.session_state.explanation_audio, "rb") as f:
860
+ audio_bytes = f.read()
861
+ st.audio(audio_bytes, format='audio/wav')
862
+
863
+ if st.button("โ–ถ๏ธ Play Explanation", use_container_width=True):
864
+ autoplay_audio(st.session_state.explanation_audio)
865
 
866
  if __name__ == "__main__":
867
  main()