Spaces:
Running
Running
Update app.py
Browse files
app.py
CHANGED
@@ -1,68 +1,70 @@
|
|
1 |
-
# app.py -
|
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 |
from PIL import Image
|
13 |
import io
|
14 |
-
import matplotlib.pyplot as plt
|
15 |
import requests
|
16 |
-
from io import BytesIO
|
17 |
import json
|
|
|
|
|
|
|
|
|
|
|
|
|
18 |
|
19 |
# Configure Streamlit page
|
20 |
st.set_page_config(
|
21 |
-
page_title="StoryCoder - Learn
|
22 |
-
page_icon="
|
23 |
layout="wide",
|
24 |
initial_sidebar_state="expanded"
|
25 |
)
|
26 |
|
27 |
-
# Custom CSS with
|
28 |
st.markdown("""
|
29 |
<style>
|
30 |
-
@import url('https://fonts.googleapis.com/css2?family=Comic+Neue:wght@700&family=
|
31 |
|
32 |
:root {
|
33 |
-
--primary: #
|
34 |
-
--secondary: #
|
35 |
-
--accent: #
|
36 |
-
--dark: #
|
37 |
-
--light: #
|
38 |
-
--
|
39 |
}
|
40 |
|
41 |
body {
|
42 |
-
background: linear-gradient(135deg, var(--
|
43 |
-
font-family: '
|
44 |
}
|
45 |
|
46 |
.stApp {
|
47 |
-
background: url('https://www.transparenttextures.com/patterns/
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
48 |
}
|
49 |
|
50 |
.story-box {
|
51 |
background-color: white;
|
52 |
border-radius: 20px;
|
53 |
padding: 25px;
|
54 |
-
box-shadow: 0 8px 16px rgba(
|
55 |
border: 3px solid var(--accent);
|
56 |
margin-bottom: 25px;
|
57 |
-
|
58 |
-
|
59 |
-
.header {
|
60 |
-
color: var(--dark);
|
61 |
-
text-shadow: 1px 1px 2px rgba(0,0,0,0.1);
|
62 |
}
|
63 |
|
64 |
.concept-card {
|
65 |
-
background: linear-gradient(145deg, #ffffff,
|
66 |
border-radius: 15px;
|
67 |
padding: 15px;
|
68 |
margin: 10px 0;
|
@@ -74,108 +76,61 @@ st.markdown("""
|
|
74 |
background: linear-gradient(45deg, var(--primary), var(--secondary));
|
75 |
color: white;
|
76 |
border-radius: 12px;
|
77 |
-
padding:
|
78 |
font-weight: bold;
|
79 |
font-size: 18px;
|
80 |
border: none;
|
81 |
transition: all 0.3s;
|
82 |
-
font-family: '
|
83 |
}
|
84 |
|
85 |
.stButton>button:hover {
|
86 |
-
transform:
|
87 |
-
box-shadow: 0 6px 12px rgba(
|
88 |
}
|
89 |
|
90 |
-
.
|
91 |
-
|
92 |
-
|
93 |
-
|
94 |
-
|
|
|
|
|
95 |
}
|
96 |
|
97 |
-
.
|
98 |
-
display:
|
|
|
99 |
gap: 10px;
|
100 |
-
margin
|
101 |
-
|
102 |
}
|
103 |
|
104 |
-
.
|
105 |
-
|
106 |
-
|
107 |
-
|
|
|
|
|
|
|
|
|
108 |
cursor: pointer;
|
109 |
-
font-weight: bold;
|
110 |
-
white-space: nowrap;
|
111 |
-
color: white;
|
112 |
-
transition: all 0.3s;
|
113 |
-
}
|
114 |
-
|
115 |
-
.tab:hover {
|
116 |
-
background-color: var(--primary);
|
117 |
-
}
|
118 |
-
|
119 |
-
.tab.active {
|
120 |
-
background-color: var(--primary);
|
121 |
box-shadow: 0 4px 8px rgba(0,0,0,0.1);
|
|
|
122 |
}
|
123 |
|
124 |
-
|
125 |
-
|
126 |
-
|
127 |
-
}
|
128 |
-
}
|
129 |
-
|
130 |
-
.animation-container {
|
131 |
-
background: linear-gradient(135deg, #d8bfd8, #e6e6fa);
|
132 |
-
border-radius: 15px;
|
133 |
-
padding: 20px;
|
134 |
-
box-shadow: 0 8px 32px rgba(75, 0, 130, 0.2);
|
135 |
-
margin-bottom: 25px;
|
136 |
-
position: relative;
|
137 |
-
overflow: hidden;
|
138 |
-
}
|
139 |
-
|
140 |
-
.animation-canvas {
|
141 |
-
border-radius: 10px;
|
142 |
-
overflow: hidden;
|
143 |
-
margin: 0 auto;
|
144 |
-
display: block;
|
145 |
-
max-width: 100%;
|
146 |
-
}
|
147 |
-
|
148 |
-
.character {
|
149 |
-
font-size: 48px;
|
150 |
-
text-align: center;
|
151 |
-
margin: 10px 0;
|
152 |
-
}
|
153 |
-
|
154 |
-
.audio-player {
|
155 |
-
width: 100%;
|
156 |
-
margin: 20px 0;
|
157 |
-
border-radius: 20px;
|
158 |
-
background: #f0e6ff;
|
159 |
-
padding: 15px;
|
160 |
-
}
|
161 |
-
|
162 |
-
.animation-gif {
|
163 |
-
max-width: 100%;
|
164 |
-
border-radius: 10px;
|
165 |
-
box-shadow: 0 8px 16px rgba(0,0,0,0.15);
|
166 |
-
border: 2px solid white;
|
167 |
}
|
168 |
|
169 |
-
.
|
170 |
-
|
171 |
-
|
172 |
-
padding: 15px;
|
173 |
-
background: white;
|
174 |
-
margin: 20px 0;
|
175 |
}
|
176 |
|
177 |
.explanation-box {
|
178 |
-
background: linear-gradient(135deg,
|
179 |
border-radius: 15px;
|
180 |
padding: 20px;
|
181 |
margin: 20px 0;
|
@@ -188,110 +143,143 @@ st.markdown("""
|
|
188 |
display: flex;
|
189 |
align-items: center;
|
190 |
gap: 10px;
|
191 |
-
|
192 |
}
|
193 |
|
194 |
-
.
|
195 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
196 |
}
|
197 |
|
198 |
-
|
199 |
-
|
200 |
-
|
201 |
-
|
|
|
202 |
}
|
203 |
|
204 |
-
.
|
205 |
-
|
206 |
-
|
207 |
-
border-radius:
|
|
|
208 |
font-weight: bold;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
209 |
}
|
210 |
</style>
|
211 |
""", unsafe_allow_html=True)
|
212 |
|
213 |
-
# Concept database
|
214 |
CONCEPTS = {
|
215 |
"loop": {
|
216 |
"name": "Loop",
|
217 |
"emoji": "🔄",
|
218 |
"description": "Loops repeat actions multiple times",
|
219 |
-
"example": "for i in range(5):\n print('Hello!')",
|
220 |
-
"color": "#
|
221 |
"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."
|
222 |
},
|
223 |
"conditional": {
|
224 |
"name": "Conditional",
|
225 |
"emoji": "❓",
|
226 |
"description": "Conditionals make decisions in code",
|
227 |
-
"example": "if
|
228 |
-
"color": "#
|
229 |
"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."
|
230 |
},
|
231 |
"function": {
|
232 |
"name": "Function",
|
233 |
"emoji": "✨",
|
234 |
"description": "Functions are reusable blocks of code",
|
235 |
-
"example": "def
|
236 |
-
"color": "#
|
237 |
"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."
|
238 |
},
|
239 |
"variable": {
|
240 |
"name": "Variable",
|
241 |
"emoji": "📦",
|
242 |
"description": "Variables store information",
|
243 |
-
"example": "score = 10\
|
244 |
-
"color": "#
|
245 |
"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."
|
246 |
-
},
|
247 |
-
"list": {
|
248 |
-
"name": "List",
|
249 |
-
"emoji": "📝",
|
250 |
-
"description": "Lists store collections of items",
|
251 |
-
"example": "fruits = ['apple', 'banana', 'orange']",
|
252 |
-
"color": "#9932CC",
|
253 |
-
"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."
|
254 |
}
|
255 |
}
|
256 |
|
257 |
-
#
|
258 |
-
|
259 |
-
"loop":
|
260 |
-
|
261 |
-
|
262 |
-
|
263 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
264 |
}
|
265 |
|
266 |
-
#
|
267 |
-
|
268 |
-
"loop": "https://i.ibb.co/4sY7d0j/loop.gif",
|
269 |
-
"conditional": "https://i.ibb.co/8Xg7zY6/conditional.gif",
|
270 |
-
"function": "https://i.ibb.co/7yVnZ8C/function.gif",
|
271 |
-
"variable": "https://i.ibb.co/7yVnZ8C/function.gif",
|
272 |
-
"list": "https://i.ibb.co/4sY7d0j/loop.gif"
|
273 |
-
}
|
274 |
|
275 |
-
def
|
276 |
-
"""
|
277 |
try:
|
278 |
-
|
279 |
-
|
280 |
-
|
281 |
-
|
|
|
|
|
282 |
|
283 |
-
def
|
284 |
-
"""
|
285 |
try:
|
286 |
-
|
287 |
-
|
288 |
-
|
289 |
-
return url
|
290 |
-
|
291 |
-
# Use fallback if primary URL fails
|
292 |
-
return FALLBACK_IMAGES.get(concept, FALLBACK_IMAGES["loop"])
|
293 |
except:
|
294 |
-
return
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
295 |
|
296 |
def analyze_story(story):
|
297 |
"""Analyze story and identify programming concepts"""
|
@@ -299,345 +287,253 @@ def analyze_story(story):
|
|
299 |
detected_concepts = []
|
300 |
|
301 |
# Check for loops
|
302 |
-
if any(word in story_lower for word in ["times", "repeat", "again", "multiple"
|
303 |
detected_concepts.append("loop")
|
304 |
|
305 |
# Check for conditionals
|
306 |
-
if any(word in story_lower for word in ["if", "when", "unless", "whether", "
|
307 |
detected_concepts.append("conditional")
|
308 |
|
309 |
# Check for functions
|
310 |
-
if any(word in story_lower for word in ["make", "create", "do", "perform", "cast"
|
311 |
detected_concepts.append("function")
|
312 |
|
313 |
# Check for variables
|
314 |
-
if any(word in story_lower for word in ["is", "has", "set to", "value", "
|
315 |
detected_concepts.append("variable")
|
316 |
|
317 |
-
# Check for lists
|
318 |
-
if any(word in story_lower for word in ["and", "many", "several", "collection", "items", "group"]):
|
319 |
-
detected_concepts.append("list")
|
320 |
-
|
321 |
return list(set(detected_concepts))
|
322 |
|
323 |
-
def
|
324 |
-
"""
|
325 |
-
for word in story.split():
|
326 |
-
if word.isdigit():
|
327 |
-
return min(int(word), 10)
|
328 |
-
return 3 # Default value
|
329 |
-
|
330 |
-
def text_to_speech_custom(text, filename="story_audio.mp3"):
|
331 |
-
"""Convert text to speech using gTTS (faster and reliable)"""
|
332 |
-
try:
|
333 |
-
tts = gTTS(text=text, lang='en')
|
334 |
-
tts.save(filename)
|
335 |
-
return filename
|
336 |
-
except Exception as e:
|
337 |
-
st.error(f"Audio creation error: {str(e)}")
|
338 |
-
return None
|
339 |
-
|
340 |
-
def autoplay_audio(file_path):
|
341 |
-
"""Autoplay audio in Streamlit"""
|
342 |
-
with open(file_path, "rb") as f:
|
343 |
-
data = f.read()
|
344 |
-
b64 = base64.b64encode(data).decode()
|
345 |
-
md = f"""
|
346 |
-
<audio autoplay>
|
347 |
-
<source src="data:audio/mp3;base64,{b64}" type="audio/mp3">
|
348 |
-
</audio>
|
349 |
-
"""
|
350 |
-
st.markdown(md, unsafe_allow_html=True)
|
351 |
-
|
352 |
-
def generate_animation_code(story, concept):
|
353 |
-
"""Generate Python animation code based on story"""
|
354 |
try:
|
355 |
-
|
356 |
-
|
|
|
|
|
357 |
|
358 |
-
|
359 |
-
|
360 |
-
|
361 |
-
|
362 |
-
import time
|
363 |
-
|
364 |
-
def animate_story():
|
365 |
-
actions = {count}
|
366 |
-
|
367 |
-
print("Story: {short_story}")
|
368 |
-
print("Concept: Looping {count} times")
|
369 |
-
|
370 |
-
for i in range(actions):
|
371 |
-
print(f"Action {{i+1}}: Performing story action")
|
372 |
-
# Animation code would go here
|
373 |
-
time.sleep(0.5)
|
374 |
-
|
375 |
-
print("Story animation complete!")
|
376 |
-
|
377 |
-
animate_story()
|
378 |
-
"""
|
379 |
-
description = "Looping through actions multiple times"
|
380 |
-
visual_cue = "Repeating actions in a loop"
|
381 |
-
|
382 |
-
elif concept == "conditional":
|
383 |
-
code = f"""
|
384 |
-
# Conditional Animation: Making Decisions
|
385 |
-
import random
|
386 |
-
|
387 |
-
print("Story: {short_story}")
|
388 |
-
print("Concept: Making decisions based on conditions")
|
389 |
-
|
390 |
-
condition = random.choice([True, False])
|
391 |
-
print(f"Condition is {{condition}}")
|
392 |
-
|
393 |
-
if condition:
|
394 |
-
print("Path 1: Following the true branch")
|
395 |
-
# True branch animation
|
396 |
-
else:
|
397 |
-
print("Path 2: Following the false branch")
|
398 |
-
# False branch animation
|
399 |
-
|
400 |
-
print("Story animation complete!")
|
401 |
-
"""
|
402 |
-
description = "Making decisions based on conditions"
|
403 |
-
visual_cue = "Branching paths based on conditions"
|
404 |
-
|
405 |
-
elif concept == "function":
|
406 |
-
code = f"""
|
407 |
-
# Function Animation: Reusable Actions
|
408 |
-
import time
|
409 |
-
|
410 |
-
print("Story: {short_story}")
|
411 |
-
print("Concept: Creating reusable functions")
|
412 |
-
|
413 |
-
def perform_action():
|
414 |
-
print("Performing story action")
|
415 |
-
# Action animation code
|
416 |
-
|
417 |
-
# Call the function multiple times
|
418 |
-
for _ in range({count}):
|
419 |
-
perform_action()
|
420 |
-
time.sleep(0.3)
|
421 |
-
|
422 |
-
print("Story animation complete!")
|
423 |
-
"""
|
424 |
-
description = "Reusing code with functions"
|
425 |
-
visual_cue = "Calling a reusable function"
|
426 |
-
|
427 |
-
elif concept == "variable":
|
428 |
-
code = f"""
|
429 |
-
# Variable Animation: Storing Information
|
430 |
-
import time
|
431 |
-
|
432 |
-
print("Story: {short_story}")
|
433 |
-
print("Concept: Using variables to store information")
|
434 |
-
|
435 |
-
character = "Hero"
|
436 |
-
score = 10
|
437 |
-
inventory = ["sword", "shield", "potion"]
|
438 |
-
|
439 |
-
print(f"Welcome {{character}}! You have {{score}} points.")
|
440 |
-
print(f"Your inventory: {{inventory}}")
|
441 |
-
|
442 |
-
for item in inventory:
|
443 |
-
print(f"Using {{item}}...")
|
444 |
-
time.sleep(0.5)
|
445 |
-
|
446 |
-
print("Story animation complete!")
|
447 |
-
"""
|
448 |
-
description = "Storing information in variables"
|
449 |
-
visual_cue = "Using stored values"
|
450 |
-
|
451 |
-
else: # list
|
452 |
-
code = f"""
|
453 |
-
# List Animation: Managing Collections
|
454 |
-
import time
|
455 |
-
|
456 |
-
print("Story: {short_story}")
|
457 |
-
print("Concept: Working with lists of items")
|
458 |
-
|
459 |
-
items = ["item1", "item2", "item3", "item4", "item5"][:{count}]
|
460 |
-
print(f"Collection has {{len(items)}} items")
|
461 |
-
|
462 |
-
for i, item in enumerate(items):
|
463 |
-
print(f"Processing item {{i+1}}: {{item}}")
|
464 |
-
time.sleep(0.4)
|
465 |
-
|
466 |
-
print("Story animation complete!")
|
467 |
-
"""
|
468 |
-
description = "Managing collections of items"
|
469 |
-
visual_cue = "Processing multiple items"
|
470 |
-
|
471 |
-
return code, description, visual_cue
|
472 |
-
|
473 |
-
except Exception as e:
|
474 |
-
return f"# Error generating code\nprint('Could not generate code: {str(e)}')", "", ""
|
475 |
-
|
476 |
-
def create_interactive_animation(story, concept):
|
477 |
-
"""Create an interactive animation using Plotly"""
|
478 |
-
try:
|
479 |
-
# Create animation data
|
480 |
-
frames = []
|
481 |
-
count = extract_count_from_story(story)
|
482 |
-
|
483 |
-
for i in range(count):
|
484 |
-
frames.append({
|
485 |
-
"frame": i,
|
486 |
-
"x": np.random.uniform(1, 9),
|
487 |
-
"y": np.random.uniform(1, 9),
|
488 |
-
"size": np.random.uniform(20, 40),
|
489 |
-
"label": f"Action {i+1}",
|
490 |
-
"color": f"hsl({i*40}, 100%, 50%)"
|
491 |
-
})
|
492 |
-
|
493 |
-
df = pd.DataFrame(frames)
|
494 |
-
|
495 |
-
# Create animated scatter plot
|
496 |
-
fig = px.scatter(
|
497 |
-
df,
|
498 |
-
x="x",
|
499 |
-
y="y",
|
500 |
-
animation_frame="frame",
|
501 |
-
text="label",
|
502 |
-
size="size",
|
503 |
-
size_max=45,
|
504 |
-
color="color",
|
505 |
-
color_discrete_sequence=px.colors.qualitative.Pastel,
|
506 |
-
range_x=[0, 10],
|
507 |
-
range_y=[0, 10],
|
508 |
-
title=f"Interactive Animation: {story[:40]}{'...' if len(story) > 40 else ''}"
|
509 |
-
)
|
510 |
-
|
511 |
-
# Customize layout
|
512 |
-
fig.update_layout(
|
513 |
-
showlegend=False,
|
514 |
-
plot_bgcolor='rgba(245, 240, 255, 0.8)',
|
515 |
-
paper_bgcolor='rgba(245, 240, 255, 0.8)',
|
516 |
-
width=800,
|
517 |
-
height=600,
|
518 |
-
xaxis=dict(showgrid=False, zeroline=False, visible=False),
|
519 |
-
yaxis=dict(showgrid=False, zeroline=False, visible=False),
|
520 |
-
font_family="Poppins"
|
521 |
-
)
|
522 |
-
|
523 |
-
fig.update_traces(
|
524 |
-
textfont_size=18,
|
525 |
-
textposition='middle center',
|
526 |
-
marker=dict(sizemode='diameter')
|
527 |
-
)
|
528 |
-
|
529 |
-
return fig
|
530 |
-
|
531 |
-
except Exception as e:
|
532 |
-
st.error(f"Interactive animation error: {str(e)}")
|
533 |
return None
|
534 |
|
535 |
-
def
|
536 |
-
"""
|
537 |
concept_info = CONCEPTS.get(concept, {})
|
538 |
|
539 |
explanation = f"""
|
540 |
-
Let
|
541 |
|
542 |
-
|
543 |
|
544 |
-
|
545 |
|
546 |
{concept_info.get('explanation', 'This is a fundamental programming concept.')}
|
547 |
|
548 |
-
|
549 |
-
- {visual_cue}
|
550 |
-
- This represents: {code_description}
|
551 |
-
|
552 |
-
<div class='concept-highlight'>How it works in code</div>:
|
553 |
-
We use {concept_info.get('name', 'this concept')} like this:
|
554 |
```
|
555 |
{concept_info.get('example', 'Example code will appear here')}
|
556 |
```
|
557 |
|
558 |
-
|
559 |
-
|
560 |
-
-
|
561 |
-
-
|
562 |
-
- Building reusable components (functions)
|
563 |
-
- Storing player information (variables)
|
564 |
-
- Managing collections of data (lists)
|
565 |
|
566 |
-
|
567 |
"""
|
568 |
|
569 |
return explanation
|
570 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
571 |
def main():
|
572 |
"""Main application function"""
|
573 |
-
st.title("
|
574 |
-
st.subheader("
|
575 |
|
576 |
# Initialize session state
|
577 |
if 'story' not in st.session_state:
|
578 |
st.session_state.story = ""
|
579 |
if 'concepts' not in st.session_state:
|
580 |
st.session_state.concepts = []
|
581 |
-
if '
|
582 |
-
st.session_state.
|
583 |
-
if '
|
584 |
-
st.session_state.
|
585 |
-
if '
|
586 |
-
st.session_state.
|
587 |
-
if 'visual_cue' not in st.session_state:
|
588 |
-
st.session_state.visual_cue = ""
|
589 |
if 'audio_file' not in st.session_state:
|
590 |
st.session_state.audio_file = None
|
591 |
-
if 'active_tab' not in st.session_state:
|
592 |
-
st.session_state.active_tab = "story"
|
593 |
if 'explanation_audio' not in st.session_state:
|
594 |
st.session_state.explanation_audio = None
|
595 |
-
if '
|
596 |
-
st.session_state.
|
597 |
|
598 |
# Create tabs
|
599 |
tabs = st.empty()
|
600 |
tab_cols = st.columns(5)
|
601 |
-
|
602 |
-
|
603 |
-
|
604 |
-
|
605 |
-
|
606 |
-
|
607 |
-
|
608 |
-
|
609 |
-
|
610 |
-
|
611 |
-
|
612 |
-
|
613 |
-
|
614 |
-
|
615 |
-
|
616 |
-
|
617 |
-
st.session_state.interactive_animation = None
|
618 |
-
st.session_state.animation_code = ""
|
619 |
-
st.session_state.code_description = ""
|
620 |
-
st.session_state.visual_cue = ""
|
621 |
-
st.session_state.audio_file = None
|
622 |
-
st.session_state.explanation_audio = None
|
623 |
-
st.session_state.concept_explanation = ""
|
624 |
-
st.session_state.active_tab = "story"
|
625 |
|
626 |
# Story creation tab
|
627 |
if st.session_state.active_tab == "story":
|
628 |
with st.container():
|
|
|
629 |
st.header("📖 Create Your Story")
|
630 |
-
st.write("Write a short story (2-
|
631 |
|
632 |
story = st.text_area(
|
633 |
"Your story:",
|
634 |
-
height=
|
635 |
-
placeholder="Once upon a time, a rabbit hopped 3 times to reach a carrot...",
|
636 |
value=st.session_state.story,
|
637 |
key="story_input"
|
638 |
)
|
639 |
|
640 |
-
if st.button("
|
641 |
if len(story) < 10:
|
642 |
st.error("Your story needs to be at least 10 characters long!")
|
643 |
else:
|
@@ -646,40 +542,23 @@ def main():
|
|
646 |
st.session_state.concepts = analyze_story(story)
|
647 |
|
648 |
# Get the main concept
|
649 |
-
main_concept = st.session_state.concepts[0] if st.session_state.concepts else "
|
650 |
|
651 |
-
with st.spinner("
|
652 |
-
st.session_state.
|
653 |
-
story, main_concept
|
654 |
-
)
|
655 |
|
656 |
-
with st.spinner("
|
657 |
-
st.session_state.
|
658 |
-
story, main_concept
|
659 |
-
)
|
660 |
-
|
661 |
-
with st.spinner("🔊 Creating audio narration..."):
|
662 |
-
audio_text = f"Your story: {story}. This demonstrates the programming concept: {st.session_state.code_description}"
|
663 |
-
st.session_state.audio_file = text_to_speech_custom(
|
664 |
-
audio_text
|
665 |
-
)
|
666 |
|
667 |
with st.spinner("🧠 Generating concept explanation..."):
|
668 |
-
st.session_state.concept_explanation =
|
669 |
-
story,
|
670 |
-
main_concept,
|
671 |
-
st.session_state.code_description,
|
672 |
-
st.session_state.visual_cue
|
673 |
-
)
|
674 |
|
675 |
-
with st.spinner("🔊 Creating explanation
|
676 |
-
|
677 |
-
|
678 |
-
|
679 |
-
"concept_explanation.mp3"
|
680 |
-
)
|
681 |
|
682 |
-
st.session_state.active_tab = "
|
683 |
st.rerun()
|
684 |
|
685 |
# Show examples
|
@@ -688,199 +567,177 @@ def main():
|
|
688 |
with col1:
|
689 |
st.caption("Loop Example")
|
690 |
st.code('"A dragon breathes fire 5 times at the castle"', language="text")
|
691 |
-
st.image(
|
692 |
-
|
693 |
-
caption="
|
694 |
with col2:
|
695 |
st.caption("Conditional Example")
|
696 |
st.code('"If it rains, the cat stays inside, else it goes out"', language="text")
|
697 |
-
st.image(
|
698 |
-
|
699 |
-
caption="
|
700 |
with col3:
|
701 |
st.caption("Function Example")
|
702 |
st.code('"A wizard casts a spell to make flowers grow"', language="text")
|
703 |
-
st.image(
|
704 |
-
|
705 |
-
caption="
|
|
|
706 |
|
707 |
-
#
|
708 |
-
elif st.session_state.active_tab == "
|
709 |
-
st.header("🎬 Your Story Animation")
|
710 |
-
|
711 |
if not st.session_state.story:
|
712 |
st.warning("Please create a story first!")
|
713 |
st.session_state.active_tab = "story"
|
714 |
st.rerun()
|
715 |
|
716 |
-
|
717 |
-
|
718 |
-
|
719 |
-
st.plotly_chart(st.session_state.interactive_animation, use_container_width=True)
|
720 |
-
else:
|
721 |
-
st.info("Interactive animation preview. The real animation would run on your computer!")
|
722 |
-
concept = st.session_state.concepts[0] if st.session_state.concepts else "loop"
|
723 |
-
st.image(create_animation_preview(concept),
|
724 |
-
use_container_width=True)
|
725 |
-
|
726 |
-
# Animation concept preview
|
727 |
-
st.subheader("📽️ Animation Concept Preview")
|
728 |
-
if st.session_state.concepts:
|
729 |
-
concept = st.session_state.concepts[0]
|
730 |
-
st.image(create_animation_preview(concept),
|
731 |
-
caption=f"{CONCEPTS[concept]['name']} Animation Example",
|
732 |
-
use_container_width=True)
|
733 |
-
else:
|
734 |
-
st.info("Animation preview would appear here")
|
735 |
-
|
736 |
-
# Play audio narration
|
737 |
-
st.subheader("🔊 Story Narration")
|
738 |
-
if st.session_state.audio_file:
|
739 |
-
audio_bytes = open(st.session_state.audio_file, 'rb').read()
|
740 |
-
st.audio(audio_bytes, format='audio/mp3')
|
741 |
|
742 |
-
|
743 |
-
|
744 |
-
|
745 |
-
st.info("Audio narration would play automatically here")
|
746 |
-
|
747 |
-
# Concept explanation section
|
748 |
-
if st.session_state.concepts and st.session_state.concept_explanation:
|
749 |
-
st.subheader("🎓 Concept Explanation")
|
750 |
-
concept = st.session_state.concepts[0]
|
751 |
-
details = CONCEPTS.get(concept, {})
|
752 |
|
753 |
-
|
754 |
-
|
755 |
-
|
756 |
-
<span style="font-size:36px;">{details.get('emoji', '✨')}</span>
|
757 |
-
<h3>Understanding {details.get('name', 'Programming')} Concept</h3>
|
758 |
-
</div>
|
759 |
-
{st.session_state.concept_explanation}
|
760 |
-
</div>
|
761 |
-
""", unsafe_allow_html=True)
|
762 |
|
|
|
763 |
if st.session_state.explanation_audio:
|
764 |
-
|
765 |
-
st.
|
766 |
-
|
767 |
-
|
768 |
-
|
769 |
-
|
770 |
-
st.success("✨ Animation created successfully!")
|
771 |
-
st.caption("This animation was generated with Python code based on your story!")
|
772 |
-
|
773 |
-
if st.button("Reveal Coding Secrets!", use_container_width=True):
|
774 |
-
st.session_state.active_tab = "concepts"
|
775 |
-
st.rerun()
|
776 |
-
|
777 |
-
# Concepts tab
|
778 |
-
elif st.session_state.active_tab == "concepts":
|
779 |
-
st.header("🔍 Coding Concepts in Your Story")
|
780 |
-
st.subheader("We secretly used these programming concepts:")
|
781 |
-
|
782 |
-
if not st.session_state.concepts:
|
783 |
-
st.warning("No concepts detected in your story! Try adding words like '3 times', 'if', or 'make'.")
|
784 |
-
else:
|
785 |
-
for concept in st.session_state.concepts:
|
786 |
-
if concept in CONCEPTS:
|
787 |
-
details = CONCEPTS[concept]
|
788 |
-
st.markdown(f"""
|
789 |
-
<div class="concept-card" style="border-left: 5px solid {details['color']};">
|
790 |
-
<div style="display:flex; align-items:center; gap:15px;">
|
791 |
-
<span style="font-size:36px;">{details['emoji']}</span>
|
792 |
-
<h3 style="color:{details['color']};">{details['name']}</h3>
|
793 |
-
</div>
|
794 |
-
<p>{details['description']}</p>
|
795 |
-
<pre style="background:#f8f4ff; padding:10px; border-radius:8px;">{details['example']}</pre>
|
796 |
-
<p><strong>Explanation:</strong> {details.get('explanation', 'Learn how this concept works!')}</p>
|
797 |
-
</div>
|
798 |
-
""", unsafe_allow_html=True)
|
799 |
-
|
800 |
-
# Show animation preview for the concept
|
801 |
-
st.image(create_animation_preview(concept),
|
802 |
-
caption=f"{details['name']} Animation Example",
|
803 |
-
use_container_width=True)
|
804 |
-
|
805 |
-
# Play explanation if available
|
806 |
-
if st.session_state.explanation_audio:
|
807 |
-
st.subheader("🔊 Full Concept Explanation")
|
808 |
-
explanation_bytes = open(st.session_state.explanation_audio, 'rb').read()
|
809 |
-
st.audio(explanation_bytes, format='audio/mp3')
|
810 |
|
811 |
-
|
812 |
-
autoplay_audio(st.session_state.explanation_audio)
|
813 |
-
|
814 |
-
if st.button("See the Magic Code!", use_container_width=True):
|
815 |
-
st.session_state.active_tab = "code"
|
816 |
-
st.rerun()
|
817 |
|
818 |
-
#
|
819 |
-
elif st.session_state.active_tab == "
|
820 |
-
st.
|
821 |
-
|
|
|
|
|
822 |
|
823 |
-
|
824 |
-
st.
|
825 |
-
st.
|
826 |
-
st.code(st.session_state.animation_code, language="python")
|
827 |
-
|
828 |
-
# Download button
|
829 |
-
st.download_button(
|
830 |
-
label="📥 Download Animation Code",
|
831 |
-
data=st.session_state.animation_code,
|
832 |
-
file_name="story_animation.py",
|
833 |
-
mime="text/python",
|
834 |
-
use_container_width=True
|
835 |
-
)
|
836 |
-
|
837 |
-
st.info("💡 To run this animation on your computer:")
|
838 |
-
st.markdown("""
|
839 |
-
1. Install Python from [python.org](https://python.org)
|
840 |
-
2. Install required libraries: `pip install matplotlib numpy`
|
841 |
-
3. Copy the code above into a file named `animation.py`
|
842 |
-
4. Run it with: `python animation.py`
|
843 |
-
""")
|
844 |
-
|
845 |
-
st.success("🎉 When you run this code, you'll see your story come to life!")
|
846 |
-
|
847 |
-
# Show what the animation would look like
|
848 |
-
st.subheader("🎬 What Your Animation Would Look Like")
|
849 |
-
concept = st.session_state.concepts[0] if st.session_state.concepts else "loop"
|
850 |
-
st.image(create_animation_preview(concept),
|
851 |
-
caption="This is similar to what your animation would look like",
|
852 |
-
use_container_width=True)
|
853 |
|
854 |
-
|
855 |
-
|
856 |
-
|
857 |
-
concept = st.session_state.concepts[0] if st.session_state.concepts else "loop"
|
858 |
-
details = CONCEPTS.get(concept, {})
|
859 |
|
|
|
860 |
st.markdown(f"""
|
861 |
<div class="explanation-box">
|
862 |
<div class="explanation-header">
|
863 |
-
<span style="font-size:36px;">{
|
864 |
-
<
|
|
|
|
|
|
|
|
|
|
|
|
|
865 |
</div>
|
866 |
-
|
|
|
|
|
|
|
|
|
|
|
867 |
</div>
|
868 |
""", unsafe_allow_html=True)
|
869 |
|
870 |
# Play explanation audio
|
871 |
if st.session_state.explanation_audio:
|
872 |
-
|
873 |
-
st.
|
874 |
-
|
875 |
-
|
876 |
-
|
877 |
-
|
878 |
-
|
879 |
-
|
880 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
881 |
st.session_state.active_tab = "story"
|
882 |
-
st.session_state.story = ""
|
883 |
st.rerun()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
884 |
|
885 |
if __name__ == "__main__":
|
886 |
main()
|
|
|
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"
|
24 |
)
|
25 |
|
26 |
+
# Custom CSS with purple/grey color scheme
|
27 |
st.markdown("""
|
28 |
<style>
|
29 |
+
@import url('https://fonts.googleapis.com/css2?family=Comic+Neue:wght@700&family=Fredoka+One&display=swap');
|
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;
|
|
|
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;
|
83 |
transition: all 0.3s;
|
84 |
+
font-family: 'Fredoka One', cursive;
|
85 |
}
|
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;
|
|
|
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 |
},
|
203 |
"conditional": {
|
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"""
|
|
|
287 |
detected_concepts = []
|
288 |
|
289 |
# Check for loops
|
290 |
+
if any(word in story_lower for word in ["times", "repeat", "again", "multiple"]):
|
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
|
298 |
+
if any(word in story_lower for word in ["make", "create", "do", "perform", "cast"]):
|
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:
|
495 |
st.session_state.audio_file = None
|
|
|
|
|
496 |
if 'explanation_audio' not in st.session_state:
|
497 |
st.session_state.explanation_audio = None
|
498 |
+
if 'active_tab' not in st.session_state:
|
499 |
+
st.session_state.active_tab = "story"
|
500 |
|
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 |
)
|
535 |
|
536 |
+
if st.button("Create Game!", use_container_width=True):
|
537 |
if len(story) < 10:
|
538 |
st.error("Your story needs to be at least 10 characters long!")
|
539 |
else:
|
|
|
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()
|
563 |
|
564 |
# Show examples
|
|
|
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()
|