# app.py - Final Version with Enhanced Animations import streamlit as st import os import time import random import numpy as np import pandas as pd import plotly.express as px import plotly.graph_objects as go from gtts import gTTS import base64 from PIL import Image import io import matplotlib.pyplot as plt import requests # 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 with enhanced visuals CHARACTERS = [ {"name": "rabbit", "emoji": "🐰", "color": "#FFB6C1", "speed": 1.5}, {"name": "dragon", "emoji": "🐉", "color": "#FF6347", "speed": 0.8}, {"name": "cat", "emoji": "ðŸą", "color": "#DDA0DD", "speed": 2.0}, {"name": "dog", "emoji": "ðŸķ", "color": "#FFD700", "speed": 2.5}, {"name": "knight", "emoji": "ðŸĪš", "color": "#87CEEB", "speed": 1.2}, {"name": "wizard", "emoji": "🧙", "color": "#98FB98", "speed": 1.0}, {"name": "scientist", "emoji": "🔎", "color": "#20B2AA", "speed": 1.3}, {"name": "pirate", "emoji": "ðŸī‍☠ïļ", "color": "#FFA500", "speed": 1.4} ] # Object database for stories STORY_OBJECTS = [ {"name": "carrot", "emoji": "ðŸĨ•", "color": "#FF8C00"}, {"name": "treasure", "emoji": "💰", "color": "#FFD700"}, {"name": "castle", "emoji": "🏰", "color": "#A9A9A9"}, {"name": "flower", "emoji": "ðŸŒļ", "color": "#FF69B4"}, {"name": "book", "emoji": "📚", "color": "#8B4513"}, {"name": "star", "emoji": "⭐", "color": "#FFFF00"}, {"name": "key", "emoji": "🔑", "color": "#DAA520"}, {"name": "apple", "emoji": "🍎", "color": "#FF0000"} ] # Animation templates with visual cues ANIMATION_TEMPLATES = { "loop": { "description": "Character moving to a target multiple times", "visual_cue": "🔄 Repeating action multiple times", "code": """ # Loop Animation import matplotlib.pyplot as plt import numpy as np fig, ax = plt.subplots(figsize=(10, 6)) ax.set_title('Your Story: {story}') ax.set_xlim(0, 10) ax.set_ylim(0, 10) character = ax.text(1, 5, '{emoji}', fontsize=48, color='{color}') target = ax.text(9, 5, '{target_emoji}', fontsize=48) info = ax.text(5, 8, 'Loop: Repeating {count} times', fontsize=16, ha='center', color='{concept_color}') for i in range({count}): # Update position for pos in np.linspace(1, 9, 20): character.set_position((pos, 5)) plt.pause(0.05) # Show loop counter info.set_text(f'Loop: Completed {{i+1}}/{count}') plt.pause(0.2) # Return to start for pos in np.linspace(9, 1, 20): character.set_position((pos, 5)) plt.pause(0.05) plt.show() """ }, "conditional": { "description": "Character making a decision based on a condition", "visual_cue": "❓ Making a decision based on a condition", "code": """ # Conditional Animation import matplotlib.pyplot as plt import numpy as np import random fig, ax = plt.subplots(figsize=(10, 6)) ax.set_title('Your Story: {story}') ax.set_xlim(0, 10) ax.set_ylim(0, 10) character = ax.text(5, 5, '{emoji}', fontsize=48, color='{color}') condition = random.choice([True, False]) info = ax.text(5, 8, 'Condition: {condition_desc}', fontsize=16, ha='center', color='{concept_color}') if condition: # Path for True condition decision = ax.text(8, 7, '✅ True Path', fontsize=20, color='green') path = np.linspace(5, 8, 20) else: # Path for False condition decision = ax.text(2, 7, '❌ False Path', fontsize=20, color='red') path = np.linspace(5, 2, 20) for pos in path: character.set_position((pos, 5)) plt.pause(0.05) plt.show() """ }, "function": { "description": "Character performing an action multiple times", "visual_cue": "âœĻ Performing action with a function", "code": """ # Function Animation import matplotlib.pyplot as plt import numpy as np fig, ax = plt.subplots(figsize=(10, 6)) ax.set_title('Your Story: {story}') ax.set_xlim(0, 10) ax.set_ylim(0, 10) character = ax.text(5, 5, '{emoji}', fontsize=48, color='{color}') info = ax.text(5, 8, 'Function: Performing action {count} times', fontsize=16, ha='center', color='{concept_color}') def perform_action(position): # Action animation character.set_fontsize(60) plt.pause(0.1) character.set_fontsize(48) plt.pause(0.1) # Move to new position character.set_position((position, 5)) for i in range({count}): perform_action(5 + i) info.set_text(f'Function: Action {{i+1}}/{count}') plt.pause(0.3) plt.show() """ } } 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 extract_count_from_story(story): """Extract a number from the story to use in animations""" for word in story.split(): if word.isdigit(): return min(int(word), 10) return 3 # Default value def extract_story_elements(story): """Extract characters and objects from the story""" # Choose a random character character = random.choice(CHARACTERS) # Choose a random object story_object = random.choice(STORY_OBJECTS) # Try to find character in story for char in CHARACTERS: if char["name"] in story.lower(): character = char break # Try to find object in story for obj in STORY_OBJECTS: if obj["name"] in story.lower(): story_object = obj break return character, story_object def create_visualization(story, concepts): """Create a visualization image based on the story and concepts""" try: # Get story elements character, story_object = extract_story_elements(story) count = extract_count_from_story(story) concept = concepts[0] if concepts else "variable" concept_color = CONCEPTS[concept]["color"] # 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, 9, 'âœĻ Your Story Visualization âœĻ', fontsize=20, ha='center', color='purple', fontweight='bold') # Add story text story_text = story[:80] + ('...' if len(story) > 80 else '') ax.text(5, 8, f'"{story_text}"', fontsize=14, ha='center', color='#333') # Add character ax.text(3, 5, character["emoji"], fontsize=100, ha='center', color=character["color"]) # Add object ax.text(7, 5, story_object["emoji"], fontsize=100, ha='center', color=story_object["color"]) # Add connection arrow ax.annotate('', xy=(7, 5), xytext=(3, 5), arrowprops=dict(arrowstyle='->', lw=3, color=concept_color)) # Add concept visualization concept_text = CONCEPTS[concept]["emoji"] + " " + CONCEPTS[concept]["name"] ax.text(5, 3, concept_text, fontsize=24, ha='center', color=concept_color, fontweight='bold') # Add action text action = f"Action: {character['name'].title()} moves toward {story_object['name']}" ax.text(5, 2.5, action, fontsize=16, ha='center', color='#333') # Add footer 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, dpi=150) buf.seek(0) plt.close() return buf except Exception as e: st.error(f"Visualization error: {str(e)}") return None def create_interactive_animation(story, concepts): """Create an interactive animation using Plotly""" try: # Get story elements character, story_object = extract_story_elements(story) count = extract_count_from_story(story) concept = concepts[0] if concepts else "variable" concept_color = CONCEPTS[concept]["color"] # Create animation data frames = [] for i in range(count): # Create a path for the character path_length = 20 for step in range(path_length): progress = (i * path_length + step) / (count * path_length) frames.append({ "frame": i * path_length + step, "x": 3 + 4 * progress, "y": 5 + np.sin(progress * np.pi * 2) * 2, # Wave motion "size": 20 + np.sin(step * 0.3) * 10, # Pulsing effect "character": character["emoji"], "color": character["color"], "info": f"{character['name'].title()} moving to {story_object['name']}", "step": f"Action {i+1}/{count}" }) df = pd.DataFrame(frames) # Create the target object (static) target_df = pd.DataFrame([{ "x": 7, "y": 5, "character": story_object["emoji"], "color": story_object["color"], "size": 30, "info": f"Target: {story_object['name']}", "step": "Target" }]) # Create animated scatter plot fig = px.scatter( df, x="x", y="y", animation_frame="frame", text="character", size="size", size_max=45, color_discrete_sequence=[character["color"]], range_x=[0, 10], range_y=[0, 10], hover_name="info", hover_data={"step": True} ) # Add the target object fig.add_trace(go.Scatter( x=target_df["x"], y=target_df["y"], text=target_df["character"], mode="text", textfont=dict(size=40, color=story_object["color"]), hoverinfo="text", hovertext=target_df["info"], showlegend=False )) # Add concept indicator fig.add_annotation( x=0.5, y=1.05, xref="paper", yref="paper", text=f"{CONCEPTS[concept]['emoji']} {CONCEPTS[concept]['name']}", showarrow=False, font=dict(size=24, color=concept_color) ) # Customize layout fig.update_layout( title=f'Animation: {story[:60]}{"..." if len(story) > 60 else ""}', showlegend=False, plot_bgcolor='rgba(240,248,255,1)', paper_bgcolor='rgba(240,248,255,1)', width=800, height=600, xaxis=dict(showgrid=False, zeroline=False, visible=False), yaxis=dict(showgrid=False, zeroline=False, visible=False), updatemenus=[dict( type="buttons", buttons=[dict( label="Play", method="animate", args=[None, {"frame": {"duration": 100, "redraw": True}}] )] )]) fig.update_traces( textfont_size=40, textposition='middle center', hoverlabel=dict(bgcolor="white", font_size=16) ) return fig except Exception as e: st.error(f"Interactive animation error: {str(e)}") return None def text_to_speech(text, filename="story_audio.mp3"): """Convert text to speech using gTTS""" try: tts = gTTS(text=text, lang='en') tts.save(filename) return filename except Exception as e: st.error(f"Audio creation error: {str(e)}") return None def autoplay_audio(file_path): """Autoplay audio in Streamlit""" with open(file_path, "rb") as f: data = f.read() b64 = base64.b64encode(data).decode() md = f""" """ st.markdown(md, unsafe_allow_html=True) def generate_animation_code(story, concepts): """Generate Python animation code based on story""" try: # Get story elements character, story_object = extract_story_elements(story) count = extract_count_from_story(story) concept = concepts[0] if concepts else "loop" # Get the appropriate template template = ANIMATION_TEMPLATES.get(concept, ANIMATION_TEMPLATES["loop"]) # Fill in the template condition_desc = random.choice(["sunny", "raining", "dark"]) code = template["code"].format( emoji=character["emoji"], color=character["color"], story=story[:80] + ('...' if len(story) > 80 else ''), count=count, condition_desc=condition_desc, target_emoji=story_object["emoji"], concept_color=CONCEPTS[concept]["color"] ) return code, template["description"], template["visual_cue"] except Exception as e: return f"# Error generating code\nprint('Could not generate code: {str(e)}')", "", "" 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', color='#333') # 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, dpi=150) buf.seek(0) plt.close() return buf 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 'visualization' not in st.session_state: st.session_state.visualization = None if 'interactive_animation' not in st.session_state: st.session_state.interactive_animation = None if 'animation_code' not in st.session_state: st.session_state.animation_code = "" if 'code_description' not in st.session_state: st.session_state.code_description = "" if 'visual_cue' not in st.session_state: st.session_state.visual_cue = "" if 'audio_file' not in st.session_state: st.session_state.audio_file = 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.visualization = None st.session_state.interactive_animation = None st.session_state.animation_code = "" st.session_state.code_description = "" st.session_state.visual_cue = "" st.session_state.audio_file = 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 visualization..."): st.session_state.visualization = create_visualization( story, st.session_state.concepts ) with st.spinner("🚀 Creating interactive animation..."): st.session_state.interactive_animation = create_interactive_animation( story, st.session_state.concepts ) with st.spinner("ðŸ’ŧ Generating animation code..."): st.session_state.animation_code, st.session_state.code_description, st.session_state.visual_cue = generate_animation_code( story, st.session_state.concepts ) with st.spinner("🔊 Creating audio narration..."): audio_text = f"Your story: {story}. This demonstrates the programming concept: {st.session_state.code_description}" st.session_state.audio_file = text_to_speech(audio_text) 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") st.image("https://i.imgur.com/7zQY1eE.png", width=200) with col2: st.caption("Conditional Example") st.code('"If it rains, the cat stays inside, else it goes out"', language="text") st.image("https://i.imgur.com/5X8jYAy.png", width=200) with col3: st.caption("Function Example") st.code('"A wizard casts a spell to make flowers grow"', language="text") st.image("https://i.imgur.com/9zJkQ7P.png", width=200) # Animation tab elif st.session_state.active_tab == "animation": st.header("🎎 Your Story Animation") if not st.session_state.story: st.warning("Please create a story first!") st.session_state.active_tab = "story" st.rerun() # Display visualization st.subheader("ðŸŽĻ Story Visualization") if st.session_state.visualization: st.image(st.session_state.visualization, use_container_width=True) else: st.warning("Visualization couldn't be generated. Showing story image instead.") story_img = create_story_image(st.session_state.story) if story_img: st.image(story_img, use_container_width=True) # Display interactive animation st.subheader("🚀 Interactive Animation") if st.session_state.interactive_animation: st.plotly_chart(st.session_state.interactive_animation, use_container_width=True) else: st.info("Interactive animation preview. The real animation would run on your computer!") st.image("https://i.imgur.com/8LzQY7a.gif", use_container_width=True) # Play audio narration st.subheader("🔊 Story Narration") if st.session_state.audio_file: audio_bytes = open(st.session_state.audio_file, 'rb').read() st.audio(audio_bytes, format='audio/mp3') if st.button("â–ķïļ Play Automatically"): autoplay_audio(st.session_state.audio_file) else: st.info("Audio narration would play automatically here") st.success("âœĻ Animation created successfully!") st.caption("This animation was generated with Python code based on your story!") if st.button("Reveal Coding Secrets!", use_container_width=True): st.session_state.active_tab = "concepts" st.rerun() # Concepts tab elif st.session_state.active_tab == "concepts": st.header("🔍 Coding Concepts in Your Story") st.subheader("We secretly used these programming concepts:") if not st.session_state.concepts: st.warning("No concepts detected in your story! Try adding words like '3 times', 'if', or 'make'.") else: for concept in st.session_state.concepts: if concept in CONCEPTS: details = CONCEPTS[concept] st.markdown(f"""
{details['emoji']}

{details['name']}

{details['description']}

{details['example']}
""", unsafe_allow_html=True) if st.button("See the Magic Code!", use_container_width=True): st.session_state.active_tab = "code" st.rerun() # Code tab elif st.session_state.active_tab == "code": st.header("ðŸ’ŧ The Magic Code Behind Your Animation") st.write("Here's the Python code that would create your animation:") if st.session_state.animation_code: st.subheader(st.session_state.code_description) st.markdown(f"**Visual Cue:** {st.session_state.visual_cue}") st.code(st.session_state.animation_code, language="python") # Download button st.download_button( label="Download Animation Code", data=st.session_state.animation_code, file_name="story_animation.py", mime="text/python", use_container_width=True ) st.info("ðŸ’Ą To run this animation on your computer:") st.markdown(""" 1. Install Python from [python.org](https://python.org) 2. Install required libraries: `pip install matplotlib numpy` 3. Copy the code above into a file named `animation.py` 4. Run it with: `python animation.py` """) st.success("🎉 When you run this code, you'll see your story come to life!") else: st.warning("No code generated yet!") if st.button("Create Another Story!", use_container_width=True): st.session_state.active_tab = "story" st.session_state.story = "" st.rerun() if __name__ == "__main__": main()