Spaces:
Running
Running
Update app.py
Browse files
app.py
CHANGED
@@ -6,10 +6,9 @@ import textwrap
|
|
6 |
from io import BytesIO
|
7 |
from PIL import Image, ImageDraw, ImageFont
|
8 |
import numpy as np
|
9 |
-
from gtts import gTTS
|
10 |
from transformers import pipeline
|
11 |
import base64
|
12 |
-
import
|
13 |
|
14 |
# Set up the page
|
15 |
st.set_page_config(
|
@@ -128,51 +127,61 @@ st.markdown("""
|
|
128 |
margin: 0 auto;
|
129 |
}
|
130 |
|
131 |
-
.
|
132 |
-
|
|
|
133 |
border-radius: 10px;
|
134 |
-
padding:
|
135 |
-
margin:
|
136 |
-
|
137 |
-
|
|
|
138 |
}
|
139 |
|
140 |
-
.
|
141 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
142 |
}
|
143 |
|
144 |
-
.
|
145 |
-
|
|
|
|
|
146 |
}
|
147 |
|
148 |
-
.
|
149 |
-
|
150 |
-
border: 2px solid #4caf50;
|
151 |
border-radius: 10px;
|
152 |
-
|
153 |
-
|
|
|
|
|
154 |
}
|
155 |
|
156 |
-
.
|
157 |
-
|
158 |
-
color: white;
|
159 |
-
border-radius: 20px;
|
160 |
-
padding: 0.5rem 1rem;
|
161 |
-
display: inline-block;
|
162 |
font-weight: bold;
|
163 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
164 |
}
|
165 |
|
166 |
/* Progress bar styling */
|
167 |
.stProgress > div > div > div {
|
168 |
background-color: #ff5722 !important;
|
169 |
}
|
170 |
-
|
171 |
-
/* Audio player styling */
|
172 |
-
.audio-player {
|
173 |
-
width: 100%;
|
174 |
-
margin-top: 10px;
|
175 |
-
}
|
176 |
</style>
|
177 |
""", unsafe_allow_html=True)
|
178 |
|
@@ -181,22 +190,19 @@ st.markdown("""
|
|
181 |
def load_models():
|
182 |
"""Load open-source AI models"""
|
183 |
try:
|
184 |
-
# Text generation model for code explanations
|
185 |
-
story_to_code = pipeline("text-generation", model="gpt2", max_length=100)
|
186 |
-
|
187 |
-
# Sentiment analysis for story understanding
|
188 |
-
sentiment_analyzer = pipeline("sentiment-analysis", model="distilbert-base-uncased-finetuned-sst-2-english")
|
189 |
-
|
190 |
# Named entity recognition for identifying objects
|
191 |
ner_model = pipeline("ner", model="dbmdz/bert-large-cased-finetuned-conll03-english")
|
192 |
|
193 |
-
# Text
|
194 |
-
|
|
|
|
|
|
|
195 |
|
196 |
-
return
|
197 |
except Exception as e:
|
198 |
st.error(f"Error loading models: {e}")
|
199 |
-
return None, None, None
|
200 |
|
201 |
# Image generation functions
|
202 |
def create_storyboard_image(text, width=400, height=300):
|
@@ -244,6 +250,9 @@ def generate_sprite_animation(story, character="spaceship", theme="space", num_f
|
|
244 |
elif theme == "medieval":
|
245 |
bg_color = (139, 69, 19)
|
246 |
star_color = None
|
|
|
|
|
|
|
247 |
else:
|
248 |
bg_color = (0, 0, 30)
|
249 |
star_color = (255, 255, 255)
|
@@ -304,6 +313,23 @@ def generate_sprite_animation(story, character="spaceship", theme="space", num_f
|
|
304 |
# Draw sword swing
|
305 |
draw.line([(knight_x+25, knight_y+12), (knight_x+45, knight_y-10)], fill=(255, 255, 0), width=2)
|
306 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
307 |
# Draw enemies based on theme
|
308 |
if theme == "space" and "alien" in story.lower():
|
309 |
alien_x = 200
|
@@ -325,6 +351,14 @@ def generate_sprite_animation(story, character="spaceship", theme="space", num_f
|
|
325 |
draw.ellipse([dragon_x, dragon_y, dragon_x+40, dragon_y+20], fill=(178, 34, 34))
|
326 |
draw.line([(dragon_x+40, dragon_y+10), (dragon_x+60, dragon_y)], fill=(178, 34, 34), width=3)
|
327 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
328 |
frames.append(img)
|
329 |
|
330 |
return frames
|
@@ -333,12 +367,12 @@ def generate_code_explanation(story, explanation_generator):
|
|
333 |
"""Generate code explanation using open-source model"""
|
334 |
try:
|
335 |
# Create a prompt for the model
|
336 |
-
prompt = f"Explain to a child how this story would become code: '{story}'"
|
337 |
|
338 |
# Generate text
|
339 |
result = explanation_generator(
|
340 |
prompt,
|
341 |
-
max_length=
|
342 |
num_return_sequences=1,
|
343 |
)
|
344 |
|
@@ -348,71 +382,85 @@ def generate_code_explanation(story, explanation_generator):
|
|
348 |
return f"""See how your story became real code? For example, when you wrote "{story.split()[0]}",
|
349 |
we used code like: character.move(). That's how we turn words into actions!"""
|
350 |
|
351 |
-
def
|
352 |
-
"""
|
353 |
try:
|
354 |
-
#
|
355 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
356 |
|
357 |
-
#
|
358 |
-
|
359 |
-
|
360 |
-
|
|
|
|
|
|
|
|
|
361 |
|
362 |
-
#
|
363 |
-
|
|
|
364 |
|
365 |
-
#
|
366 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
367 |
|
368 |
-
return
|
|
|
369 |
except Exception as e:
|
370 |
-
st.error(f"
|
371 |
-
return
|
372 |
-
|
373 |
-
# Achievement system
|
374 |
-
ACHIEVEMENTS = {
|
375 |
-
"first_story": {"name": "Storyteller", "icon": "๐", "description": "Created your first story!"},
|
376 |
-
"code_master": {"name": "Code Master", "icon": "๐ป", "description": "Used 5 different coding concepts"},
|
377 |
-
"animator": {"name": "Animator", "icon": "๐ฌ", "description": "Created 3 animations"},
|
378 |
-
"voice_artist": {"name": "Voice Artist", "icon": "๐ค", "description": "Used text-to-speech feature"},
|
379 |
-
"character_designer": {"name": "Character Designer", "icon": "๐พ", "description": "Created a custom character"}
|
380 |
-
}
|
381 |
-
|
382 |
-
def unlock_achievement(achievement_id):
|
383 |
-
"""Unlock an achievement for the user"""
|
384 |
-
if achievement_id not in st.session_state.achievements_unlocked:
|
385 |
-
st.session_state.achievements_unlocked.append(achievement_id)
|
386 |
-
st.session_state.new_achievement = achievement_id
|
387 |
-
return True
|
388 |
-
return False
|
389 |
|
390 |
# Header section
|
391 |
st.markdown('<div class="header">CodeTales โจ</div>', unsafe_allow_html=True)
|
392 |
st.markdown('<div class="subheader">Storytime + Coding Magic</div>', unsafe_allow_html=True)
|
393 |
-
st.markdown('<div style="text-align:center; color:white; font-size:1.2rem; margin-bottom:2rem;">Turn wild stories into playable games with
|
394 |
|
395 |
# How it works section
|
396 |
-
|
397 |
-
|
398 |
-
|
399 |
-
|
400 |
-
|
401 |
-
|
402 |
-
|
403 |
-
|
404 |
-
|
405 |
-
|
406 |
-
|
407 |
-
|
408 |
-
Tavus shows: *"See? 'spaceship.move_right()' makes it fly! That's coding!"*
|
409 |
-
|
410 |
-
**5. Level Up! ๐**
|
411 |
-
Earn achievements as you learn and create!
|
412 |
-
""")
|
413 |
|
414 |
# Load models
|
415 |
-
|
416 |
|
417 |
# Initialize session state
|
418 |
if 'animation_generated' not in st.session_state:
|
@@ -427,29 +475,28 @@ if 'selected_character' not in st.session_state:
|
|
427 |
st.session_state.selected_character = "spaceship"
|
428 |
if 'selected_theme' not in st.session_state:
|
429 |
st.session_state.selected_theme = "space"
|
430 |
-
if 'sound_enabled' not in st.session_state:
|
431 |
-
st.session_state.sound_enabled = True
|
432 |
-
if 'achievements_unlocked' not in st.session_state:
|
433 |
-
st.session_state.achievements_unlocked = []
|
434 |
-
if 'new_achievement' not in st.session_state:
|
435 |
-
st.session_state.new_achievement = None
|
436 |
if 'story_count' not in st.session_state:
|
437 |
st.session_state.story_count = 0
|
438 |
if 'level' not in st.session_state:
|
439 |
st.session_state.level = 1
|
440 |
if 'xp' not in st.session_state:
|
441 |
st.session_state.xp = 0
|
442 |
-
if '
|
443 |
-
st.session_state.
|
444 |
-
|
445 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
446 |
|
447 |
-
|
448 |
-
|
449 |
-
"
|
450 |
-
"
|
451 |
-
"
|
452 |
-
"Jungle Explorer": "An explorer runs through the jungle, jumping over snakes and swinging on vines."
|
453 |
}
|
454 |
|
455 |
# Main content
|
@@ -460,75 +507,45 @@ col1, col2 = st.columns([1, 1])
|
|
460 |
|
461 |
with col1:
|
462 |
st.markdown('<div class="story-box">', unsafe_allow_html=True)
|
463 |
-
st.markdown("### ๐ Write Your Story
|
464 |
-
|
465 |
-
# Story template selector
|
466 |
-
selected_template = st.selectbox(
|
467 |
-
"Or choose a story template to start with:",
|
468 |
-
list(story_templates.keys()),
|
469 |
-
index=0
|
470 |
-
)
|
471 |
|
472 |
story_text = st.text_area(
|
473 |
"Tell your adventure story...",
|
474 |
height=200,
|
475 |
placeholder="Once upon a time, a brave spaceship zoomed through space, shooting lasers at alien spaceships...",
|
476 |
label_visibility="collapsed",
|
477 |
-
value=
|
478 |
)
|
479 |
|
480 |
-
|
481 |
-
st.markdown("### ๐ง Choose Your Hero")
|
482 |
-
char_cols = st.columns(3)
|
483 |
-
characters = {
|
484 |
-
"spaceship": "๐ Spaceship",
|
485 |
-
"dragon": "๐ Dragon",
|
486 |
-
"knight": "๐ก๏ธ Knight"
|
487 |
-
}
|
488 |
-
|
489 |
-
for i, (char_key, char_label) in enumerate(characters.items()):
|
490 |
-
with char_cols[i]:
|
491 |
-
if st.button(
|
492 |
-
char_label,
|
493 |
-
key=f"char_{char_key}",
|
494 |
-
use_container_width=True,
|
495 |
-
type="primary" if st.session_state.selected_character == char_key else "secondary"
|
496 |
-
):
|
497 |
-
st.session_state.selected_character = char_key
|
498 |
-
|
499 |
-
# Theme selection
|
500 |
-
st.markdown("### ๐จ Choose Your World")
|
501 |
-
theme_cols = st.columns(3)
|
502 |
-
themes = {
|
503 |
-
"space": "๐ Space",
|
504 |
-
"jungle": "๐ฟ Jungle",
|
505 |
-
"medieval": "๐ฐ Medieval"
|
506 |
-
}
|
507 |
-
|
508 |
-
for i, (theme_key, theme_label) in enumerate(themes.items()):
|
509 |
-
with theme_cols[i]:
|
510 |
-
if st.button(
|
511 |
-
theme_label,
|
512 |
-
key=f"theme_{theme_key}",
|
513 |
-
use_container_width=True,
|
514 |
-
type="primary" if st.session_state.selected_theme == theme_key else "secondary"
|
515 |
-
):
|
516 |
-
st.session_state.selected_theme = theme_key
|
517 |
-
|
518 |
-
# Voice options
|
519 |
-
st.session_state.voice_enabled = st.toggle(
|
520 |
-
"๐ฃ๏ธ Enable Tavus Voice",
|
521 |
-
value=st.session_state.voice_enabled,
|
522 |
-
help="Hear Tavus explain coding concepts with text-to-speech"
|
523 |
-
)
|
524 |
-
|
525 |
-
# Generate button with animation
|
526 |
-
if st.button("โจ Generate Animation!", use_container_width=True, key="generate", type="primary"):
|
527 |
if story_text.strip():
|
528 |
st.session_state.story_text = story_text
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
529 |
st.session_state.animation_generated = True
|
530 |
-
|
531 |
-
with st.spinner("๐งโโ๏ธ Cooking your story in the magic oven..."):
|
532 |
# Generate animation frames
|
533 |
st.session_state.animation_frames = generate_sprite_animation(
|
534 |
story_text,
|
@@ -541,38 +558,21 @@ with col1:
|
|
541 |
|
542 |
# Update user progress
|
543 |
st.session_state.story_count += 1
|
544 |
-
st.session_state.xp +=
|
545 |
|
546 |
# Check for level up
|
547 |
if st.session_state.xp >= 100:
|
548 |
st.session_state.level += 1
|
549 |
st.session_state.xp = st.session_state.xp - 100
|
550 |
st.session_state.new_level = True
|
551 |
-
|
552 |
-
# Unlock achievements
|
553 |
-
if st.session_state.story_count == 1:
|
554 |
-
unlock_achievement("first_story")
|
555 |
-
|
556 |
-
# Generate speech if enabled
|
557 |
-
if st.session_state.voice_enabled:
|
558 |
-
st.session_state.speech_data = text_to_speech(st.session_state.code_explanation)
|
559 |
-
if st.session_state.speech_data:
|
560 |
-
unlock_achievement("voice_artist")
|
561 |
-
|
562 |
-
# Simulate sound effect
|
563 |
-
if st.session_state.sound_enabled:
|
564 |
-
st.session_state.sound_message = "๐ Sound effects added: Laser blasts, explosions, and more!"
|
565 |
-
else:
|
566 |
-
st.session_state.sound_message = ""
|
567 |
-
else:
|
568 |
-
st.warning("Please enter a story first!")
|
569 |
st.markdown('</div>', unsafe_allow_html=True)
|
570 |
|
571 |
with col2:
|
572 |
st.markdown('<div class="story-box">', unsafe_allow_html=True)
|
573 |
-
st.markdown("### ๐ฎ Your Game Animation")
|
574 |
|
575 |
if st.session_state.animation_generated and st.session_state.animation_frames:
|
|
|
576 |
# Display animation frames
|
577 |
st.markdown("**Your Story Comes to Life!**")
|
578 |
cols = st.columns(len(st.session_state.animation_frames))
|
@@ -599,195 +599,105 @@ with col2:
|
|
599 |
use_container_width=True
|
600 |
)
|
601 |
|
602 |
-
# Display sound message
|
603 |
-
if st.session_state.sound_enabled and 'sound_message' in st.session_state:
|
604 |
-
st.info(st.session_state.sound_message)
|
605 |
-
|
606 |
# Display character and theme info
|
607 |
st.success(f"โจ Your {characters[st.session_state.selected_character]} in the {themes[st.session_state.selected_theme]} world!")
|
608 |
|
609 |
-
|
610 |
-
|
611 |
-
st.markdown("### ๐ Tavus Explanation")
|
612 |
-
st.markdown(f"""
|
613 |
-
<audio controls autoplay style="width:100%">
|
614 |
-
<source src="{st.session_state.speech_data}" type="audio/mp3">
|
615 |
-
Your browser does not support the audio element.
|
616 |
-
</audio>
|
617 |
-
""", unsafe_allow_html=True)
|
618 |
-
|
619 |
-
elif st.session_state.animation_generated:
|
620 |
-
st.warning("Couldn't generate animation. Try a different story!")
|
621 |
st.image("https://img.freepik.com/free-vector/hand-drawn-colorful-space-background_23-2148821798.jpg",
|
622 |
use_container_width=True)
|
|
|
623 |
elif story_text:
|
624 |
-
|
625 |
preview_img = create_storyboard_image(story_text)
|
626 |
st.image(preview_img, caption="Your Story Preview", use_container_width=True)
|
627 |
-
st.info("๐ Click
|
628 |
else:
|
629 |
st.image("https://img.freepik.com/free-vector/hand-drawn-colorful-space-background_23-2148821798.jpg",
|
630 |
use_container_width=True)
|
631 |
-
st.info("๐ Write your story and click
|
632 |
|
633 |
st.markdown('</div>', unsafe_allow_html=True)
|
634 |
|
635 |
# Tavus explanation section
|
636 |
if st.session_state.animation_generated and st.session_state.story_text:
|
637 |
st.markdown('<div class="robot-speech">', unsafe_allow_html=True)
|
638 |
-
st.markdown("### ๐ค Tavus the Robot Teacher
|
639 |
|
640 |
# Extract action words from story
|
641 |
-
action_words = ["zoom", "shoot", "fly", "move", "jump", "run", "attack",
|
642 |
-
"
|
643 |
-
|
|
|
644 |
|
645 |
if found_actions:
|
646 |
# Create explanation based on found words
|
647 |
-
|
648 |
-
|
649 |
-
|
650 |
-
for action in found_actions[:3]: # Limit to 3 explanations
|
651 |
-
if action == "zoom":
|
652 |
-
explanations.append("makes your spaceship move super fast!")
|
653 |
-
code_snippets.append("spaceship.accelerate(speed=10)")
|
654 |
-
elif action == "shoot":
|
655 |
-
explanations.append("creates laser beams to defeat enemies!")
|
656 |
-
code_snippets.append("laser = spaceship.fire_weapon()")
|
657 |
-
elif action == "fly":
|
658 |
-
explanations.append("keeps your character moving through the air!")
|
659 |
-
code_snippets.append("character.apply_gravity(False)")
|
660 |
-
elif action == "move":
|
661 |
-
explanations.append("changes position on the screen!")
|
662 |
-
code_snippets.append("player.move(direction='right')")
|
663 |
-
elif action == "jump":
|
664 |
-
explanations.append("makes your character leap upwards!")
|
665 |
-
code_snippets.append("hero.jump(height=100)")
|
666 |
-
elif action == "run":
|
667 |
-
explanations.append("makes your character move faster!")
|
668 |
-
code_snippets.append("player.speed = player.speed * 2")
|
669 |
-
elif action == "attack":
|
670 |
-
explanations.append("lets your hero fight the bad guys!")
|
671 |
-
code_snippets.append("sword.swing(damage=15)")
|
672 |
-
elif action == "laser":
|
673 |
-
explanations.append("creates powerful energy beams!")
|
674 |
-
code_snippets.append("weapon = Laser(color='red', damage=20)")
|
675 |
-
elif action == "alien":
|
676 |
-
explanations.append("adds enemies to your game!")
|
677 |
-
code_snippets.append("enemy = Alien(position=(100, 200))")
|
678 |
-
elif action == "spaceship":
|
679 |
-
explanations.append("creates your main character!")
|
680 |
-
code_snippets.append("player = Spaceship(image='spaceship.png')")
|
681 |
-
elif action == "dragon":
|
682 |
-
explanations.append("adds a fire-breathing challenge!")
|
683 |
-
code_snippets.append("boss = Dragon(health=100, damage=25)")
|
684 |
-
elif action == "fire":
|
685 |
-
explanations.append("creates dangerous fire effects!")
|
686 |
-
code_snippets.append("flame = FireEffect(position, size=30)")
|
687 |
-
elif action == "hero":
|
688 |
-
explanations.append("creates the main player character!")
|
689 |
-
code_snippets.append("player = Hero(name='Your Hero')")
|
690 |
-
elif action == "castle":
|
691 |
-
explanations.append("adds a majestic building to your world!")
|
692 |
-
code_snippets.append("castle = Building(type='castle', position=(300, 150))")
|
693 |
-
elif action == "escape":
|
694 |
-
explanations.append("creates exciting chase sequences!")
|
695 |
-
code_snippets.append("player.start_escape_sequence(speed=15)")
|
696 |
-
elif action == "fight":
|
697 |
-
explanations.append("initiates battle mechanics!")
|
698 |
-
code_snippets.append("initiate_combat(enemy)")
|
699 |
-
elif action == "win":
|
700 |
-
explanations.append("creates victory conditions!")
|
701 |
-
code_snippets.append("if player.score > 100: show_victory_screen()")
|
702 |
|
703 |
-
|
704 |
-
|
705 |
-
|
706 |
-
|
707 |
-
|
708 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
709 |
|
710 |
-
st.markdown("
|
711 |
-
else:
|
712 |
-
st.markdown("I see you created an awesome story! Every word you write can become game code. Try adding action words like 'jump', 'run', or 'shoot' next time!")
|
713 |
|
714 |
# Show AI-generated explanation
|
715 |
st.markdown("### ๐ง AI-Powered Explanation:")
|
716 |
st.write(st.session_state.code_explanation)
|
717 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
718 |
st.markdown("</div>", unsafe_allow_html=True)
|
719 |
|
720 |
-
# Achievements section
|
721 |
-
if st.session_state.achievements_unlocked:
|
722 |
-
st.markdown("### ๐ Your Achievements")
|
723 |
-
achievement_cols = st.columns(5)
|
724 |
-
for i, ach_id in enumerate(st.session_state.achievements_unlocked):
|
725 |
-
with achievement_cols[i % 5]:
|
726 |
-
ach = ACHIEVEMENTS[ach_id]
|
727 |
-
st.markdown(f'<div class="achievement-badge">{ach["icon"]}</div>', unsafe_allow_html=True)
|
728 |
-
st.markdown(f"**{ach['name']}**")
|
729 |
-
st.caption(ach['description'])
|
730 |
-
|
731 |
-
# New achievement notification
|
732 |
-
if 'new_achievement' in st.session_state and st.session_state.new_achievement:
|
733 |
-
ach = ACHIEVEMENTS[st.session_state.new_achievement]
|
734 |
-
st.success(f"๐ Achievement Unlocked: {ach['name']} - {ach['description']}")
|
735 |
-
st.session_state.new_achievement = None
|
736 |
-
|
737 |
# New level notification
|
738 |
if 'new_level' in st.session_state and st.session_state.new_level:
|
739 |
st.balloons()
|
740 |
st.success(f"๐ Level Up! You're now Level {st.session_state.level}!")
|
741 |
st.session_state.new_level = False
|
742 |
|
743 |
-
# Coding challenges section
|
744 |
-
st.markdown("### ๐ป Coding Challenges")
|
745 |
-
with st.expander("Complete challenges to earn XP!"):
|
746 |
-
challenge_cols = st.columns(3)
|
747 |
-
|
748 |
-
with challenge_cols[0]:
|
749 |
-
st.markdown("#### Challenge 1: The Mover")
|
750 |
-
st.code("""
|
751 |
-
# Make the character move to the right
|
752 |
-
character.move_right(10)
|
753 |
-
""")
|
754 |
-
if st.button("Run Challenge 1", key="challenge1"):
|
755 |
-
st.session_state.xp += 5
|
756 |
-
st.success("+5 XP! Character moved right!")
|
757 |
-
|
758 |
-
with challenge_cols[1]:
|
759 |
-
st.markdown("#### Challenge 2: The Jumper")
|
760 |
-
st.code("""
|
761 |
-
# Make the character jump
|
762 |
-
character.jump(20)
|
763 |
-
""")
|
764 |
-
if st.button("Run Challenge 2", key="challenge2"):
|
765 |
-
st.session_state.xp += 5
|
766 |
-
st.success("+5 XP! Character jumped!")
|
767 |
-
|
768 |
-
with challenge_cols[2]:
|
769 |
-
st.markdown("#### Challenge 3: The Shooter")
|
770 |
-
st.code("""
|
771 |
-
# Make the character shoot a laser
|
772 |
-
laser = character.shoot()
|
773 |
-
""")
|
774 |
-
if st.button("Run Challenge 3", key="challenge3"):
|
775 |
-
st.session_state.xp += 5
|
776 |
-
st.success("+5 XP! Laser fired!")
|
777 |
-
|
778 |
# Benefits section
|
779 |
st.markdown("""
|
780 |
-
## โค Why
|
781 |
|
782 |
| For Kids ๐ง๐ฆ | For Parents & Teachers ๐ช๐ฉโ๐ซ |
|
783 |
|--------------|----------------------------|
|
784 |
-
| โจ
|
785 |
-
| ๐
|
786 |
-
| ๐ฎ
|
787 |
-
| ๐
|
788 |
-
| ๐
|
789 |
-
| ๐ Achievement system motivates learning | ๐ Progress tracking shows growth |
|
790 |
-
| ๐ฃ๏ธ Voice explanations for accessibility | ๐จ Creative expression through stories |
|
791 |
""")
|
792 |
|
793 |
# Footer
|
@@ -795,8 +705,8 @@ st.markdown("---")
|
|
795 |
st.markdown("""
|
796 |
<center>
|
797 |
<p style="color:white; font-size:1.1rem;">
|
798 |
-
โจ Made with
|
799 |
-
Transforming stories into games since 2023
|
800 |
</p>
|
801 |
</center>
|
802 |
""", unsafe_allow_html=True)
|
|
|
6 |
from io import BytesIO
|
7 |
from PIL import Image, ImageDraw, ImageFont
|
8 |
import numpy as np
|
|
|
9 |
from transformers import pipeline
|
10 |
import base64
|
11 |
+
import re
|
12 |
|
13 |
# Set up the page
|
14 |
st.set_page_config(
|
|
|
127 |
margin: 0 auto;
|
128 |
}
|
129 |
|
130 |
+
.hero-suggestion {
|
131 |
+
background: #ffeb3b;
|
132 |
+
color: #333;
|
133 |
border-radius: 10px;
|
134 |
+
padding: 1rem;
|
135 |
+
margin: 1rem 0;
|
136 |
+
text-align: center;
|
137 |
+
font-weight: bold;
|
138 |
+
border: 2px dashed #ff9800;
|
139 |
}
|
140 |
|
141 |
+
.world-suggestion {
|
142 |
+
background: #4caf50;
|
143 |
+
color: white;
|
144 |
+
border-radius: 10px;
|
145 |
+
padding: 1rem;
|
146 |
+
margin: 1rem 0;
|
147 |
+
text-align: center;
|
148 |
+
font-weight: bold;
|
149 |
+
border: 2px dashed #2e7d32;
|
150 |
}
|
151 |
|
152 |
+
.step-container {
|
153 |
+
display: flex;
|
154 |
+
justify-content: space-between;
|
155 |
+
margin-bottom: 2rem;
|
156 |
}
|
157 |
|
158 |
+
.step {
|
159 |
+
background: rgba(255, 255, 255, 0.9);
|
|
|
160 |
border-radius: 10px;
|
161 |
+
padding: 1rem;
|
162 |
+
width: 23%;
|
163 |
+
text-align: center;
|
164 |
+
box-shadow: 0 4px 8px rgba(0,0,0,0.1);
|
165 |
}
|
166 |
|
167 |
+
.step-number {
|
168 |
+
font-size: 1.5rem;
|
|
|
|
|
|
|
|
|
169 |
font-weight: bold;
|
170 |
+
background: #ff5722;
|
171 |
+
color: white;
|
172 |
+
border-radius: 50%;
|
173 |
+
width: 30px;
|
174 |
+
height: 30px;
|
175 |
+
display: flex;
|
176 |
+
align-items: center;
|
177 |
+
justify-content: center;
|
178 |
+
margin: 0 auto 10px;
|
179 |
}
|
180 |
|
181 |
/* Progress bar styling */
|
182 |
.stProgress > div > div > div {
|
183 |
background-color: #ff5722 !important;
|
184 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
185 |
</style>
|
186 |
""", unsafe_allow_html=True)
|
187 |
|
|
|
190 |
def load_models():
|
191 |
"""Load open-source AI models"""
|
192 |
try:
|
|
|
|
|
|
|
|
|
|
|
|
|
193 |
# Named entity recognition for identifying objects
|
194 |
ner_model = pipeline("ner", model="dbmdz/bert-large-cased-finetuned-conll03-english")
|
195 |
|
196 |
+
# Text classification for theme detection
|
197 |
+
classifier = pipeline("zero-shot-classification", model="facebook/bart-large-mnli")
|
198 |
+
|
199 |
+
# Text generation for code explanations
|
200 |
+
explanation_generator = pipeline("text2text-generation", model="google/flan-t5-large")
|
201 |
|
202 |
+
return ner_model, classifier, explanation_generator
|
203 |
except Exception as e:
|
204 |
st.error(f"Error loading models: {e}")
|
205 |
+
return None, None, None
|
206 |
|
207 |
# Image generation functions
|
208 |
def create_storyboard_image(text, width=400, height=300):
|
|
|
250 |
elif theme == "medieval":
|
251 |
bg_color = (139, 69, 19)
|
252 |
star_color = None
|
253 |
+
elif theme == "underwater":
|
254 |
+
bg_color = (0, 105, 148)
|
255 |
+
star_color = None
|
256 |
else:
|
257 |
bg_color = (0, 0, 30)
|
258 |
star_color = (255, 255, 255)
|
|
|
313 |
# Draw sword swing
|
314 |
draw.line([(knight_x+25, knight_y+12), (knight_x+45, knight_y-10)], fill=(255, 255, 0), width=2)
|
315 |
|
316 |
+
elif character == "mermaid":
|
317 |
+
mermaid_x = 50 + i * 40
|
318 |
+
mermaid_y = 120
|
319 |
+
# Draw mermaid tail
|
320 |
+
draw.ellipse([mermaid_x, mermaid_y, mermaid_x+30, mermaid_y+20], fill=(255, 105, 180))
|
321 |
+
# Draw mermaid body
|
322 |
+
draw.ellipse([mermaid_x+5, mermaid_y-20, mermaid_x+25, mermaid_y], fill=(255, 218, 185))
|
323 |
+
# Draw hair
|
324 |
+
draw.ellipse([mermaid_x-5, mermaid_y-25, mermaid_x+30, mermaid_y-15], fill=(255, 215, 0))
|
325 |
+
|
326 |
+
if "swim" in story.lower() and i > 0:
|
327 |
+
# Draw bubbles
|
328 |
+
for j in range(3):
|
329 |
+
bubble_x = mermaid_x + random.randint(0, 30)
|
330 |
+
bubble_y = mermaid_y - random.randint(10, 30)
|
331 |
+
draw.ellipse([bubble_x, bubble_y, bubble_x+5, bubble_y+5], fill=(173, 216, 230))
|
332 |
+
|
333 |
# Draw enemies based on theme
|
334 |
if theme == "space" and "alien" in story.lower():
|
335 |
alien_x = 200
|
|
|
351 |
draw.ellipse([dragon_x, dragon_y, dragon_x+40, dragon_y+20], fill=(178, 34, 34))
|
352 |
draw.line([(dragon_x+40, dragon_y+10), (dragon_x+60, dragon_y)], fill=(178, 34, 34), width=3)
|
353 |
|
354 |
+
elif theme == "underwater" and "shark" in story.lower():
|
355 |
+
shark_x = 220
|
356 |
+
shark_y = 80 + i*10
|
357 |
+
# Draw shark body
|
358 |
+
draw.ellipse([shark_x, shark_y, shark_x+60, shark_y+30], fill=(169, 169, 169))
|
359 |
+
# Draw shark fin
|
360 |
+
draw.polygon([(shark_x+40, shark_y), (shark_x+50, shark_y-20), (shark_x+60, shark_y)], fill=(169, 169, 169))
|
361 |
+
|
362 |
frames.append(img)
|
363 |
|
364 |
return frames
|
|
|
367 |
"""Generate code explanation using open-source model"""
|
368 |
try:
|
369 |
# Create a prompt for the model
|
370 |
+
prompt = f"Explain to a child how this story would become code: '{story}'. Use simple analogies and relate to real-world objects."
|
371 |
|
372 |
# Generate text
|
373 |
result = explanation_generator(
|
374 |
prompt,
|
375 |
+
max_length=250,
|
376 |
num_return_sequences=1,
|
377 |
)
|
378 |
|
|
|
382 |
return f"""See how your story became real code? For example, when you wrote "{story.split()[0]}",
|
383 |
we used code like: character.move(). That's how we turn words into actions!"""
|
384 |
|
385 |
+
def extract_story_elements(story, ner_model, classifier):
|
386 |
+
"""Extract hero and world from the story using AI models"""
|
387 |
try:
|
388 |
+
# Default values
|
389 |
+
hero = "spaceship"
|
390 |
+
world = "space"
|
391 |
+
|
392 |
+
# Find hero based on keywords and entities
|
393 |
+
hero_keywords = {
|
394 |
+
"spaceship": ["spaceship", "rocket", "ship", "alien", "planet", "star"],
|
395 |
+
"dragon": ["dragon", "monster", "creature", "beast", "wyvern"],
|
396 |
+
"knight": ["knight", "warrior", "prince", "princess", "king", "queen", "sword"],
|
397 |
+
"mermaid": ["mermaid", "merman", "sea", "ocean", "underwater", "fish"]
|
398 |
+
}
|
399 |
+
|
400 |
+
# Find world based on keywords and classification
|
401 |
+
world_labels = ["space", "jungle", "medieval", "underwater"]
|
402 |
+
|
403 |
+
# Find hero by keywords
|
404 |
+
story_lower = story.lower()
|
405 |
+
for candidate, keywords in hero_keywords.items():
|
406 |
+
if any(keyword in story_lower for keyword in keywords):
|
407 |
+
hero = candidate
|
408 |
+
break
|
409 |
+
|
410 |
+
# Use NER to find entities
|
411 |
+
entities = ner_model(story)
|
412 |
+
person_entities = [e['word'] for e in entities if e['entity'] in ['B-PER', 'I-PER']]
|
413 |
|
414 |
+
# If we found specific character names, adjust hero
|
415 |
+
if person_entities:
|
416 |
+
if "dragon" in story_lower:
|
417 |
+
hero = "dragon"
|
418 |
+
elif "knight" in story_lower or "king" in story_lower or "queen" in story_lower:
|
419 |
+
hero = "knight"
|
420 |
+
elif "mermaid" in story_lower or "sea" in story_lower:
|
421 |
+
hero = "mermaid"
|
422 |
|
423 |
+
# Classify world
|
424 |
+
result = classifier(story, world_labels)
|
425 |
+
world = result['labels'][0]
|
426 |
|
427 |
+
# Override based on specific keywords
|
428 |
+
if "underwater" in story_lower or "ocean" in story_lower or "sea" in story_lower:
|
429 |
+
world = "underwater"
|
430 |
+
if "forest" in story_lower or "jungle" in story_lower:
|
431 |
+
world = "jungle"
|
432 |
+
if "castle" in story_lower or "kingdom" in story_lower or "dragon" in story_lower:
|
433 |
+
world = "medieval"
|
434 |
+
if "space" in story_lower or "alien" in story_lower or "planet" in story_lower:
|
435 |
+
world = "space"
|
436 |
|
437 |
+
return hero, world
|
438 |
+
|
439 |
except Exception as e:
|
440 |
+
st.error(f"Error analyzing story: {str(e)}")
|
441 |
+
return "spaceship", "space"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
442 |
|
443 |
# Header section
|
444 |
st.markdown('<div class="header">CodeTales โจ</div>', unsafe_allow_html=True)
|
445 |
st.markdown('<div class="subheader">Storytime + Coding Magic</div>', unsafe_allow_html=True)
|
446 |
+
st.markdown('<div style="text-align:center; color:white; font-size:1.2rem; margin-bottom:2rem;">Turn wild stories into playable games with AI magic!</div>', unsafe_allow_html=True)
|
447 |
|
448 |
# How it works section
|
449 |
+
st.markdown("### โจ How It Works")
|
450 |
+
step_container = st.container()
|
451 |
+
with step_container:
|
452 |
+
cols = st.columns(4)
|
453 |
+
with cols[0]:
|
454 |
+
st.markdown('<div class="step"><div class="step-number">1</div>Write Your Story</div>', unsafe_allow_html=True)
|
455 |
+
with cols[1]:
|
456 |
+
st.markdown('<div class="step"><div class="step-number">2</div>AI Chooses Hero & World</div>', unsafe_allow_html=True)
|
457 |
+
with cols[2]:
|
458 |
+
st.markdown('<div class="step"><div class="step-number">3</div>Watch Animation</div>', unsafe_allow_html=True)
|
459 |
+
with cols[3]:
|
460 |
+
st.markdown('<div class="step"><div class="step-number">4</div>Learn Coding</div>', unsafe_allow_html=True)
|
|
|
|
|
|
|
|
|
|
|
461 |
|
462 |
# Load models
|
463 |
+
ner_model, classifier, explanation_generator = load_models()
|
464 |
|
465 |
# Initialize session state
|
466 |
if 'animation_generated' not in st.session_state:
|
|
|
475 |
st.session_state.selected_character = "spaceship"
|
476 |
if 'selected_theme' not in st.session_state:
|
477 |
st.session_state.selected_theme = "space"
|
|
|
|
|
|
|
|
|
|
|
|
|
478 |
if 'story_count' not in st.session_state:
|
479 |
st.session_state.story_count = 0
|
480 |
if 'level' not in st.session_state:
|
481 |
st.session_state.level = 1
|
482 |
if 'xp' not in st.session_state:
|
483 |
st.session_state.xp = 0
|
484 |
+
if 'analyzed' not in st.session_state:
|
485 |
+
st.session_state.analyzed = False
|
486 |
+
|
487 |
+
# Character and theme mapping
|
488 |
+
characters = {
|
489 |
+
"spaceship": "๐ Spaceship",
|
490 |
+
"dragon": "๐ Dragon",
|
491 |
+
"knight": "๐ก๏ธ Knight",
|
492 |
+
"mermaid": "๐งโโ๏ธ Mermaid"
|
493 |
+
}
|
494 |
|
495 |
+
themes = {
|
496 |
+
"space": "๐ Space",
|
497 |
+
"jungle": "๐ฟ Jungle",
|
498 |
+
"medieval": "๐ฐ Medieval",
|
499 |
+
"underwater": "๐ Underwater"
|
|
|
500 |
}
|
501 |
|
502 |
# Main content
|
|
|
507 |
|
508 |
with col1:
|
509 |
st.markdown('<div class="story-box">', unsafe_allow_html=True)
|
510 |
+
st.markdown("### ๐ Step 1: Write Your Story")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
511 |
|
512 |
story_text = st.text_area(
|
513 |
"Tell your adventure story...",
|
514 |
height=200,
|
515 |
placeholder="Once upon a time, a brave spaceship zoomed through space, shooting lasers at alien spaceships...",
|
516 |
label_visibility="collapsed",
|
517 |
+
value=st.session_state.story_text
|
518 |
)
|
519 |
|
520 |
+
if st.button("โจ Analyze My Story!", use_container_width=True, key="analyze", type="primary"):
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
521 |
if story_text.strip():
|
522 |
st.session_state.story_text = story_text
|
523 |
+
with st.spinner("๐ AI is reading your story to find the perfect hero and world..."):
|
524 |
+
# Extract story elements using AI
|
525 |
+
hero, world = extract_story_elements(story_text, ner_model, classifier)
|
526 |
+
st.session_state.selected_character = hero
|
527 |
+
st.session_state.selected_theme = world
|
528 |
+
st.session_state.analyzed = True
|
529 |
+
|
530 |
+
# Show suggestions
|
531 |
+
st.success(f"๐ฏ AI found a {characters[hero]} hero in a {themes[world]} world!")
|
532 |
+
else:
|
533 |
+
st.warning("Please enter a story first!")
|
534 |
+
|
535 |
+
if st.session_state.analyzed:
|
536 |
+
st.markdown("### ๐ง Step 2: Your Hero & World")
|
537 |
+
|
538 |
+
st.markdown(f'<div class="hero-suggestion">Your Hero: {characters[st.session_state.selected_character]}</div>',
|
539 |
+
unsafe_allow_html=True)
|
540 |
+
st.markdown(f'<div class="world-suggestion">Your World: {themes[st.session_state.selected_theme]}</div>',
|
541 |
+
unsafe_allow_html=True)
|
542 |
+
|
543 |
+
st.info("๐ก The AI chose these based on your story! Click Generate Animation when ready.")
|
544 |
+
|
545 |
+
# Generate animation button
|
546 |
+
if st.button("๐ฌ Generate Animation!", use_container_width=True, key="generate", type="primary"):
|
547 |
st.session_state.animation_generated = True
|
548 |
+
with st.spinner("๐งโโ๏ธ Creating your animation..."):
|
|
|
549 |
# Generate animation frames
|
550 |
st.session_state.animation_frames = generate_sprite_animation(
|
551 |
story_text,
|
|
|
558 |
|
559 |
# Update user progress
|
560 |
st.session_state.story_count += 1
|
561 |
+
st.session_state.xp += 20
|
562 |
|
563 |
# Check for level up
|
564 |
if st.session_state.xp >= 100:
|
565 |
st.session_state.level += 1
|
566 |
st.session_state.xp = st.session_state.xp - 100
|
567 |
st.session_state.new_level = True
|
568 |
+
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
569 |
st.markdown('</div>', unsafe_allow_html=True)
|
570 |
|
571 |
with col2:
|
572 |
st.markdown('<div class="story-box">', unsafe_allow_html=True)
|
|
|
573 |
|
574 |
if st.session_state.animation_generated and st.session_state.animation_frames:
|
575 |
+
st.markdown("### ๐ฎ Step 3: Your Animation")
|
576 |
# Display animation frames
|
577 |
st.markdown("**Your Story Comes to Life!**")
|
578 |
cols = st.columns(len(st.session_state.animation_frames))
|
|
|
599 |
use_container_width=True
|
600 |
)
|
601 |
|
|
|
|
|
|
|
|
|
602 |
# Display character and theme info
|
603 |
st.success(f"โจ Your {characters[st.session_state.selected_character]} in the {themes[st.session_state.selected_theme]} world!")
|
604 |
|
605 |
+
elif st.session_state.analyzed:
|
606 |
+
st.markdown("### ๐ฎ Step 3: Your Animation")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
607 |
st.image("https://img.freepik.com/free-vector/hand-drawn-colorful-space-background_23-2148821798.jpg",
|
608 |
use_container_width=True)
|
609 |
+
st.info("๐ Click 'Generate Animation' to bring your story to life!")
|
610 |
elif story_text:
|
611 |
+
st.markdown("### ๐ฎ Your Animation Will Appear Here")
|
612 |
preview_img = create_storyboard_image(story_text)
|
613 |
st.image(preview_img, caption="Your Story Preview", use_container_width=True)
|
614 |
+
st.info("๐ Click 'Analyze My Story' to begin the magic!")
|
615 |
else:
|
616 |
st.image("https://img.freepik.com/free-vector/hand-drawn-colorful-space-background_23-2148821798.jpg",
|
617 |
use_container_width=True)
|
618 |
+
st.info("๐ Write your story and click 'Analyze My Story' to begin!")
|
619 |
|
620 |
st.markdown('</div>', unsafe_allow_html=True)
|
621 |
|
622 |
# Tavus explanation section
|
623 |
if st.session_state.animation_generated and st.session_state.story_text:
|
624 |
st.markdown('<div class="robot-speech">', unsafe_allow_html=True)
|
625 |
+
st.markdown("### ๐ค Step 4: Tavus the Robot Teacher Explains Coding")
|
626 |
|
627 |
# Extract action words from story
|
628 |
+
action_words = ["zoom", "shoot", "fly", "move", "jump", "run", "attack",
|
629 |
+
"laser", "alien", "spaceship", "dragon", "fire", "hero",
|
630 |
+
"sword", "castle", "escape", "fight", "win", "swim", "dive"]
|
631 |
+
found_actions = [word for word in action_words if re.search(r'\b' + word + r'\b', st.session_state.story_text.lower())]
|
632 |
|
633 |
if found_actions:
|
634 |
# Create explanation based on found words
|
635 |
+
st.markdown("#### ๐งฉ Story Actions to Code Concepts")
|
636 |
+
cols = st.columns(3)
|
637 |
+
action_colors = ["#ff6b6b", "#4ecdc4", "#ffd166", "#06d6a0", "#118ab2"]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
638 |
|
639 |
+
for i, action in enumerate(found_actions[:6]):
|
640 |
+
with cols[i % 3]:
|
641 |
+
color = action_colors[i % len(action_colors)]
|
642 |
+
st.markdown(f'<div style="background:{color}; color:white; border-radius:10px; padding:10px; text-align:center;">', unsafe_allow_html=True)
|
643 |
+
st.markdown(f"**{action.capitalize()}**")
|
644 |
+
|
645 |
+
# Add code snippets based on action
|
646 |
+
if action == "zoom":
|
647 |
+
st.code("hero.move_fast(10)", language="python")
|
648 |
+
elif action == "shoot":
|
649 |
+
st.code("laser = hero.create_weapon()", language="python")
|
650 |
+
elif action == "fly":
|
651 |
+
st.code("hero.fly(height=100)", language="python")
|
652 |
+
elif action == "move":
|
653 |
+
st.code("hero.move(direction='right')", language="python")
|
654 |
+
elif action == "jump":
|
655 |
+
st.code("hero.jump(power=20)", language="python")
|
656 |
+
elif action == "run":
|
657 |
+
st.code("hero.speed = hero.speed * 2", language="python")
|
658 |
+
elif action == "attack":
|
659 |
+
st.code("hero.attack(enemy)", language="python")
|
660 |
+
elif action == "swim":
|
661 |
+
st.code("hero.swim(speed=5)", language="python")
|
662 |
+
elif action == "dive":
|
663 |
+
st.code("hero.dive(depth=30)", language="python")
|
664 |
+
else:
|
665 |
+
st.code(f"hero.{action}()", language="python")
|
666 |
+
|
667 |
+
st.markdown("</div>", unsafe_allow_html=True)
|
668 |
|
669 |
+
st.markdown("---")
|
|
|
|
|
670 |
|
671 |
# Show AI-generated explanation
|
672 |
st.markdown("### ๐ง AI-Powered Explanation:")
|
673 |
st.write(st.session_state.code_explanation)
|
674 |
|
675 |
+
st.markdown("#### ๐ก Remember:")
|
676 |
+
st.markdown("""
|
677 |
+
- Every action in your story becomes code
|
678 |
+
- Code is just instructions for computers
|
679 |
+
- You're already thinking like a coder!
|
680 |
+
""")
|
681 |
+
|
682 |
st.markdown("</div>", unsafe_allow_html=True)
|
683 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
684 |
# New level notification
|
685 |
if 'new_level' in st.session_state and st.session_state.new_level:
|
686 |
st.balloons()
|
687 |
st.success(f"๐ Level Up! You're now Level {st.session_state.level}!")
|
688 |
st.session_state.new_level = False
|
689 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
690 |
# Benefits section
|
691 |
st.markdown("""
|
692 |
+
## โค Why Kids & Parents Love CodeTales
|
693 |
|
694 |
| For Kids ๐ง๐ฆ | For Parents & Teachers ๐ช๐ฉโ๐ซ |
|
695 |
|--------------|----------------------------|
|
696 |
+
| โจ Create your own animated stories | ๐ง Teaches computational thinking |
|
697 |
+
| ๐ See imagination come to life | ๐ Develops problem-solving skills |
|
698 |
+
| ๐ฎ Interactive game creation | โ Reinforces STEM fundamentals |
|
699 |
+
| ๐ Fun characters and worlds | ๐งฉ Encourages logical reasoning |
|
700 |
+
| ๐ Unlock new levels | ๐ Tracks learning progress |
|
|
|
|
|
701 |
""")
|
702 |
|
703 |
# Footer
|
|
|
705 |
st.markdown("""
|
706 |
<center>
|
707 |
<p style="color:white; font-size:1.1rem;">
|
708 |
+
โจ Made with magic by CodeTales Team โจ<br>
|
709 |
+
Transforming stories into games since 2023
|
710 |
</p>
|
711 |
</center>
|
712 |
""", unsafe_allow_html=True)
|