Spaces:
Sleeping
Sleeping
# app.py | |
import streamlit as st | |
import time | |
import random | |
import textwrap | |
from io import BytesIO | |
from PIL import Image, ImageDraw, ImageFont | |
import numpy as np | |
from transformers import pipeline | |
import base64 | |
import re | |
# Set up the page | |
st.set_page_config( | |
page_title="CodeTales โจ", | |
page_icon="๐", | |
layout="wide", | |
initial_sidebar_state="expanded" | |
) | |
# 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; | |
} | |
.hero-suggestion { | |
background: #ffeb3b; | |
color: #333; | |
border-radius: 10px; | |
padding: 1rem; | |
margin: 1rem 0; | |
text-align: center; | |
font-weight: bold; | |
border: 2px dashed #ff9800; | |
} | |
.world-suggestion { | |
background: #4caf50; | |
color: white; | |
border-radius: 10px; | |
padding: 1rem; | |
margin: 1rem 0; | |
text-align: center; | |
font-weight: bold; | |
border: 2px dashed #2e7d32; | |
} | |
.step-container { | |
display: flex; | |
justify-content: space-between; | |
margin-bottom: 2rem; | |
} | |
.step { | |
background: rgba(255, 255, 255, 0.9); | |
border-radius: 10px; | |
padding: 1rem; | |
width: 23%; | |
text-align: center; | |
box-shadow: 0 4px 8px rgba(0,0,0,0.1); | |
} | |
.step-number { | |
font-size: 1.5rem; | |
font-weight: bold; | |
background: #ff5722; | |
color: white; | |
border-radius: 50%; | |
width: 30px; | |
height: 30px; | |
display: flex; | |
align-items: center; | |
justify-content: center; | |
margin: 0 auto 10px; | |
} | |
/* 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: | |
# Named entity recognition for identifying objects | |
ner_model = pipeline("ner", model="dbmdz/bert-large-cased-finetuned-conll03-english") | |
# Text classification for theme detection | |
classifier = pipeline("zero-shot-classification", model="facebook/bart-large-mnli") | |
# Text generation for code explanations | |
explanation_generator = pipeline("text2text-generation", model="google/flan-t5-large") | |
return ner_model, classifier, explanation_generator | |
except Exception as e: | |
st.error(f"Error loading models: {e}") | |
return 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 | |
elif theme == "underwater": | |
bg_color = (0, 105, 148) | |
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) | |
elif character == "mermaid": | |
mermaid_x = 50 + i * 40 | |
mermaid_y = 120 | |
# Draw mermaid tail | |
draw.ellipse([mermaid_x, mermaid_y, mermaid_x+30, mermaid_y+20], fill=(255, 105, 180)) | |
# Draw mermaid body | |
draw.ellipse([mermaid_x+5, mermaid_y-20, mermaid_x+25, mermaid_y], fill=(255, 218, 185)) | |
# Draw hair | |
draw.ellipse([mermaid_x-5, mermaid_y-25, mermaid_x+30, mermaid_y-15], fill=(255, 215, 0)) | |
if "swim" in story.lower() and i > 0: | |
# Draw bubbles | |
for j in range(3): | |
bubble_x = mermaid_x + random.randint(0, 30) | |
bubble_y = mermaid_y - random.randint(10, 30) | |
draw.ellipse([bubble_x, bubble_y, bubble_x+5, bubble_y+5], fill=(173, 216, 230)) | |
# 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) | |
elif theme == "underwater" and "shark" in story.lower(): | |
shark_x = 220 | |
shark_y = 80 + i*10 | |
# Draw shark body | |
draw.ellipse([shark_x, shark_y, shark_x+60, shark_y+30], fill=(169, 169, 169)) | |
# Draw shark fin | |
draw.polygon([(shark_x+40, shark_y), (shark_x+50, shark_y-20), (shark_x+60, shark_y)], fill=(169, 169, 169)) | |
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}'. Use simple analogies and relate to real-world objects." | |
# Generate text | |
result = explanation_generator( | |
prompt, | |
max_length=250, | |
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 extract_story_elements(story, ner_model, classifier): | |
"""Extract hero and world from the story using AI models""" | |
try: | |
# Default values | |
hero = "spaceship" | |
world = "space" | |
# Find hero based on keywords and entities | |
hero_keywords = { | |
"spaceship": ["spaceship", "rocket", "ship", "alien", "planet", "star"], | |
"dragon": ["dragon", "monster", "creature", "beast", "wyvern"], | |
"knight": ["knight", "warrior", "prince", "princess", "king", "queen", "sword"], | |
"mermaid": ["mermaid", "merman", "sea", "ocean", "underwater", "fish"] | |
} | |
# Find world based on keywords and classification | |
world_labels = ["space", "jungle", "medieval", "underwater"] | |
# Find hero by keywords | |
story_lower = story.lower() | |
for candidate, keywords in hero_keywords.items(): | |
if any(keyword in story_lower for keyword in keywords): | |
hero = candidate | |
break | |
# Use NER to find entities | |
entities = ner_model(story) | |
person_entities = [e['word'] for e in entities if e['entity'] in ['B-PER', 'I-PER']] | |
# If we found specific character names, adjust hero | |
if person_entities: | |
if "dragon" in story_lower: | |
hero = "dragon" | |
elif "knight" in story_lower or "king" in story_lower or "queen" in story_lower: | |
hero = "knight" | |
elif "mermaid" in story_lower or "sea" in story_lower: | |
hero = "mermaid" | |
# Classify world | |
result = classifier(story, world_labels) | |
world = result['labels'][0] | |
# Override based on specific keywords | |
if "underwater" in story_lower or "ocean" in story_lower or "sea" in story_lower: | |
world = "underwater" | |
if "forest" in story_lower or "jungle" in story_lower: | |
world = "jungle" | |
if "castle" in story_lower or "kingdom" in story_lower or "dragon" in story_lower: | |
world = "medieval" | |
if "space" in story_lower or "alien" in story_lower or "planet" in story_lower: | |
world = "space" | |
return hero, world | |
except Exception as e: | |
st.error(f"Error analyzing story: {str(e)}") | |
return "spaceship", "space" | |
# 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 AI magic!</div>', unsafe_allow_html=True) | |
# How it works section | |
st.markdown("### โจ How It Works") | |
step_container = st.container() | |
with step_container: | |
cols = st.columns(4) | |
with cols[0]: | |
st.markdown('<div class="step"><div class="step-number">1</div>Write Your Story</div>', unsafe_allow_html=True) | |
with cols[1]: | |
st.markdown('<div class="step"><div class="step-number">2</div>AI Chooses Hero & World</div>', unsafe_allow_html=True) | |
with cols[2]: | |
st.markdown('<div class="step"><div class="step-number">3</div>Watch Animation</div>', unsafe_allow_html=True) | |
with cols[3]: | |
st.markdown('<div class="step"><div class="step-number">4</div>Learn Coding</div>', unsafe_allow_html=True) | |
# Load models | |
ner_model, classifier, 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 '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 'analyzed' not in st.session_state: | |
st.session_state.analyzed = False | |
# Character and theme mapping | |
characters = { | |
"spaceship": "๐ Spaceship", | |
"dragon": "๐ Dragon", | |
"knight": "๐ก๏ธ Knight", | |
"mermaid": "๐งโโ๏ธ Mermaid" | |
} | |
themes = { | |
"space": "๐ Space", | |
"jungle": "๐ฟ Jungle", | |
"medieval": "๐ฐ Medieval", | |
"underwater": "๐ Underwater" | |
} | |
# 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("### ๐ Step 1: Write Your Story") | |
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=st.session_state.story_text | |
) | |
if st.button("โจ Analyze My Story!", use_container_width=True, key="analyze", type="primary"): | |
if story_text.strip(): | |
st.session_state.story_text = story_text | |
with st.spinner("๐ AI is reading your story to find the perfect hero and world..."): | |
# Extract story elements using AI | |
hero, world = extract_story_elements(story_text, ner_model, classifier) | |
st.session_state.selected_character = hero | |
st.session_state.selected_theme = world | |
st.session_state.analyzed = True | |
# Show suggestions | |
st.success(f"๐ฏ AI found a {characters[hero]} hero in a {themes[world]} world!") | |
else: | |
st.warning("Please enter a story first!") | |
if st.session_state.analyzed: | |
st.markdown("### ๐ง Step 2: Your Hero & World") | |
st.markdown(f'<div class="hero-suggestion">Your Hero: {characters[st.session_state.selected_character]}</div>', | |
unsafe_allow_html=True) | |
st.markdown(f'<div class="world-suggestion">Your World: {themes[st.session_state.selected_theme]}</div>', | |
unsafe_allow_html=True) | |
st.info("๐ก The AI chose these based on your story! Click Generate Animation when ready.") | |
# Generate animation button | |
if st.button("๐ฌ Generate Animation!", use_container_width=True, key="generate", type="primary"): | |
st.session_state.animation_generated = True | |
with st.spinner("๐งโโ๏ธ Creating your animation..."): | |
# 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 += 20 | |
# 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 | |
st.markdown('</div>', unsafe_allow_html=True) | |
with col2: | |
st.markdown('<div class="story-box">', unsafe_allow_html=True) | |
if st.session_state.animation_generated and st.session_state.animation_frames: | |
st.markdown("### ๐ฎ Step 3: Your Animation") | |
# 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 character and theme info | |
st.success(f"โจ Your {characters[st.session_state.selected_character]} in the {themes[st.session_state.selected_theme]} world!") | |
elif st.session_state.analyzed: | |
st.markdown("### ๐ฎ Step 3: Your Animation") | |
st.image("https://img.freepik.com/free-vector/hand-drawn-colorful-space-background_23-2148821798.jpg", | |
use_container_width=True) | |
st.info("๐ Click 'Generate Animation' to bring your story to life!") | |
elif story_text: | |
st.markdown("### ๐ฎ Your Animation Will Appear Here") | |
preview_img = create_storyboard_image(story_text) | |
st.image(preview_img, caption="Your Story Preview", use_container_width=True) | |
st.info("๐ Click 'Analyze My Story' to begin the magic!") | |
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 'Analyze My Story' to begin!") | |
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("### ๐ค Step 4: Tavus the Robot Teacher Explains Coding") | |
# Extract action words from story | |
action_words = ["zoom", "shoot", "fly", "move", "jump", "run", "attack", | |
"laser", "alien", "spaceship", "dragon", "fire", "hero", | |
"sword", "castle", "escape", "fight", "win", "swim", "dive"] | |
found_actions = [word for word in action_words if re.search(r'\b' + word + r'\b', st.session_state.story_text.lower())] | |
if found_actions: | |
# Create explanation based on found words | |
st.markdown("#### ๐งฉ Story Actions to Code Concepts") | |
cols = st.columns(3) | |
action_colors = ["#ff6b6b", "#4ecdc4", "#ffd166", "#06d6a0", "#118ab2"] | |
for i, action in enumerate(found_actions[:6]): | |
with cols[i % 3]: | |
color = action_colors[i % len(action_colors)] | |
st.markdown(f'<div style="background:{color}; color:white; border-radius:10px; padding:10px; text-align:center;">', unsafe_allow_html=True) | |
st.markdown(f"**{action.capitalize()}**") | |
# Add code snippets based on action | |
if action == "zoom": | |
st.code("hero.move_fast(10)", language="python") | |
elif action == "shoot": | |
st.code("laser = hero.create_weapon()", language="python") | |
elif action == "fly": | |
st.code("hero.fly(height=100)", language="python") | |
elif action == "move": | |
st.code("hero.move(direction='right')", language="python") | |
elif action == "jump": | |
st.code("hero.jump(power=20)", language="python") | |
elif action == "run": | |
st.code("hero.speed = hero.speed * 2", language="python") | |
elif action == "attack": | |
st.code("hero.attack(enemy)", language="python") | |
elif action == "swim": | |
st.code("hero.swim(speed=5)", language="python") | |
elif action == "dive": | |
st.code("hero.dive(depth=30)", language="python") | |
else: | |
st.code(f"hero.{action}()", language="python") | |
st.markdown("</div>", unsafe_allow_html=True) | |
st.markdown("---") | |
# Show AI-generated explanation | |
st.markdown("### ๐ง AI-Powered Explanation:") | |
st.write(st.session_state.code_explanation) | |
st.markdown("#### ๐ก Remember:") | |
st.markdown(""" | |
- Every action in your story becomes code | |
- Code is just instructions for computers | |
- You're already thinking like a coder! | |
""") | |
st.markdown("</div>", unsafe_allow_html=True) | |
# 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 | |
# Benefits section | |
st.markdown(""" | |
## โค Why Kids & Parents Love CodeTales | |
| For Kids ๐ง๐ฆ | For Parents & Teachers ๐ช๐ฉโ๐ซ | | |
|--------------|----------------------------| | |
| โจ Create your own animated stories | ๐ง Teaches computational thinking | | |
| ๐ See imagination come to life | ๐ Develops problem-solving skills | | |
| ๐ฎ Interactive game creation | โ Reinforces STEM fundamentals | | |
| ๐ Fun characters and worlds | ๐งฉ Encourages logical reasoning | | |
| ๐ Unlock new levels | ๐ Tracks learning progress | | |
""") | |
# Footer | |
st.markdown("---") | |
st.markdown(""" | |
<center> | |
<p style="color:white; font-size:1.1rem;"> | |
โจ Made with magic by CodeTales Team โจ<br> | |
Transforming stories into games since 2023 | |
</p> | |
</center> | |
""", unsafe_allow_html=True) |