Spaces:
Running
Running
# 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 | |
import pygame | |
import sys | |
import os | |
import tempfile | |
# 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; | |
} | |
.coding-tutorial { | |
background: rgba(41, 128, 185, 0.9); | |
border-radius: 20px; | |
padding: 1.5rem; | |
margin-top: 2rem; | |
color: white; | |
border: 3px solid #2980b9; | |
} | |
.code-playground { | |
background: rgba(44, 62, 80, 0.9); | |
border-radius: 15px; | |
padding: 1.5rem; | |
margin-top: 1.5rem; | |
} | |
/* Progress bar styling */ | |
.stProgress > div > div > div { | |
background-color: #ff5722 !important; | |
} | |
.level-indicator { | |
background: rgba(52, 152, 219, 0.9); | |
color: white; | |
border-radius: 50px; | |
padding: 0.5rem 1rem; | |
display: inline-block; | |
margin-bottom: 1rem; | |
font-weight: bold; | |
border: 2px solid #2c3e50; | |
} | |
</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=8): | |
"""Generate a sprite-based animation from story""" | |
frames = [] | |
width, height = 400, 300 | |
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 = (34, 139, 34) | |
star_color = None | |
elif theme == "medieval": | |
bg_color = (139, 69, 19) | |
star_color = None | |
elif theme == "underwater": | |
bg_color = (0, 105, 148) | |
star_color = None | |
elif theme == "desert": | |
bg_color = (210, 180, 140) | |
star_color = None | |
elif theme == "arctic": | |
bg_color = (240, 248, 255) | |
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 background elements | |
if theme == "space": | |
# Stars | |
for _ in range(50): | |
x = random.randint(0, width) | |
y = random.randint(0, height) | |
draw.ellipse([x, y, x+2, y+2], fill=star_color) | |
# Planets | |
draw.ellipse([300, 30, 380, 110], fill=(255, 165, 0), outline=(255, 215, 0), width=2) | |
elif theme == "jungle": | |
# Trees | |
for x in [50, 150, 250, 350]: | |
draw.rectangle([x, 150, x+20, 250], fill=(101, 67, 33)) | |
draw.ellipse([x-20, 120, x+40, 180], fill=(34, 139, 34)) | |
# Sun | |
draw.ellipse([320, 30, 380, 90], fill=(255, 215, 0)) | |
elif theme == "medieval": | |
# Castle | |
draw.rectangle([250, 100, 350, 250], fill=(169, 169, 169)) | |
draw.rectangle([280, 150, 320, 250], fill=(105, 105, 105)) | |
for x in [260, 300, 340]: | |
draw.polygon([(x, 80), (x-20, 100), (x+20, 100)], fill=(220, 20, 60)) | |
# Mountains | |
draw.polygon([(0, 250), (100, 100), (200, 250)], fill=(120, 120, 120)) | |
elif theme == "underwater": | |
# Seaweed | |
for x in [50, 150, 250, 350]: | |
draw.line([(x, 250), (x, 180)], fill=(0, 128, 0), width=5) | |
draw.ellipse([x-20, 170, x+20, 190], fill=(0, 128, 0)) | |
# Bubbles | |
for _ in range(20): | |
x = random.randint(0, width) | |
y = random.randint(0, 200) | |
size = random.randint(5, 15) | |
draw.ellipse([x, y, x+size, y+size], fill=(173, 216, 230), outline=(70, 130, 180)) | |
elif theme == "desert": | |
# Sand dunes | |
for x in [0, 100, 200, 300]: | |
draw.arc([x, 150, x+200, 300], 180, 360, fill=(210, 180, 140), width=50) | |
# Sun | |
draw.ellipse([300, 30, 370, 100], fill=(255, 140, 0)) | |
elif theme == "arctic": | |
# Snowy ground | |
draw.rectangle([0, 200, width, height], fill=(255, 255, 255)) | |
# Snowflakes | |
for _ in range(50): | |
x = random.randint(0, width) | |
y = random.randint(0, 180) | |
draw.ellipse([x, y, x+3, y+3], fill=(255, 255, 255)) | |
# Mountains | |
draw.polygon([(0, 250), (100, 100), (200, 250)], fill=(200, 200, 200)) | |
# Draw moving elements based on frame | |
if character == "spaceship": | |
ship_x = 50 + i * 40 | |
ship_y = 120 | |
# Spaceship body | |
draw.polygon([(ship_x, ship_y), (ship_x+50, ship_y), | |
(ship_x+25, ship_y-30)], fill=(169, 169, 169)) | |
# Spaceship details | |
draw.rectangle([ship_x+20, ship_y-10, ship_x+30, ship_y], fill=(0, 191, 255)) | |
if "shoot" in story.lower() and i > 1: | |
for j in range(3): | |
laser_x = ship_x + 25 | |
laser_y = ship_y - 30 + j*5 | |
draw.line([(laser_x, laser_y), (width, laser_y)], fill=(255, 0, 0), width=3) | |
elif character == "dragon": | |
dragon_x = 50 + i * 30 | |
dragon_y = 140 | |
# Dragon body | |
draw.ellipse([dragon_x, dragon_y, dragon_x+60, dragon_y+30], fill=(178, 34, 34)) | |
# Dragon head | |
draw.ellipse([dragon_x+50, dragon_y+5, dragon_x+70, dragon_y+25], fill=(178, 34, 34)) | |
# Dragon wings | |
draw.ellipse([dragon_x+10, dragon_y-20, dragon_x+40, dragon_y+10], fill=(138, 43, 226)) | |
if "fire" in story.lower() and i > 0: | |
for j in range(5): | |
flame_x = dragon_x + 70 | |
flame_y = dragon_y + 15 - j*5 | |
flame_size = random.randint(8, 18) | |
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 * 30 | |
knight_y = 150 | |
# Knight body | |
draw.rectangle([knight_x, knight_y, knight_x+25, knight_y+50], fill=(70, 70, 70)) | |
# Knight head | |
draw.ellipse([knight_x+5, knight_y-15, knight_x+20, knight_y], fill=(210, 180, 140)) | |
# Knight helmet | |
draw.arc([knight_x, knight_y-20, knight_x+25, knight_y], 0, 180, fill=(50, 50, 50), width=3) | |
# Sword | |
draw.rectangle([knight_x+20, knight_y+10, knight_x+30, knight_y+15], fill=(192, 192, 192)) | |
draw.polygon([(knight_x+30, knight_y+12), (knight_x+40, knight_y+10), (knight_x+40, knight_y+15)], fill=(192, 192, 192)) | |
if "attack" in story.lower() and i % 2 == 1: | |
# Draw sword swing | |
draw.line([(knight_x+30, knight_y+12), (knight_x+60, knight_y-20)], fill=(255, 255, 0), width=3) | |
elif character == "mermaid": | |
mermaid_x = 50 + i * 30 | |
mermaid_y = 150 | |
# Mermaid tail | |
draw.ellipse([mermaid_x, mermaid_y, mermaid_x+40, mermaid_y+30], fill=(255, 105, 180)) | |
# Mermaid body | |
draw.ellipse([mermaid_x+10, mermaid_y-30, mermaid_x+30, mermaid_y], fill=(255, 218, 185)) | |
# Mermaid hair | |
draw.ellipse([mermaid_x-10, mermaid_y-35, mermaid_x+40, mermaid_y-20], 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, 40) | |
bubble_y = mermaid_y - random.randint(20, 40) | |
draw.ellipse([bubble_x, bubble_y, bubble_x+8, bubble_y+8], fill=(173, 216, 230)) | |
elif character == "robot": | |
robot_x = 50 + i * 35 | |
robot_y = 150 | |
# Robot body | |
draw.rectangle([robot_x, robot_y, robot_x+30, robot_y+50], fill=(100, 100, 100)) | |
# Robot head | |
draw.rectangle([robot_x-5, robot_y-20, robot_x+35, robot_y], fill=(150, 150, 150)) | |
# Robot eyes | |
draw.ellipse([robot_x+5, robot_y-15, robot_x+10, robot_y-10], fill=(0, 255, 0)) | |
draw.ellipse([robot_x+20, robot_y-15, robot_x+25, robot_y-10], fill=(0, 255, 0)) | |
# Robot arms | |
draw.rectangle([robot_x-10, robot_y+10, robot_x, robot_y+20], fill=(100, 100, 100)) | |
draw.rectangle([robot_x+30, robot_y+10, robot_x+40, robot_y+20], fill=(100, 100, 100)) | |
if "laser" in story.lower() and i > 1: | |
draw.line([(robot_x+40, robot_y+15), (width, robot_y+15)], fill=(0, 255, 0), width=3) | |
elif character == "unicorn": | |
unicorn_x = 50 + i * 30 | |
unicorn_y = 150 | |
# Unicorn body | |
draw.ellipse([unicorn_x, unicorn_y, unicorn_x+40, unicorn_y+20], fill=(255, 240, 245)) | |
# Unicorn head | |
draw.ellipse([unicorn_x+30, unicorn_y-5, unicorn_x+50, unicorn_y+15], fill=(255, 240, 245)) | |
# Unicorn horn | |
draw.polygon([(unicorn_x+40, unicorn_y-10), (unicorn_x+45, unicorn_y-30), (unicorn_x+50, unicorn_y-10)], fill=(255, 215, 0)) | |
# Unicorn mane | |
for j in range(5): | |
color = random.choice(['#FF69B4', '#9370DB', '#87CEFA']) | |
draw.ellipse([unicorn_x+25+j*3, unicorn_y-10-j*2, unicorn_x+35+j*3, unicorn_y+j*2], fill=color) | |
# Draw enemies based on theme | |
if theme == "space" and "alien" in story.lower(): | |
alien_x = 300 | |
alien_y = 120 - i*5 | |
draw.ellipse([alien_x, alien_y, alien_x+30, alien_y+30], fill=(50, 205, 50)) | |
draw.ellipse([alien_x+7, alien_y+8, alien_x+12, alien_y+13], fill=(0, 0, 0)) | |
draw.ellipse([alien_x+18, alien_y+8, alien_x+23, alien_y+13], fill=(0, 0, 0)) | |
draw.arc([alien_x+7, alien_y+15, alien_x+23, alien_y+25], 0, 180, fill=(0, 0, 0), width=2) | |
elif theme == "jungle" and "snake" in story.lower(): | |
snake_x = 300 | |
snake_y = 180 - i*5 | |
for segment in range(8): | |
offset = segment * 8 | |
draw.ellipse([snake_x+offset, snake_y+offset, snake_x+offset+20, snake_y+offset+20], fill=(0, 128, 0)) | |
elif theme == "medieval" and "dragon" in story.lower() and character != "dragon": | |
dragon_x = 250 | |
dragon_y = 100 | |
draw.ellipse([dragon_x, dragon_y, dragon_x+50, dragon_y+25], fill=(178, 34, 34)) | |
draw.line([(dragon_x+50, dragon_y+12), (dragon_x+80, dragon_y)], fill=(178, 34, 34), width=4) | |
elif theme == "underwater" and "shark" in story.lower(): | |
shark_x = 250 | |
shark_y = 100 + i*8 | |
# Shark body | |
draw.ellipse([shark_x, shark_y, shark_x+80, shark_y+40], fill=(169, 169, 169)) | |
# Shark fin | |
draw.polygon([(shark_x+60, shark_y), (shark_x+70, shark_y-30), (shark_x+80, shark_y)], fill=(169, 169, 169)) | |
elif theme == "desert" and "scorpion" in story.lower(): | |
scorpion_x = 300 | |
scorpion_y = 200 | |
# Scorpion body | |
draw.ellipse([scorpion_x, scorpion_y, scorpion_x+40, scorpion_y+20], fill=(165, 42, 42)) | |
# Scorpion tail | |
draw.line([(scorpion_x+40, scorpion_y+10), (scorpion_x+60, scorpion_y-10)], fill=(165, 42, 42), width=3) | |
# Scorpion claws | |
draw.line([(scorpion_x, scorpion_y+5), (scorpion_x-15, scorpion_y)], fill=(165, 42, 42), width=3) | |
draw.line([(scorpion_x, scorpion_y+15), (scorpion_x-15, scorpion_y+20)], fill=(165, 42, 42), width=3) | |
elif theme == "arctic" and "yeti" in story.lower(): | |
yeti_x = 280 | |
yeti_y = 180 | |
# Yeti body | |
draw.ellipse([yeti_x, yeti_y, yeti_x+50, yeti_y+50], fill=(240, 240, 240)) | |
# Yeti eyes | |
draw.ellipse([yeti_x+15, yeti_y+15, yeti_x+20, yeti_y+20], fill=(0, 0, 0)) | |
draw.ellipse([yeti_x+30, yeti_y+15, yeti_x+35, yeti_y+20], fill=(0, 0, 0)) | |
# Yeti mouth | |
draw.arc([yeti_x+15, yeti_y+25, yeti_x+35, yeti_y+35], 0, 180, fill=(0, 0, 0), width=2) | |
# Add frame number | |
draw.text((10, 10), f"Frame {i+1}", fill=(255, 255, 255)) | |
frames.append(img) | |
return frames | |
def generate_pygame_code(story, character, theme): | |
"""Generate PyGame code based on the story""" | |
# Basic PyGame template | |
code = f"""import pygame | |
import sys | |
# Initialize Pygame | |
pygame.init() | |
# Screen dimensions | |
WIDTH, HEIGHT = 800, 600 | |
screen = pygame.display.set_mode((WIDTH, HEIGHT)) | |
pygame.display.set_caption("Your Story: {character.capitalize()} in {theme.capitalize()}") | |
# Colors | |
BACKGROUND = {get_theme_color(theme)} | |
HERO_COLOR = {get_character_color(character)} | |
# Hero position | |
hero_x = 100 | |
hero_y = HEIGHT // 2 | |
hero_speed = 5 | |
# Main game loop | |
clock = pygame.time.Clock() | |
running = True | |
while running: | |
for event in pygame.event.get(): | |
if event.type == pygame.QUIT: | |
running = False | |
# Handle keyboard input | |
keys = pygame.key.get_pressed() | |
if keys[pygame.K_LEFT]: | |
hero_x -= hero_speed | |
if keys[pygame.K_RIGHT]: | |
hero_x += hero_speed | |
if keys[pygame.K_UP]: | |
hero_y -= hero_speed | |
if keys[pygame.K_DOWN]: | |
hero_y += hero_speed | |
# Fill the background | |
screen.fill(BACKGROUND) | |
# Draw the hero | |
{get_character_drawing_code(character)} | |
# Draw story elements | |
{get_story_elements_code(story, theme)} | |
pygame.display.flip() | |
clock.tick(60) | |
pygame.quit() | |
sys.exit() | |
""" | |
return code | |
def get_theme_color(theme): | |
"""Get theme color for PyGame code""" | |
colors = { | |
"space": "(0, 0, 30)", | |
"jungle": "(34, 139, 34)", | |
"medieval": "(139, 69, 19)", | |
"underwater": "(0, 105, 148)", | |
"desert": "(210, 180, 140)", | |
"arctic": "(240, 248, 255)" | |
} | |
return colors.get(theme, "(0, 0, 30)") | |
def get_character_color(character): | |
"""Get character color for PyGame code""" | |
colors = { | |
"spaceship": "(169, 169, 169)", | |
"dragon": "(178, 34, 34)", | |
"knight": "(70, 70, 70)", | |
"mermaid": "(255, 105, 180)", | |
"robot": "(100, 100, 100)", | |
"unicorn": "(255, 240, 245)" | |
} | |
return colors.get(character, "(169, 169, 169)") | |
def get_character_drawing_code(character): | |
"""Get PyGame drawing code for character""" | |
if character == "spaceship": | |
return """ # Draw spaceship | |
pygame.draw.polygon(screen, HERO_COLOR, [(hero_x, hero_y), | |
(hero_x+50, hero_y), | |
(hero_x+25, hero_y-30)]) | |
pygame.draw.rect(screen, (0, 191, 255), (hero_x+20, hero_y-10, 10, 10))""" | |
elif character == "dragon": | |
return """ # Draw dragon | |
pygame.draw.ellipse(screen, HERO_COLOR, (hero_x, hero_y, 60, 30)) | |
pygame.draw.ellipse(screen, HERO_COLOR, (hero_x+50, hero_y+5, 20, 20)) | |
pygame.draw.ellipse(screen, (138, 43, 226), (hero_x+10, hero_y-20, 30, 30))""" | |
elif character == "knight": | |
return """ # Draw knight | |
pygame.draw.rect(screen, HERO_COLOR, (hero_x, hero_y, 25, 50)) | |
pygame.draw.ellipse(screen, (210, 180, 140), (hero_x+5, hero_y-15, 15, 15)) | |
pygame.draw.arc(screen, (50, 50, 50), (hero_x, hero_y-20, 25, 20), 0, 3.14, 3) | |
pygame.draw.rect(screen, (192, 192, 192), (hero_x+20, hero_y+10, 10, 5)) | |
pygame.draw.polygon(screen, (192, 192, 192), [(hero_x+30, hero_y+12), | |
(hero_x+40, hero_y+10), | |
(hero_x+40, hero_y+15)])""" | |
elif character == "mermaid": | |
return """ # Draw mermaid | |
pygame.draw.ellipse(screen, HERO_COLOR, (hero_x, hero_y, 40, 30)) | |
pygame.draw.ellipse(screen, (255, 218, 185), (hero_x+10, hero_y-30, 20, 30)) | |
pygame.draw.ellipse(screen, (255, 215, 0), (hero_x-10, hero_y-35, 50, 15))""" | |
elif character == "robot": | |
return """ # Draw robot | |
pygame.draw.rect(screen, HERO_COLOR, (hero_x, hero_y, 30, 50)) | |
pygame.draw.rect(screen, (150, 150, 150), (hero_x-5, hero_y-20, 40, 20)) | |
pygame.draw.ellipse(screen, (0, 255, 0), (hero_x+5, hero_y-15, 5, 5)) | |
pygame.draw.ellipse(screen, (0, 255, 0), (hero_x+20, hero_y-15, 5, 5)) | |
pygame.draw.rect(screen, HERO_COLOR, (hero_x-10, hero_y+10, 10, 10)) | |
pygame.draw.rect(screen, HERO_COLOR, (hero_x+30, hero_y+10, 10, 10))""" | |
elif character == "unicorn": | |
return """ # Draw unicorn | |
pygame.draw.ellipse(screen, HERO_COLOR, (hero_x, hero_y, 40, 20)) | |
pygame.draw.ellipse(screen, HERO_COLOR, (hero_x+30, hero_y-5, 20, 20)) | |
pygame.draw.polygon(screen, (255, 215, 0), [(hero_x+40, hero_y-10), | |
(hero_x+45, hero_y-30), | |
(hero_x+50, hero_y-10)])""" | |
else: | |
return """ # Draw hero | |
pygame.draw.rect(screen, HERO_COLOR, (hero_x, hero_y, 40, 60))""" | |
def get_story_elements_code(story, theme): | |
"""Get PyGame code for story elements""" | |
code = "" | |
if "alien" in story.lower() and theme == "space": | |
code += """ # Draw alien | |
pygame.draw.ellipse(screen, (50, 205, 50), (600, 200, 30, 30)) | |
pygame.draw.ellipse(screen, (0, 0, 0), (610, 210, 5, 5)) | |
pygame.draw.ellipse(screen, (0, 0, 0), (620, 210, 5, 5)) | |
pygame.draw.arc(screen, (0, 0, 0), (610, 220, 15, 10), 0, 3.14, 2) | |
""" | |
if "dragon" in story.lower() and theme == "medieval": | |
code += """ # Draw dragon | |
pygame.draw.ellipse(screen, (178, 34, 34), (650, 150, 50, 25)) | |
pygame.draw.line(screen, (178, 34, 34), (700, 162), (730, 150), 4) | |
""" | |
if "treasure" in story.lower(): | |
code += """ # Draw treasure | |
pygame.draw.rect(screen, (255, 215, 0), (700, 400, 30, 20)) | |
pygame.draw.rect(screen, (255, 215, 0), (705, 395, 20, 10)) | |
""" | |
if "castle" in story.lower() and theme == "medieval": | |
code += """ # Draw castle | |
pygame.draw.rect(screen, (169, 169, 169), (600, 300, 150, 150)) | |
pygame.draw.rect(screen, (105, 105, 105), (650, 350, 50, 100)) | |
for x in range(610, 740, 40): | |
pygame.draw.polygon(screen, (220, 20, 60), [(x, 300), (x-20, 320), (x+20, 320)]) | |
""" | |
return code | |
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", "space"], | |
"dragon": ["dragon", "monster", "creature", "beast", "wyvern"], | |
"knight": ["knight", "warrior", "prince", "princess", "king", "queen", "sword", "castle"], | |
"mermaid": ["mermaid", "merman", "sea", "ocean", "underwater", "fish"], | |
"robot": ["robot", "cyborg", "machine", "android"], | |
"unicorn": ["unicorn", "horse", "pony", "magic"] | |
} | |
# Find world based on keywords and classification | |
world_labels = ["space", "jungle", "medieval", "underwater", "desert", "arctic"] | |
# 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" | |
elif "robot" in story_lower: | |
hero = "robot" | |
elif "unicorn" in story_lower: | |
hero = "unicorn" | |
# 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 or "knight" in story_lower: | |
world = "medieval" | |
if "space" in story_lower or "alien" in story_lower or "planet" in story_lower: | |
world = "space" | |
if "desert" in story_lower or "sand" in story_lower: | |
world = "desert" | |
if "arctic" in story_lower or "snow" in story_lower or "ice" in story_lower: | |
world = "arctic" | |
return hero, world | |
except Exception as e: | |
st.error(f"Error analyzing story: {str(e)}") | |
return "spaceship", "space" | |
def run_pygame_code(code): | |
"""Run PyGame code and capture the output""" | |
with tempfile.NamedTemporaryFile(suffix=".py", delete=False, mode="w") as f: | |
f.write(code) | |
temp_file_name = f.name | |
# Create a video capture of the PyGame window | |
os.system(f"python {temp_file_name} &") | |
return temp_file_name | |
# 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 | |
if 'pygame_code' not in st.session_state: | |
st.session_state.pygame_code = "" | |
if 'show_code' not in st.session_state: | |
st.session_state.show_code = False | |
# Character and theme mapping | |
characters = { | |
"spaceship": "๐ Spaceship", | |
"dragon": "๐ Dragon", | |
"knight": "๐ก๏ธ Knight", | |
"mermaid": "๐งโโ๏ธ Mermaid", | |
"robot": "๐ค Robot", | |
"unicorn": "๐ฆ Unicorn" | |
} | |
themes = { | |
"space": "๐ Space", | |
"jungle": "๐ฟ Jungle", | |
"medieval": "๐ฐ Medieval", | |
"underwater": "๐ Underwater", | |
"desert": "๐๏ธ Desert", | |
"arctic": "โ๏ธ Arctic" | |
} | |
# 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) | |
# Customization options | |
st.markdown("### ๐จ Customize Your Hero") | |
custom_char = st.selectbox( | |
"Choose a different hero:", | |
options=list(characters.keys()), | |
format_func=lambda x: characters[x], | |
index=list(characters.keys()).index(st.session_state.selected_character) | |
) | |
st.session_state.selected_character = custom_char | |
# Generate animation button | |
if st.button("๐ฌ Generate Animation & Code!", use_container_width=True, key="generate", type="primary"): | |
st.session_state.animation_generated = True | |
with st.spinner("๐งโโ๏ธ Creating your animation and code..."): | |
# 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, | |
num_frames=8 | |
) | |
# Generate code explanation | |
st.session_state.code_explanation = generate_code_explanation(story_text, explanation_generator) | |
# Generate PyGame code | |
st.session_state.pygame_code = generate_pygame_code( | |
story_text, | |
st.session_state.selected_character, | |
st.session_state.selected_theme | |
) | |
# Update user progress | |
st.session_state.story_count += 1 | |
st.session_state.xp += 25 | |
# 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!**") | |
# Show animation as 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 | |
) | |
st.image(gif_buffer, caption="Your Animated Story", use_container_width=True) | |
# Download button | |
st.download_button( | |
label="โฌ๏ธ Download Animation", | |
data=gif_buffer.getvalue(), | |
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 & Code!' 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) | |
# Coding tutorial 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") | |
# Show code explanation | |
st.markdown("### ๐ง AI-Powered Explanation:") | |
st.write(st.session_state.code_explanation) | |
# Toggle for showing full code | |
if st.button("๐จโ๐ป Show Me the Full Code!", key="show_code"): | |
st.session_state.show_code = not st.session_state.show_code | |
if st.session_state.show_code: | |
st.markdown("### ๐ Your Story as Python Code") | |
st.code(st.session_state.pygame_code, language='python') | |
if st.button("โถ๏ธ Run This Code in PyGame", key="run_pygame"): | |
with st.spinner("๐ Launching your game..."): | |
temp_file = run_pygame_code(st.session_state.pygame_code) | |
st.success(f"Game launched! Check your desktop for the PyGame window") | |
st.info("Use arrow keys to move your character!") | |
# Step-by-step coding tutorial | |
st.markdown("### ๐ฃ Step-by-Step Coding Tutorial") | |
with st.expander("1. Setting Up Your Game"): | |
st.markdown(""" | |
Every PyGame program starts with these basic steps: | |
```python | |
import pygame # Import the game library | |
import sys # Import system library for exit | |
# Initialize Pygame | |
pygame.init() | |
# Create a game window | |
WIDTH, HEIGHT = 800, 600 | |
screen = pygame.display.set_mode((WIDTH, HEIGHT)) | |
pygame.display.set_caption("Your Awesome Game") | |
``` | |
""") | |
with st.expander("2. Creating Your Hero"): | |
st.markdown(f""" | |
Let's create your {st.session_state.selected_character} hero: | |
```python | |
# Hero position | |
hero_x = 100 | |
hero_y = 300 | |
# How to draw your hero | |
def draw_hero(): | |
{get_character_drawing_code(st.session_state.selected_character).replace('\\n', '\\n ')} | |
``` | |
""") | |
with st.expander("3. Making Your Hero Move"): | |
st.markdown(""" | |
We'll use keyboard input to move the hero: | |
```python | |
hero_speed = 5 | |
# Inside the game loop | |
keys = pygame.key.get_pressed() | |
if keys[pygame.K_LEFT]: | |
hero_x -= hero_speed | |
if keys[pygame.K_RIGHT]: | |
hero_x += hero_speed | |
if keys[pygame.K_UP]: | |
hero_y -= hero_speed | |
if keys[pygame.K_DOWN]: | |
hero_y += hero_speed | |
``` | |
""") | |
with st.expander("4. Adding Your Story Elements"): | |
story_elements = [] | |
if "alien" in st.session_state.story_text.lower(): | |
story_elements.append("Aliens") | |
if "dragon" in st.session_state.story_text.lower(): | |
story_elements.append("Dragons") | |
if "treasure" in st.session_state.story_text.lower(): | |
story_elements.append("Treasure") | |
if story_elements: | |
elements = ", ".join(story_elements) | |
st.markdown(f""" | |
Let's add {elements} from your story: | |
```python | |
def draw_story_elements(): | |
{get_story_elements_code(st.session_state.story_text, st.session_state.selected_theme).replace('\n', '\n ')} | |
``` | |
""") | |
else: | |
st.markdown(""" | |
Add your own story elements with drawing commands: | |
```python | |
# Example: Draw a treasure chest | |
pygame.draw.rect(screen, (255, 215, 0), (700, 400, 30, 20)) | |
pygame.draw.rect(screen, (255, 215, 0), (705, 395, 20, 10)) | |
``` | |
""") | |
with st.expander("5. Running Your Game"): | |
st.markdown(""" | |
Finally, we put everything together in a game loop: | |
```python | |
# Main game loop | |
clock = pygame.time.Clock() | |
running = True | |
while running: | |
# Handle events | |
for event in pygame.event.get(): | |
if event.type == pygame.QUIT: | |
running = False | |
# Handle movement | |
# ... (movement code from step 3) | |
# Draw everything | |
screen.fill(BACKGROUND) | |
draw_hero() | |
draw_story_elements() | |
# Update the display | |
pygame.display.flip() | |
clock.tick(60) | |
pygame.quit() | |
sys.exit() | |
``` | |
""") | |
st.markdown("</div>", unsafe_allow_html=True) | |
# Code Playground | |
if st.session_state.animation_generated: | |
st.markdown('<div class="code-playground">', unsafe_allow_html=True) | |
st.markdown("### ๐งช Step 5: Code Playground") | |
st.markdown("Try modifying your code and see the changes!") | |
# Editable code area | |
modified_code = st.text_area( | |
"Edit your Python code:", | |
value=st.session_state.pygame_code, | |
height=300, | |
key="code_editor" | |
) | |
# Run the modified code | |
if st.button("๐ Run My Modified Code", key="run_modified_code"): | |
with st.spinner("Running your code..."): | |
temp_file = run_pygame_code(modified_code) | |
st.success("Game launched with your modifications!") | |
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 | |
# Achievements | |
st.markdown("### ๐ Your Achievements") | |
achievement_cols = st.columns(4) | |
with achievement_cols[0]: | |
st.markdown('<div class="achievement-badge">๐</div>', unsafe_allow_html=True) | |
st.markdown("**Storyteller**", help="Created your first story") | |
st.markdown(f"{st.session_state.story_count} story created") | |
with achievement_cols[1]: | |
st.markdown('<div class="achievement-badge">๐พ</div>', unsafe_allow_html=True) | |
st.markdown("**Animator**", help="Generated 5 animations") | |
st.markdown(f"{min(st.session_state.story_count, 5)}/5 animations") | |
with achievement_cols[2]: | |
st.markdown('<div class="achievement-badge">๐ป</div>', unsafe_allow_html=True) | |
st.markdown("**Coder**", help="Modified and ran code") | |
st.markdown("Unlocked!" if st.session_state.animation_generated else "Locked") | |
with achievement_cols[3]: | |
st.markdown('<div class="achievement-badge">๐</div>', unsafe_allow_html=True) | |
st.markdown("**Explorer**", help="Used 3 different heroes") | |
heroes_used = len(set([st.session_state.selected_character])) | |
st.markdown(f"{heroes_used}/3 heroes used") | |
# 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 | | |
| ๐ป Real Python coding experience | ๐ฏ Aligned with coding curriculum | | |
""") | |
# 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) |