# app.py - Final Deployment-Ready Version import streamlit as st import os import time import base64 import random import tempfile import subprocess import numpy as np import pygame import sys import imageio from PIL import Image import io import matplotlib.pyplot as plt # Configure Streamlit page st.set_page_config( page_title="StoryCoder - Learn Python Through Stories", page_icon="🧙♂️", layout="wide", initial_sidebar_state="expanded" ) # Custom CSS for colorful UI st.markdown(""" """, unsafe_allow_html=True) # Concept database CONCEPTS = { "loop": { "name": "Loop", "emoji": "🔄", "description": "Loops repeat actions multiple times", "example": "for i in range(5):\n print('Hello!')", "color": "#FF9E6D" }, "conditional": { "name": "Conditional", "emoji": "❓", "description": "Conditionals make decisions in code", "example": "if sunny:\n go_outside()\nelse:\n stay_inside()", "color": "#4ECDC4" }, "function": { "name": "Function", "emoji": "✨", "description": "Functions are reusable blocks of code", "example": "def greet(name):\n print(f'Hello {name}!')", "color": "#FFD166" }, "variable": { "name": "Variable", "emoji": "📦", "description": "Variables store information", "example": "score = 10\nplayer = 'Alex'", "color": "#FF6B6B" }, "list": { "name": "List", "emoji": "📝", "description": "Lists store collections of items", "example": "fruits = ['apple', 'banana', 'orange']", "color": "#1A535C" } } # Character database CHARACTERS = { "rabbit": {"color": (255, 150, 150), "speed": 5, "size": 30}, "dragon": {"color": (255, 100, 100), "speed": 3, "size": 60}, "cat": {"color": (200, 150, 255), "speed": 6, "size": 25}, "dog": {"color": (255, 200, 150), "speed": 7, "size": 35}, "knight": {"color": (150, 200, 255), "speed": 4, "size": 40}, "wizard": {"color": (200, 255, 150), "speed": 4, "size": 35}, "scientist": {"color": (150, 255, 200), "speed": 5, "size": 30}, "pirate": {"color": (255, 255, 150), "speed": 5, "size": 40} } def analyze_story(story): """Analyze story and identify programming concepts""" story_lower = story.lower() detected_concepts = [] # Check for loops if any(word in story_lower for word in ["times", "repeat", "again", "multiple"]): detected_concepts.append("loop") # Check for conditionals if any(word in story_lower for word in ["if", "when", "unless", "whether"]): detected_concepts.append("conditional") # Check for functions if any(word in story_lower for word in ["make", "create", "do", "perform", "cast"]): detected_concepts.append("function") # Check for variables if any(word in story_lower for word in ["is", "has", "set to", "value"]): detected_concepts.append("variable") # Check for lists if any(word in story_lower for word in ["and", "many", "several", "collection", "items"]): detected_concepts.append("list") return list(set(detected_concepts)) def generate_pygame_animation(story, concepts): """Generate PyGame animation based on story and concepts""" try: # Choose a random character character = random.choice(list(CHARACTERS.keys())) char_details = CHARACTERS[character] # Extract count from story count = 3 for word in story.split(): if word.isdigit(): count = min(int(word), 10) break # Create a temporary file for the animation with tempfile.NamedTemporaryFile(suffix=".gif", delete=False) as tmpfile: gif_path = tmpfile.name # Set up PyGame in headless mode os.environ['SDL_VIDEODRIVER'] = 'dummy' pygame.init() pygame.display.set_mode((1, 1)) # Tiny invisible window # Animation settings WIDTH, HEIGHT = 800, 600 screen = pygame.Surface((WIDTH, HEIGHT)) clock = pygame.time.Clock() frames = [] # Colors BACKGROUND = (25, 25, 50) GROUND = (40, 100, 40) SUN = (255, 255, 200) # Character setup char_x = 100 char_y = HEIGHT - 100 target_x = 700 target_y = HEIGHT - 100 hops = 0 hop_height = 0 # Animation loop for frame in range(120): # Clear screen screen.fill(BACKGROUND) # Draw ground pygame.draw.rect(screen, GROUND, (0, HEIGHT - 50, WIDTH, 50)) # Draw sun pygame.draw.circle(screen, SUN, (700, 100), 50) # Draw target pygame.draw.circle(screen, (255, 200, 100), (target_x, target_y), 30) pygame.draw.circle(screen, (255, 150, 50), (target_x, target_y), 20) pygame.draw.circle(screen, (255, 100, 0), (target_x, target_y), 10) # Move character if hops < count: char_x += char_details["speed"] # Hop animation hop_height = 50 * np.sin(frame * 0.2) char_y = HEIGHT - 100 - abs(hop_height) # Check if target reached if char_x >= target_x - 40: hops += 1 char_x = 100 # Draw character pygame.draw.circle(screen, char_details["color"], (int(char_x), int(char_y)), char_details["size"]) # Draw eyes pygame.draw.circle(screen, (255, 255, 255), (int(char_x + 10), int(char_y - 5)), 8) pygame.draw.circle(screen, (0, 0, 0), (int(char_x + 10), int(char_y - 5)), 4) # Draw hop counter font = pygame.font.SysFont(None, 36) text = font.render(f"Hops: {hops}/{count}", True, (255, 255, 255)) screen.blit(text, (20, 20)) # Draw story text story_text = font.render(story[:40] + ("..." if len(story) > 40 else ""), True, (200, 200, 255)) screen.blit(story_text, (WIDTH//2 - story_text.get_width()//2, 50)) # Capture frame frame_data = pygame.surfarray.array3d(screen).swapaxes(0, 1) frames.append(frame_data) clock.tick(30) # Save as GIF imageio.mimsave(gif_path, frames, fps=30) return gif_path except Exception as e: st.error(f"Animation error: {str(e)}") return None def create_story_image(story): """Create a story image with Matplotlib""" try: # Create figure fig, ax = plt.subplots(figsize=(10, 6)) ax.set_facecolor('#f0f8ff') ax.set_xlim(0, 10) ax.set_ylim(0, 10) ax.axis('off') # Add title ax.text(5, 8, '✨ Your Story ✨', fontsize=24, ha='center', color='purple', fontweight='bold') # Add story text (wrapped) words = story.split() lines = [] current_line = [] for word in words: if len(' '.join(current_line + [word])) < 60: current_line.append(word) else: lines.append(' '.join(current_line)) current_line = [word] if current_line: lines.append(' '.join(current_line)) for i, line in enumerate(lines): ax.text(5, 6.5 - i*0.7, line, fontsize=14, ha='center') # Add decoration ax.text(2, 2, '🐰', fontsize=40, ha='center') ax.text(8, 2, '🐉', fontsize=40, ha='center') ax.text(5, 1, 'Created with StoryCoder', fontsize=12, ha='center', style='italic', color='gray') # Save to buffer buf = io.BytesIO() plt.savefig(buf, format='png', bbox_inches='tight', pad_inches=0.5) buf.seek(0) image = Image.open(buf) plt.close() return image except Exception as e: st.error(f"Image generation error: {str(e)}") return None def main(): """Main application function""" st.title("🧙♂️ StoryCoder - Learn Python Through Stories!") st.subheader("Turn your story into an animation and discover coding secrets!") # Initialize session state if 'story' not in st.session_state: st.session_state.story = "" if 'concepts' not in st.session_state: st.session_state.concepts = [] if 'animation_path' not in st.session_state: st.session_state.animation_path = None if 'active_tab' not in st.session_state: st.session_state.active_tab = "story" # Create tabs tabs = st.empty() tab_cols = st.columns(5) with tab_cols[0]: if st.button("📖 Create Story"): st.session_state.active_tab = "story" with tab_cols[1]: if st.button("🎬 Animation"): st.session_state.active_tab = "animation" with tab_cols[2]: if st.button("🔍 Concepts"): st.session_state.active_tab = "concepts" with tab_cols[3]: if st.button("💻 Code"): st.session_state.active_tab = "code" with tab_cols[4]: if st.button("🔄 Reset"): st.session_state.story = "" st.session_state.concepts = [] st.session_state.animation_path = None st.session_state.active_tab = "story" # Story creation tab if st.session_state.active_tab == "story": with st.container(): st.header("📖 Create Your Story") st.write("Write a short story (2-5 sentences) and I'll turn it into an animation!") story = st.text_area( "Your story:", height=200, placeholder="Once upon a time, a rabbit hopped 3 times to reach a carrot...", value=st.session_state.story, key="story_input" ) if st.button("Create Animation!", use_container_width=True): if len(story) < 10: st.error("Your story needs to be at least 10 characters long!") else: st.session_state.story = story with st.spinner("🧠 Analyzing your story for coding concepts..."): st.session_state.concepts = analyze_story(story) with st.spinner("🎬 Creating your animation (this may take a moment)..."): st.session_state.animation_path = generate_pygame_animation( story, st.session_state.concepts ) st.session_state.active_tab = "animation" st.rerun() # Show examples st.subheader("✨ Story Examples") col1, col2, col3 = st.columns(3) with col1: st.caption("Loop Example") st.code('"A dragon breathes fire 5 times at the castle"', language="text") with col2: st.caption("Conditional Example") st.code('"If it rains, the cat stays inside, else it goes out"', language="text") with col3: st.caption("Function Example") st.code('"A wizard casts a spell to make flowers grow"', language="text") # Animation tab elif st.session_state.active_tab == "animation": st.header("🎬 Your Story Animation") if not st.session_state.animation_path: st.warning("Please create a story first!") st.session_state.active_tab = "story" st.rerun() # Display animation st.markdown(f"""
{details['description']}
{details['example']}