Spaces:
Sleeping
Sleeping
# app.py | |
import streamlit as st | |
import time | |
import random | |
import json | |
import textwrap | |
import os | |
import base64 | |
from io import BytesIO | |
from PIL import Image, ImageDraw, ImageFont | |
import numpy as np | |
from gtts import gTTS | |
from pygame import mixer | |
import tempfile | |
import requests | |
from transformers import pipeline, set_seed | |
# Set up the page | |
st.set_page_config( | |
page_title="CodeTales โจ", | |
page_icon="๐", | |
layout="wide", | |
initial_sidebar_state="expanded" | |
) | |
# Initialize pygame mixer | |
mixer.init() | |
# Custom CSS | |
st.markdown(""" | |
<style> | |
@import url('https://fonts.googleapis.com/css2?family=Comic+Neue:wght@700&family=Press+Start+2P&display=swap'); | |
.stApp { | |
background: linear-gradient(135deg, #6e8efb, #a777e3); | |
} | |
.header { | |
font-family: 'Press Start 2P', cursive; | |
color: white; | |
text-align: center; | |
font-size: 2.5rem; | |
text-shadow: 3px 3px 0 #000; | |
padding: 1rem; | |
letter-spacing: 2px; | |
} | |
.subheader { | |
font-family: 'Comic Neue', cursive; | |
color: #ffeb3b; | |
text-align: center; | |
font-size: 1.8rem; | |
margin-bottom: 2rem; | |
} | |
.story-box { | |
background-color: rgba(255, 255, 255, 0.9); | |
border-radius: 20px; | |
padding: 2rem; | |
box-shadow: 0 8px 16px rgba(0,0,0,0.2); | |
margin-bottom: 2rem; | |
border: 4px solid #ff6b6b; | |
} | |
.robot-speech { | |
background-color: #4caf50; | |
color: white; | |
border-radius: 20px; | |
padding: 1.5rem; | |
font-size: 1.2rem; | |
position: relative; | |
margin-top: 2rem; | |
border: 3px solid #2e7d32; | |
} | |
.robot-speech:after { | |
content: ''; | |
position: absolute; | |
top: -20px; | |
left: 50px; | |
border: 10px solid transparent; | |
border-bottom-color: #4caf50; | |
} | |
.generate-btn { | |
background: linear-gradient(135deg, #ff5722, #ff9800) !important; | |
color: white !important; | |
font-weight: bold !important; | |
font-size: 1.2rem !important; | |
padding: 0.7rem 2rem !important; | |
border-radius: 50px !important; | |
border: none !important; | |
box-shadow: 0 4px 8px rgba(0,0,0,0.3) !important; | |
transition: all 0.3s !important; | |
margin-top: 1rem; | |
font-family: 'Press Start 2P', cursive !important; | |
letter-spacing: 1px; | |
} | |
.generate-btn:hover { | |
transform: scale(1.05) !important; | |
box-shadow: 0 6px 12px rgba(0,0,0,0.4) !important; | |
background: linear-gradient(135deg, #ff7043, #ffa726) !important; | |
} | |
.code-block { | |
background: #2d2d2d; | |
color: #f8f8f2; | |
padding: 1rem; | |
border-radius: 10px; | |
font-family: 'Courier New', monospace; | |
font-size: 1.1rem; | |
margin: 1rem 0; | |
overflow-x: auto; | |
border-left: 4px solid #ff9800; | |
} | |
.animation-frame { | |
border: 3px solid #ffeb3b; | |
border-radius: 10px; | |
margin: 5px; | |
} | |
.achievement-badge { | |
background: #ffd54f; | |
color: #333; | |
border-radius: 50%; | |
width: 60px; | |
height: 60px; | |
display: flex; | |
align-items: center; | |
justify-content: center; | |
font-size: 1.5rem; | |
box-shadow: 0 4px 8px rgba(0,0,0,0.2); | |
margin: 0 auto; | |
} | |
.character-option { | |
border: 3px solid transparent; | |
border-radius: 10px; | |
padding: 5px; | |
margin: 5px; | |
cursor: pointer; | |
transition: all 0.3s; | |
} | |
.character-option:hover { | |
transform: scale(1.05); | |
} | |
.character-option.selected { | |
border-color: #ff5722; | |
} | |
.tab-content { | |
padding: 1rem; | |
border: 2px solid #4caf50; | |
border-radius: 10px; | |
background: rgba(255, 255, 255, 0.8); | |
margin-top: 1rem; | |
} | |
.level-indicator { | |
background: #4caf50; | |
color: white; | |
border-radius: 20px; | |
padding: 0.5rem 1rem; | |
display: inline-block; | |
font-weight: bold; | |
margin-bottom: 1rem; | |
} | |
/* Progress bar styling */ | |
.stProgress > div > div > div { | |
background-color: #ff5722 !important; | |
} | |
</style> | |
""", unsafe_allow_html=True) | |
# Initialize AI models | |
def load_models(): | |
"""Load open-source AI models""" | |
try: | |
# Text generation model for code explanations | |
story_to_code = pipeline("text-generation", model="gpt2", max_length=100) | |
# Sentiment analysis for story understanding | |
sentiment_analyzer = pipeline("sentiment-analysis", model="distilbert-base-uncased-finetuned-sst-2-english") | |
# Named entity recognition for identifying objects | |
ner_model = pipeline("ner", model="dbmdz/bert-large-cased-finetuned-conll03-english") | |
# Text-to-text generation for better explanations | |
explanation_generator = pipeline("text2text-generation", model="google/flan-t5-base") | |
return story_to_code, sentiment_analyzer, ner_model, explanation_generator | |
except Exception as e: | |
st.error(f"Error loading models: {e}") | |
return None, None, None, None | |
# Image generation functions | |
def create_storyboard_image(text, width=400, height=300): | |
"""Create a storyboard image from text""" | |
# Create blank image | |
img = Image.new('RGB', (width, height), color=(25, 25, 112)) # Dark blue background | |
# Load a comic-style font (fallback to default if not available) | |
try: | |
font = ImageFont.truetype("comic.ttf", 16) | |
except: | |
font = ImageFont.load_default() | |
draw = ImageDraw.Draw(img) | |
# Draw title | |
draw.text((10, 10), "Your Story Comes to Life!", fill=(255, 215, 0), font=font) | |
# Draw text box | |
draw.rectangle([10, 40, width-10, height-40], fill=(240, 248, 255), outline=(255, 215, 0), width=2) | |
# Wrap text | |
wrapped_text = textwrap.fill(text, width=40) | |
draw.text((20, 50), wrapped_text, fill=(25, 25, 112), font=font) | |
# Draw decorations | |
draw.rectangle([width-50, height-30, width-30, height-10], fill=(220, 20, 60), outline=(255, 215, 0), width=1) | |
draw.ellipse([20, height-50, 70, height], fill=(30, 144, 255), outline=(255, 215, 0), width=1) | |
return img | |
def generate_sprite_animation(story, character="spaceship", theme="space", num_frames=4): | |
"""Generate a sprite-based animation from story""" | |
frames = [] | |
width, height = 300, 200 | |
for i in range(num_frames): | |
# Create base image with theme | |
if theme == "space": | |
bg_color = (0, 0, 30) | |
star_color = (255, 255, 255) | |
elif theme == "jungle": | |
bg_color = (0, 100, 0) | |
star_color = None # No stars in jungle | |
elif theme == "medieval": | |
bg_color = (139, 69, 19) | |
star_color = None | |
else: | |
bg_color = (0, 0, 30) | |
star_color = (255, 255, 255) | |
img = Image.new('RGB', (width, height), color=bg_color) | |
draw = ImageDraw.Draw(img) | |
# Draw stars for space theme | |
if star_color: | |
for _ in range(30): | |
x = random.randint(0, width) | |
y = random.randint(0, height) | |
draw.ellipse([x, y, x+2, y+2], fill=star_color) | |
# Draw moving elements based on frame | |
if character == "spaceship": | |
ship_x = 50 + i * 60 | |
ship_y = 80 | |
draw.polygon([(ship_x, ship_y), (ship_x+30, ship_y), | |
(ship_x+15, ship_y-20)], fill=(169, 169, 169)) | |
if "shoot" in story.lower() and i > 1: | |
for j in range(3): | |
laser_x = ship_x + 15 | |
laser_y = ship_y - 20 + j*5 | |
draw.line([(laser_x, laser_y), (width, laser_y)], fill=(255, 0, 0), width=2) | |
elif character == "dragon": | |
dragon_x = 50 + i * 40 | |
dragon_y = 100 | |
# Draw dragon body | |
draw.ellipse([dragon_x, dragon_y, dragon_x+40, dragon_y+20], fill=(178, 34, 34)) | |
# Draw dragon head | |
draw.ellipse([dragon_x+30, dragon_y-5, dragon_x+50, dragon_y+15], fill=(178, 34, 34)) | |
# Draw wings | |
draw.ellipse([dragon_x+10, dragon_y-15, dragon_x+30, dragon_y], fill=(138, 43, 226)) | |
if "fire" in story.lower() and i > 0: | |
for j in range(5): | |
flame_x = dragon_x + 50 | |
flame_y = dragon_y + 5 - j*5 | |
flame_size = random.randint(5, 15) | |
draw.ellipse([flame_x, flame_y, flame_x+flame_size, flame_y+flame_size], | |
fill=(255, random.randint(100, 200), 0)) | |
elif character == "knight": | |
knight_x = 50 + i * 40 | |
knight_y = 120 | |
# Draw knight body | |
draw.rectangle([knight_x, knight_y, knight_x+20, knight_y+40], fill=(70, 70, 70)) | |
# Draw knight head | |
draw.ellipse([knight_x+5, knight_y-15, knight_x+15, knight_y-5], fill=(210, 180, 140)) | |
# Draw sword | |
draw.rectangle([knight_x+15, knight_y+10, knight_x+25, knight_y+15], fill=(192, 192, 192)) | |
draw.polygon([(knight_x+25, knight_y+12), (knight_x+35, knight_y+10), (knight_x+35, knight_y+15)], fill=(192, 192, 192)) | |
if "attack" in story.lower() and i % 2 == 1: | |
# Draw sword swing | |
draw.line([(knight_x+25, knight_y+12), (knight_x+45, knight_y-10)], fill=(255, 255, 0), width=2) | |
# Draw enemies based on theme | |
if theme == "space" and "alien" in story.lower(): | |
alien_x = 200 | |
alien_y = 100 - i*10 | |
draw.ellipse([alien_x, alien_y, alien_x+20, alien_y+20], fill=(50, 205, 50)) | |
draw.ellipse([alien_x+5, alien_y+5, alien_x+7, alien_y+7], fill=(0, 0, 0)) | |
draw.ellipse([alien_x+13, alien_y+5, alien_x+15, alien_y+7], fill=(0, 0, 0)) | |
elif theme == "jungle" and "snake" in story.lower(): | |
snake_x = 200 | |
snake_y = 150 - i*5 | |
for segment in range(5): | |
offset = segment * 5 | |
draw.ellipse([snake_x+offset, snake_y+offset, snake_x+offset+15, snake_y+offset+15], fill=(0, 128, 0)) | |
elif theme == "medieval" and "dragon" in story.lower() and character != "dragon": | |
dragon_x = 220 | |
dragon_y = 80 | |
draw.ellipse([dragon_x, dragon_y, dragon_x+40, dragon_y+20], fill=(178, 34, 34)) | |
draw.line([(dragon_x+40, dragon_y+10), (dragon_x+60, dragon_y)], fill=(178, 34, 34), width=3) | |
frames.append(img) | |
return frames | |
def generate_code_explanation(story, explanation_generator): | |
"""Generate code explanation using open-source model""" | |
try: | |
# Create a prompt for the model | |
prompt = f"Explain to a child how this story would become code: '{story}'" | |
# Generate text | |
result = explanation_generator( | |
prompt, | |
max_length=200, | |
num_return_sequences=1, | |
) | |
return result[0]['generated_text'] | |
except: | |
# Fallback explanation if model fails | |
return f"""See how your story became real code? For example, when you wrote "{story.split()[0]}", | |
we used code like: character.move(). That's how we turn words into actions!""" | |
def text_to_speech(text, lang='en'): | |
"""Convert text to speech using gTTS""" | |
try: | |
tts = gTTS(text=text, lang=lang, slow=False) | |
with tempfile.NamedTemporaryFile(delete=True, suffix='.mp3') as fp: | |
tts.save(fp.name) | |
return fp.name | |
except Exception as e: | |
st.error(f"Text-to-speech error: {e}") | |
return None | |
# Achievement system | |
ACHIEVEMENTS = { | |
"first_story": {"name": "Storyteller", "icon": "๐", "description": "Created your first story!"}, | |
"code_master": {"name": "Code Master", "icon": "๐ป", "description": "Used 5 different coding concepts"}, | |
"animator": {"name": "Animator", "icon": "๐ฌ", "description": "Created 3 animations"}, | |
"voice_artist": {"name": "Voice Artist", "icon": "๐ค", "description": "Used text-to-speech feature"}, | |
"character_designer": {"name": "Character Designer", "icon": "๐พ", "description": "Created a custom character"} | |
} | |
def unlock_achievement(achievement_id): | |
"""Unlock an achievement for the user""" | |
if achievement_id not in st.session_state.achievements_unlocked: | |
st.session_state.achievements_unlocked.append(achievement_id) | |
st.session_state.new_achievement = achievement_id | |
return True | |
return False | |
# Header section | |
st.markdown('<div class="header">CodeTales โจ</div>', unsafe_allow_html=True) | |
st.markdown('<div class="subheader">Storytime + Coding Magic</div>', unsafe_allow_html=True) | |
st.markdown('<div style="text-align:center; color:white; font-size:1.2rem; margin-bottom:2rem;">Turn wild stories into playable games with open-source AI magic!</div>', unsafe_allow_html=True) | |
# How it works section | |
with st.expander("โจ How It Works (Like Baking a Cake) ๐"): | |
st.markdown(""" | |
**1. Kids Write a Story (The Recipe)** | |
Example: *"The spaceship zooms past aliens and shoots lasers!"* | |
**2. AI is the Magic Oven ๐ฎ** | |
We turn words into code using open-source AI models | |
**3. Animation Pops Out (The Cake!) ๐ฎ** | |
See your story come to life with custom animations! | |
**4. Robot Teacher Explains ๐ค** | |
Tavus shows: *"See? 'spaceship.move_right()' makes it fly! That's coding!"* | |
**5. Level Up! ๐** | |
Earn achievements as you learn and create! | |
""") | |
# Load models | |
story_to_code, sentiment_analyzer, ner_model, explanation_generator = load_models() | |
# Initialize session state | |
if 'animation_generated' not in st.session_state: | |
st.session_state.animation_generated = False | |
if 'story_text' not in st.session_state: | |
st.session_state.story_text = "" | |
if 'animation_frames' not in st.session_state: | |
st.session_state.animation_frames = [] | |
if 'code_explanation' not in st.session_state: | |
st.session_state.code_explanation = "" | |
if 'selected_character' not in st.session_state: | |
st.session_state.selected_character = "spaceship" | |
if 'selected_theme' not in st.session_state: | |
st.session_state.selected_theme = "space" | |
if 'sound_enabled' not in st.session_state: | |
st.session_state.sound_enabled = True | |
if 'achievements_unlocked' not in st.session_state: | |
st.session_state.achievements_unlocked = [] | |
if 'new_achievement' not in st.session_state: | |
st.session_state.new_achievement = None | |
if 'story_count' not in st.session_state: | |
st.session_state.story_count = 0 | |
if 'level' not in st.session_state: | |
st.session_state.level = 1 | |
if 'xp' not in st.session_state: | |
st.session_state.xp = 0 | |
if 'voice_enabled' not in st.session_state: | |
st.session_state.voice_enabled = False | |
if 'custom_character' not in st.session_state: | |
st.session_state.custom_character = None | |
# Story templates | |
story_templates = { | |
"None": "", | |
"Space Adventure": "A brave spaceship zooms through space, shooting lasers at alien spaceships!", | |
"Dragon Quest": "A knight fights a fire-breathing dragon to save the princess in the castle.", | |
"Jungle Explorer": "An explorer runs through the jungle, jumping over snakes and swinging on vines." | |
} | |
# Main content | |
st.markdown(f'<div class="level-indicator">Level {st.session_state.level} โจ XP: {st.session_state.xp}/100</div>', unsafe_allow_html=True) | |
progress = st.progress(st.session_state.xp / 100) | |
col1, col2 = st.columns([1, 1]) | |
with col1: | |
st.markdown('<div class="story-box">', unsafe_allow_html=True) | |
st.markdown("### ๐ Write Your Story Here:") | |
# Story template selector | |
selected_template = st.selectbox( | |
"Or choose a story template to start with:", | |
list(story_templates.keys()), | |
index=0 | |
) | |
story_text = st.text_area( | |
"Tell your adventure story...", | |
height=200, | |
placeholder="Once upon a time, a brave spaceship zoomed through space, shooting lasers at alien spaceships...", | |
label_visibility="collapsed", | |
value=story_templates[selected_template] if selected_template != "None" else st.session_state.story_text | |
) | |
# Character selection | |
st.markdown("### ๐ง Choose Your Hero") | |
char_cols = st.columns(3) | |
characters = { | |
"spaceship": "๐ Spaceship", | |
"dragon": "๐ Dragon", | |
"knight": "๐ก๏ธ Knight" | |
} | |
for i, (char_key, char_label) in enumerate(characters.items()): | |
with char_cols[i]: | |
if st.button( | |
char_label, | |
key=f"char_{char_key}", | |
use_container_width=True, | |
type="primary" if st.session_state.selected_character == char_key else "secondary" | |
): | |
st.session_state.selected_character = char_key | |
# Theme selection | |
st.markdown("### ๐จ Choose Your World") | |
theme_cols = st.columns(3) | |
themes = { | |
"space": "๐ Space", | |
"jungle": "๐ฟ Jungle", | |
"medieval": "๐ฐ Medieval" | |
} | |
for i, (theme_key, theme_label) in enumerate(themes.items()): | |
with theme_cols[i]: | |
if st.button( | |
theme_label, | |
key=f"theme_{theme_key}", | |
use_container_width=True, | |
type="primary" if st.session_state.selected_theme == theme_key else "secondary" | |
): | |
st.session_state.selected_theme = theme_key | |
# Voice options | |
st.session_state.voice_enabled = st.toggle( | |
"๐ฃ๏ธ Enable Tavus Voice", | |
value=st.session_state.voice_enabled, | |
help="Hear Tavus explain coding concepts with text-to-speech" | |
) | |
# Generate button with animation | |
if st.button("โจ Generate Animation!", use_container_width=True, key="generate", type="primary"): | |
if story_text.strip(): | |
st.session_state.story_text = story_text | |
st.session_state.animation_generated = True | |
with st.spinner("๐งโโ๏ธ Cooking your story in the magic oven..."): | |
# Generate animation frames | |
st.session_state.animation_frames = generate_sprite_animation( | |
story_text, | |
character=st.session_state.selected_character, | |
theme=st.session_state.selected_theme | |
) | |
# Generate code explanation | |
st.session_state.code_explanation = generate_code_explanation(story_text, explanation_generator) | |
# Update user progress | |
st.session_state.story_count += 1 | |
st.session_state.xp += 15 | |
# Check for level up | |
if st.session_state.xp >= 100: | |
st.session_state.level += 1 | |
st.session_state.xp = st.session_state.xp - 100 | |
st.session_state.new_level = True | |
# Unlock achievements | |
if st.session_state.story_count == 1: | |
unlock_achievement("first_story") | |
# Generate speech if enabled | |
if st.session_state.voice_enabled: | |
speech_file = text_to_speech(st.session_state.code_explanation) | |
if speech_file: | |
st.session_state.speech_file = speech_file | |
unlock_achievement("voice_artist") | |
# Simulate sound effect | |
if st.session_state.sound_enabled: | |
st.session_state.sound_message = "๐ Sound effects added: Laser blasts, explosions, and more!" | |
else: | |
st.session_state.sound_message = "" | |
else: | |
st.warning("Please enter a story first!") | |
st.markdown('</div>', unsafe_allow_html=True) | |
with col2: | |
st.markdown('<div class="story-box">', unsafe_allow_html=True) | |
st.markdown("### ๐ฎ Your Game Animation") | |
if st.session_state.animation_generated and st.session_state.animation_frames: | |
# Display animation frames | |
st.markdown("**Your Story Comes to Life!**") | |
cols = st.columns(len(st.session_state.animation_frames)) | |
for i, frame in enumerate(st.session_state.animation_frames): | |
with cols[i]: | |
st.image(frame, caption=f"Frame {i+1}", use_container_width=True) | |
# Create an animated GIF | |
gif_buffer = BytesIO() | |
st.session_state.animation_frames[0].save( | |
gif_buffer, | |
format='GIF', | |
save_all=True, | |
append_images=st.session_state.animation_frames[1:], | |
duration=500, | |
loop=0 | |
) | |
gif_data = gif_buffer.getvalue() | |
st.download_button( | |
label="โฌ๏ธ Download Animation", | |
data=gif_data, | |
file_name="your_story.gif", | |
mime="image/gif", | |
use_container_width=True | |
) | |
# Display sound message | |
if st.session_state.sound_enabled and 'sound_message' in st.session_state: | |
st.info(st.session_state.sound_message) | |
# Display character and theme info | |
st.success(f"โจ Your {characters[st.session_state.selected_character]} in the {themes[st.session_state.selected_theme]} world!") | |
# Play voice explanation | |
if st.session_state.voice_enabled and 'speech_file' in st.session_state: | |
if st.button("๐ Play Tavus Explanation", use_container_width=True): | |
try: | |
mixer.music.load(st.session_state.speech_file) | |
mixer.music.play() | |
except: | |
st.warning("Couldn't play audio. Try enabling sound in your browser.") | |
elif st.session_state.animation_generated: | |
st.warning("Couldn't generate animation. Try a different story!") | |
st.image("https://img.freepik.com/free-vector/hand-drawn-colorful-space-background_23-2148821798.jpg", | |
use_container_width=True) | |
elif story_text: | |
# Show storyboard preview | |
preview_img = create_storyboard_image(story_text) | |
st.image(preview_img, caption="Your Story Preview", use_container_width=True) | |
st.info("๐ Click Generate to bring your story to life!") | |
else: | |
st.image("https://img.freepik.com/free-vector/hand-drawn-colorful-space-background_23-2148821798.jpg", | |
use_container_width=True) | |
st.info("๐ Write your story and click Generate to see the magic!") | |
st.markdown('</div>', unsafe_allow_html=True) | |
# Tavus explanation section | |
if st.session_state.animation_generated and st.session_state.story_text: | |
st.markdown('<div class="robot-speech">', unsafe_allow_html=True) | |
st.markdown("### ๐ค Tavus the Robot Teacher says:") | |
# Extract action words from story | |
action_words = ["zoom", "shoot", "fly", "move", "jump", "run", "attack", "laser", "alien", "spaceship", | |
"dragon", "fire", "hero", "sword", "castle", "run", "escape", "fight", "win"] | |
found_actions = [word for word in action_words if word in st.session_state.story_text.lower()] | |
if found_actions: | |
# Create explanation based on found words | |
explanations = [] | |
code_snippets = [] | |
for action in found_actions[:3]: # Limit to 3 explanations | |
if action == "zoom": | |
explanations.append("makes your spaceship move super fast!") | |
code_snippets.append("spaceship.accelerate(speed=10)") | |
elif action == "shoot": | |
explanations.append("creates laser beams to defeat enemies!") | |
code_snippets.append("laser = spaceship.fire_weapon()") | |
elif action == "fly": | |
explanations.append("keeps your character moving through the air!") | |
code_snippets.append("character.apply_gravity(False)") | |
elif action == "move": | |
explanations.append("changes position on the screen!") | |
code_snippets.append("player.move(direction='right')") | |
elif action == "jump": | |
explanations.append("makes your character leap upwards!") | |
code_snippets.append("hero.jump(height=100)") | |
elif action == "run": | |
explanations.append("makes your character move faster!") | |
code_snippets.append("player.speed = player.speed * 2") | |
elif action == "attack": | |
explanations.append("lets your hero fight the bad guys!") | |
code_snippets.append("sword.swing(damage=15)") | |
elif action == "laser": | |
explanations.append("creates powerful energy beams!") | |
code_snippets.append("weapon = Laser(color='red', damage=20)") | |
elif action == "alien": | |
explanations.append("adds enemies to your game!") | |
code_snippets.append("enemy = Alien(position=(100, 200))") | |
elif action == "spaceship": | |
explanations.append("creates your main character!") | |
code_snippets.append("player = Spaceship(image='spaceship.png')") | |
elif action == "dragon": | |
explanations.append("adds a fire-breathing challenge!") | |
code_snippets.append("boss = Dragon(health=100, damage=25)") | |
elif action == "fire": | |
explanations.append("creates dangerous fire effects!") | |
code_snippets.append("flame = FireEffect(position, size=30)") | |
elif action == "hero": | |
explanations.append("creates the main player character!") | |
code_snippets.append("player = Hero(name='Your Hero')") | |
elif action == "castle": | |
explanations.append("adds a majestic building to your world!") | |
code_snippets.append("castle = Building(type='castle', position=(300, 150))") | |
elif action == "escape": | |
explanations.append("creates exciting chase sequences!") | |
code_snippets.append("player.start_escape_sequence(speed=15)") | |
elif action == "fight": | |
explanations.append("initiates battle mechanics!") | |
code_snippets.append("initiate_combat(enemy)") | |
elif action == "win": | |
explanations.append("creates victory conditions!") | |
code_snippets.append("if player.score > 100: show_victory_screen()") | |
st.markdown("See how your story became real code? Here's what happened:") | |
for i, (action, explanation, snippet) in enumerate(zip(found_actions, explanations, code_snippets)): | |
st.markdown(f"**{i+1}. When you said '{action}'**:") | |
st.markdown(f'<div class="code-block">{snippet}</div>', unsafe_allow_html=True) | |
st.markdown(f"This code {explanation}") | |
st.markdown("That's the magic of coding - turning words into actions!") | |
else: | |
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!") | |
# Show AI-generated explanation | |
st.markdown("### ๐ง AI-Powered Explanation:") | |
st.write(st.session_state.code_explanation) | |
st.markdown("</div>", unsafe_allow_html=True) | |
# Achievements section | |
if st.session_state.achievements_unlocked: | |
st.markdown("### ๐ Your Achievements") | |
achievement_cols = st.columns(5) | |
for i, ach_id in enumerate(st.session_state.achievements_unlocked): | |
with achievement_cols[i % 5]: | |
ach = ACHIEVEMENTS[ach_id] | |
st.markdown(f'<div class="achievement-badge">{ach["icon"]}</div>', unsafe_allow_html=True) | |
st.markdown(f"**{ach['name']}**") | |
st.caption(ach['description']) | |
# New achievement notification | |
if 'new_achievement' in st.session_state and st.session_state.new_achievement: | |
ach = ACHIEVEMENTS[st.session_state.new_achievement] | |
st.success(f"๐ Achievement Unlocked: {ach['name']} - {ach['description']}") | |
st.session_state.new_achievement = None | |
# New level notification | |
if 'new_level' in st.session_state and st.session_state.new_level: | |
st.balloons() | |
st.success(f"๐ Level Up! You're now Level {st.session_state.level}!") | |
st.session_state.new_level = False | |
# Coding challenges section | |
st.markdown("### ๐ป Coding Challenges") | |
with st.expander("Complete challenges to earn XP!"): | |
challenge_cols = st.columns(3) | |
with challenge_cols[0]: | |
st.markdown("#### Challenge 1: The Mover") | |
st.code(""" | |
# Make the character move to the right | |
character.move_right(10) | |
""") | |
if st.button("Run Challenge 1", key="challenge1"): | |
st.session_state.xp += 5 | |
st.success("+5 XP! Character moved right!") | |
with challenge_cols[1]: | |
st.markdown("#### Challenge 2: The Jumper") | |
st.code(""" | |
# Make the character jump | |
character.jump(20) | |
""") | |
if st.button("Run Challenge 2", key="challenge2"): | |
st.session_state.xp += 5 | |
st.success("+5 XP! Character jumped!") | |
with challenge_cols[2]: | |
st.markdown("#### Challenge 3: The Shooter") | |
st.code(""" | |
# Make the character shoot a laser | |
laser = character.shoot() | |
""") | |
if st.button("Run Challenge 3", key="challenge3"): | |
st.session_state.xp += 5 | |
st.success("+5 XP! Laser fired!") | |
# Benefits section | |
st.markdown(""" | |
## โค Why Everyone Will Love CodeTales | |
| For Kids ๐ง๐ฆ | For Parents & Teachers ๐ช๐ฉโ๐ซ | | |
|--------------|----------------------------| | |
| โจ Feels like playing, not learning | ๐ง Secretly teaches programming concepts | | |
| ๐ Brings imagination to life | ๐ Develops logical thinking skills | | |
| ๐ฎ Creates personal video games | โ Reinforces math fundamentals | | |
| ๐ Makes learning fun and exciting | ๐งฉ Encourages problem-solving abilities | | |
| ๐ 100% private with no API keys | ๐ฐ Completely free and open-source | | |
| ๐ Achievement system motivates learning | ๐ Progress tracking shows growth | | |
| ๐ฃ๏ธ Voice explanations for accessibility | ๐จ Creative expression through stories | | |
""") | |
# Footer | |
st.markdown("---") | |
st.markdown(""" | |
<center> | |
<p style="color:white; font-size:1.1rem;"> | |
โจ Made with open-source magic by CodeTales Team โจ<br> | |
Transforming stories into games since 2023 | No API Keys Required! | |
</p> | |
</center> | |
""", unsafe_allow_html=True) |