Spaces:
Running
Running
# app.py - Game-Based Learning Version | |
import streamlit as st | |
import os | |
import time | |
import random | |
import base64 | |
import json | |
import requests | |
import re | |
from PIL import Image | |
import io | |
import matplotlib.pyplot as plt | |
import numpy as np | |
import pandas as pd | |
import plotly.express as px | |
import plotly.graph_objects as go | |
# Configure Streamlit page | |
st.set_page_config( | |
page_title="StoryCoder - Learn Coding Through Games", | |
page_icon="๐งโโ๏ธ", | |
layout="wide", | |
initial_sidebar_state="expanded" | |
) | |
# Custom CSS for colorful UI | |
st.markdown(""" | |
<style> | |
@import url('https://fonts.googleapis.com/css2?family=Comic+Neue:wght@700&display=swap'); | |
:root { | |
--primary: #FF6B6B; | |
--secondary: #4ECDC4; | |
--accent: #FFD166; | |
--dark: #1A535C; | |
--light: #F7FFF7; | |
} | |
body { | |
background: linear-gradient(135deg, var(--light) 0%, #E8F4F8 100%); | |
font-family: 'Comic Neue', cursive; | |
} | |
.stApp { | |
background: url('https://www.transparenttextures.com/patterns/cartographer.png'); | |
} | |
.story-box { | |
background-color: white; | |
border-radius: 20px; | |
padding: 25px; | |
box-shadow: 0 8px 16px rgba(26, 83, 92, 0.15); | |
border: 3px solid var(--accent); | |
margin-bottom: 25px; | |
} | |
.header { | |
color: var(--dark); | |
text-shadow: 2px 2px 4px rgba(0,0,0,0.1); | |
} | |
.concept-card { | |
background: linear-gradient(145deg, #ffffff, #f0f0f0); | |
border-radius: 15px; | |
padding: 15px; | |
margin: 10px 0; | |
border-left: 5px solid var(--secondary); | |
box-shadow: 0 4px 8px rgba(0,0,0,0.05); | |
} | |
.stButton>button { | |
background: linear-gradient(45deg, var(--primary), var(--secondary)); | |
color: white; | |
border-radius: 12px; | |
padding: 10px 24px; | |
font-weight: bold; | |
font-size: 18px; | |
border: none; | |
transition: all 0.3s; | |
} | |
.stButton>button:hover { | |
transform: scale(1.05); | |
box-shadow: 0 6px 12px rgba(0,0,0,0.15); | |
} | |
.stTextInput>div>div>input { | |
border-radius: 12px; | |
padding: 12px; | |
border: 2px solid var(--accent); | |
} | |
.tabs { | |
display: flex; | |
gap: 10px; | |
margin-bottom: 20px; | |
overflow-x: auto; | |
} | |
.tab { | |
padding: 10px 20px; | |
background-color: var(--accent); | |
border-radius: 10px; | |
cursor: pointer; | |
font-weight: bold; | |
white-space: nowrap; | |
} | |
.tab.active { | |
background-color: var(--secondary); | |
color: white; | |
} | |
@media (max-width: 768px) { | |
.tabs { | |
flex-wrap: wrap; | |
} | |
} | |
.game-container { | |
background-color: #1a1a2e; | |
border-radius: 15px; | |
padding: 20px; | |
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3); | |
margin-bottom: 25px; | |
position: relative; | |
overflow: hidden; | |
} | |
.game-preview { | |
border-radius: 10px; | |
overflow: hidden; | |
margin: 0 auto; | |
display: block; | |
max-width: 100%; | |
} | |
.character { | |
font-size: 48px; | |
text-align: center; | |
margin: 10px 0; | |
} | |
.game-instructions { | |
background-color: #f0f8ff; | |
border-radius: 15px; | |
padding: 15px; | |
margin: 15px 0; | |
} | |
.ai-game { | |
border: 3px solid var(--accent); | |
border-radius: 15px; | |
padding: 15px; | |
background: white; | |
margin: 20px 0; | |
} | |
</style> | |
""", 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" | |
} | |
} | |
# Pre-generated game examples | |
GAME_EXAMPLES = { | |
"loop": { | |
"image": "https://placehold.co/600x400/orange/white?text=Loop+Game", | |
"description": "Collect coins in a looping maze" | |
}, | |
"conditional": { | |
"image": "https://placehold.co/600x400/blue/white?text=Conditional+Game", | |
"description": "Avoid obstacles by making decisions" | |
}, | |
"function": { | |
"image": "https://placehold.co/600x400/green/white?text=Function+Game", | |
"description": "Cast spells using reusable functions" | |
} | |
} | |
# Initialize Groq client | |
def get_groq_client(): | |
api_key = st.secrets.get("GROQ_API_KEY", "") | |
if not api_key: | |
st.warning("Groq API key not found. Using simpler game generation.") | |
return None | |
try: | |
from groq import Groq | |
return Groq(api_key=api_key) | |
except ImportError: | |
st.error("Groq library not installed. Please install with `pip install groq`") | |
return None | |
except Exception as e: | |
st.error(f"Error initializing Groq client: {str(e)}") | |
return None | |
# Analyze story and identify programming concepts | |
def analyze_story(story): | |
story_lower = story.lower() | |
detected_concepts = [] | |
# Check for loops | |
if any(word in story_lower for word in ["times", "repeat", "again", "multiple", "many"]): | |
detected_concepts.append("loop") | |
# Check for conditionals | |
if any(word in story_lower for word in ["if", "when", "unless", "whether", "decision"]): | |
detected_concepts.append("conditional") | |
# Check for functions | |
if any(word in story_lower for word in ["make", "create", "do", "perform", "cast", "spell"]): | |
detected_concepts.append("function") | |
# Check for variables | |
if any(word in story_lower for word in ["is", "has", "set to", "value", "store"]): | |
detected_concepts.append("variable") | |
# Check for lists | |
if any(word in story_lower for word in ["and", "many", "several", "collection", "items", "group"]): | |
detected_concepts.append("list") | |
return list(set(detected_concepts)) | |
# Extract entities from story using regex | |
def extract_entities(story): | |
entities = { | |
"characters": [], | |
"objects": [], | |
"actions": [], | |
"locations": [] | |
} | |
# Extract characters (proper nouns) | |
entities["characters"] = list(set(re.findall(r'\b[A-Z][a-z]+\b', story))) | |
# Extract objects (nouns after "the") | |
entities["objects"] = list(set(re.findall(r'\bthe\s+(\w+)', story, re.I))) | |
# Extract actions (verbs) | |
entities["actions"] = list(set(re.findall(r'\b(\w+ed|\w+ing)\b', story))) | |
# Extract locations (nouns after "in", "on", "at") | |
locations = re.findall(r'\b(in|on|at)\s+the?\s?(\w+)', story) | |
entities["locations"] = list(set([loc[1] for loc in locations])) | |
# Filter out common words | |
common_words = ["the", "a", "an", "and", "or", "but", "if", "then", "when", "where"] | |
for key in entities: | |
entities[key] = [word for word in entities[key] | |
if isinstance(word, str) and | |
word.lower() not in common_words and | |
len(word) > 2] | |
return entities | |
# Generate game concept using Groq | |
def generate_game_concept(story, concepts, entities): | |
groq_client = get_groq_client() | |
if not groq_client: | |
# Fallback concept | |
return { | |
"title": "Adventure Game", | |
"description": "An exciting game based on your story!", | |
"mechanics": "Collect items and avoid obstacles", | |
"code_concepts": ", ".join(concepts), | |
"instructions": "Use arrow keys to move and space to jump" | |
} | |
try: | |
prompt = f""" | |
You are a game designer creating educational games for kids aged 6-12. | |
Create a simple 2D game concept based on the following story: | |
Story: "{story}" | |
Programming concepts to include: {', '.join(concepts)} | |
Extracted entities: | |
- Characters: {', '.join(entities['characters'])} | |
- Objects: {', '.join(entities['objects'])} | |
- Actions: {', '.join(entities['actions'])} | |
- Locations: {', '.join(entities['locations'])} | |
Respond in JSON format with these keys: | |
- title: Game title (max 5 words) | |
- description: Game description (1-2 sentences) | |
- mechanics: Core game mechanics (1 sentence) | |
- code_concepts: How programming concepts are used in the game | |
- instructions: Simple game controls (max 10 words) | |
""" | |
response = groq_client.chat.completions.create( | |
model="llama3-8b-8192", | |
messages=[{"role": "user", "content": prompt}], | |
temperature=0.7, | |
max_tokens=512, | |
response_format={"type": "json_object"} | |
) | |
game_data = json.loads(response.choices[0].message.content) | |
return game_data | |
except Exception as e: | |
st.error(f"Game concept generation error: {str(e)}") | |
return { | |
"title": "Adventure Game", | |
"description": "An exciting game based on your story!", | |
"mechanics": "Collect items and avoid obstacles", | |
"code_concepts": ", ".join(concepts), | |
"instructions": "Use arrow keys to move and space to jump" | |
} | |
# Generate game code using Groq | |
def generate_game_code(story, game_concept): | |
groq_client = get_groq_client() | |
if not groq_client: | |
# Fallback game code | |
return """# Simple Game Template | |
import pygame | |
import sys | |
# Initialize pygame | |
pygame.init() | |
# Set up display | |
WIDTH, HEIGHT = 800, 600 | |
screen = pygame.display.set_mode((WIDTH, HEIGHT)) | |
pygame.display.set_caption("Your Adventure Game") | |
# Colors | |
WHITE = (255, 255, 255) | |
BLUE = (0, 0, 255) | |
RED = (255, 0, 0) | |
GREEN = (0, 255, 0) | |
# Player | |
player = pygame.Rect(400, 300, 50, 50) | |
player_speed = 5 | |
# Game loop | |
clock = pygame.time.Clock() | |
running = True | |
while running: | |
for event in pygame.event.get(): | |
if event.type == pygame.QUIT: | |
running = False | |
# Player movement | |
keys = pygame.key.get_pressed() | |
if keys[pygame.K_LEFT]: | |
player.x -= player_speed | |
if keys[pygame.K_RIGHT]: | |
player.x += player_speed | |
if keys[pygame.K_UP]: | |
player.y -= player_speed | |
if keys[pygame.K_DOWN]: | |
player.y += player_speed | |
# Drawing | |
screen.fill(WHITE) | |
pygame.draw.rect(screen, BLUE, player) | |
# Display instructions | |
font = pygame.font.SysFont(None, 36) | |
text = font.render("Your Adventure Game - Move with arrow keys", True, (0, 0, 0)) | |
screen.blit(text, (50, 50)) | |
pygame.display.flip() | |
clock.tick(60) | |
pygame.quit() | |
sys.exit() | |
""", "Success" | |
try: | |
prompt = f""" | |
You are a Python game developer creating educational games for kids using Pygame. | |
Create a simple 2D game based on the following concept: | |
Game Title: {game_concept['title']} | |
Description: {game_concept['description']} | |
Mechanics: {game_concept['mechanics']} | |
Instructions: {game_concept['instructions']} | |
Requirements: | |
- Use Pygame library | |
- Simple graphics (shapes and colors) | |
- Include at least one character the player controls | |
- Include collectible items | |
- Include obstacles to avoid | |
- Score tracking | |
- Game over condition | |
Output ONLY the Python code with no additional text or explanations. | |
""" | |
response = groq_client.chat.completions.create( | |
model="llama3-70b-8192", | |
messages=[{"role": "user", "content": prompt}], | |
temperature=0.5, | |
max_tokens=2048 | |
) | |
game_code = response.choices[0].message.content | |
# Clean up the code | |
if "```python" in game_code: | |
game_code = game_code.split("```python")[1].split("```")[0] | |
elif "```" in game_code: | |
game_code = game_code.split("```")[1] | |
return game_code, "Success" | |
except Exception as e: | |
return f"# Error generating game code\nprint('{str(e)}')", str(e) | |
# Create placeholder game preview | |
def generate_game_preview(game_concept): | |
try: | |
# Create a simple placeholder image | |
img = Image.new('RGB', (600, 400), color=(73, 109, 137)) | |
# Add text to the image | |
try: | |
from PIL import ImageDraw, ImageFont | |
draw = ImageDraw.Draw(img) | |
# Try to use a default font | |
try: | |
font = ImageFont.truetype("arial.ttf", 30) | |
except: | |
# Fallback to basic font | |
font = ImageFont.load_default() | |
title = f"{game_concept['title']}" | |
draw.text((150, 150), title, fill=(255, 255, 0), font=font) | |
draw.text((100, 200), "Game Preview", fill=(255, 255, 255), font=font) | |
except: | |
pass | |
# Convert to bytes | |
buf = io.BytesIO() | |
img.save(buf, format='PNG') | |
buf.seek(0) | |
return buf | |
except Exception as e: | |
st.error(f"Game preview generation error: {str(e)}") | |
return None | |
# Create interactive visualization of game mechanics | |
def create_game_visualization(game_concept): | |
try: | |
# Create a simple visualization using Plotly | |
fig = go.Figure() | |
# Add player character | |
fig.add_trace(go.Scatter( | |
x=[0.5], y=[0.5], | |
mode='markers+text', | |
marker=dict(size=50, color='blue'), | |
text='Player', | |
textposition='bottom center', | |
name='Player' | |
)) | |
# Add collectibles | |
for i in range(5): | |
fig.add_trace(go.Scatter( | |
x=[random.uniform(0.2, 0.8)], y=[random.uniform(0.2, 0.8)], | |
mode='markers', | |
marker=dict(size=30, color='gold', symbol='star'), | |
name='Collectible' | |
)) | |
# Add obstacles | |
for i in range(3): | |
fig.add_trace(go.Scatter( | |
x=[random.uniform(0.1, 0.9)], y=[random.uniform(0.1, 0.9)], | |
mode='markers', | |
marker=dict(size=40, color='red', symbol='x'), | |
name='Obstacle' | |
)) | |
# Update layout | |
fig.update_layout( | |
title=f"Game Visualization: {game_concept.get('title', 'Your Game')}", | |
xaxis=dict(showgrid=False, zeroline=False, visible=False, range=[0, 1]), | |
yaxis=dict(showgrid=False, zeroline=False, visible=False, range=[0, 1]), | |
showlegend=False, | |
margin=dict(l=20, r=20, t=40, b=20), | |
height=400, | |
paper_bgcolor='rgba(0,0,0,0)', | |
plot_bgcolor='rgba(0,0,0,0)' | |
) | |
return fig | |
except Exception as e: | |
st.error(f"Visualization error: {str(e)}") | |
return None | |
def main(): | |
"""Main application function""" | |
st.title("๐งโโ๏ธ StoryCoder - Learn Coding Through Games!") | |
st.subheader("Turn your story into a game 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 'entities' not in st.session_state: | |
st.session_state.entities = {} | |
if 'game_concept' not in st.session_state: | |
st.session_state.game_concept = {} | |
if 'game_code' not in st.session_state: | |
st.session_state.game_code = "" | |
if 'game_preview' not in st.session_state: | |
st.session_state.game_preview = None | |
if 'active_tab' not in st.session_state: | |
st.session_state.active_tab = "story" | |
# Create tabs | |
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("๐ฎ Game"): | |
st.session_state.active_tab = "game" | |
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"): | |
for key in list(st.session_state.keys()): | |
if key != 'active_tab': | |
del st.session_state[key] | |
st.session_state.active_tab = "story" | |
st.rerun() | |
# 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 a game!") | |
story = st.text_area( | |
"Your story:", | |
height=200, | |
placeholder="Once upon a time, a rabbit named Ruby needed to collect 5 magical carrots in the enchanted forest while avoiding mischievous squirrels...", | |
value=st.session_state.story, | |
key="story_input" | |
) | |
if st.button("Create Game!", use_container_width=True): | |
if len(story) < 20: | |
st.error("Your story needs to be at least 20 characters long!") | |
else: | |
st.session_state.story = story | |
with st.spinner("๐ง Analyzing your story..."): | |
st.session_state.concepts = analyze_story(story) | |
st.session_state.entities = extract_entities(story) | |
with st.spinner("๐ฎ Designing your game..."): | |
st.session_state.game_concept = generate_game_concept( | |
story, | |
st.session_state.concepts, | |
st.session_state.entities | |
) | |
with st.spinner("๐พ Generating game preview..."): | |
st.session_state.game_preview = generate_game_preview( | |
st.session_state.game_concept | |
) | |
with st.spinner("๐ป Creating game code..."): | |
st.session_state.game_code, error = generate_game_code( | |
story, | |
st.session_state.game_concept | |
) | |
if error and "Error" in error: | |
st.error(f"Code generation error: {error}") | |
st.session_state.active_tab = "game" | |
st.rerun() | |
# Show examples | |
st.subheader("โจ Story Examples") | |
col1, col2, col3 = st.columns(3) | |
with col1: | |
st.caption("Loop Example") | |
st.code('"Ruby the rabbit needs to collect 5 magical carrots that appear every 10 seconds in the enchanted forest"', language="text") | |
st.image(GAME_EXAMPLES["loop"]["image"], | |
use_container_width=True, | |
caption=GAME_EXAMPLES["loop"]["description"]) | |
with col2: | |
st.caption("Conditional Example") | |
st.code('"If Alex the wizard sees a dragon, he casts a fire spell, otherwise he walks forward to find treasures"', language="text") | |
st.image(GAME_EXAMPLES["conditional"]["image"], | |
use_container_width=True, | |
caption=GAME_EXAMPLES["conditional"]["description"]) | |
with col3: | |
st.caption("Function Example") | |
st.code('"Wizard Lily creates magic spells to solve puzzles - each spell is a special function"', language="text") | |
st.image(GAME_EXAMPLES["function"]["image"], | |
use_container_width=True, | |
caption=GAME_EXAMPLES["function"]["description"]) | |
# Game tab | |
elif st.session_state.active_tab == "game": | |
st.header("๐ฎ Your Story Game") | |
if not st.session_state.get('story'): | |
st.warning("Please create a story first!") | |
st.session_state.active_tab = "story" | |
st.rerun() | |
# Display game concept | |
st.subheader(f"โจ {st.session_state.game_concept.get('title', 'Your Adventure Game')}") | |
st.write(st.session_state.game_concept.get('description', 'An exciting game based on your story!')) | |
# Display game preview | |
st.subheader("๐ผ๏ธ Game Preview") | |
if st.session_state.game_preview: | |
st.image(st.session_state.game_preview, use_container_width=True) | |
else: | |
concept = st.session_state.concepts[0] if st.session_state.concepts else "loop" | |
st.image(GAME_EXAMPLES[concept]["image"], | |
use_container_width=True, | |
caption="Example game preview") | |
# Game mechanics visualization | |
st.subheader("๐ฎ Game Mechanics") | |
if st.session_state.game_concept: | |
fig = create_game_visualization(st.session_state.game_concept) | |
if fig: | |
st.plotly_chart(fig, use_container_width=True) | |
else: | |
st.info("Game mechanics visualization would appear here") | |
# Game instructions | |
st.subheader("๐ How to Play") | |
st.markdown(f""" | |
<div class="game-instructions"> | |
<h4>Game Controls:</h4> | |
<p>{st.session_state.game_concept.get('instructions', 'Use arrow keys to move and space to jump')}</p> | |
<h4>Game Mechanics:</h4> | |
<p>{st.session_state.game_concept.get('mechanics', 'Collect items and avoid obstacles')}</p> | |
<h4>Coding Concepts:</h4> | |
<p>{st.session_state.game_concept.get('code_concepts', 'Loops, conditionals, and functions')}</p> | |
</div> | |
""", unsafe_allow_html=True) | |
if st.button("Show 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 Game") | |
st.subheader("We used these programming concepts in your game:") | |
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""" | |
<div class="concept-card" style="border-left: 5px solid {details['color']};"> | |
<div style="display:flex; align-items:center; gap:15px;"> | |
<span style="font-size:36px;">{details['emoji']}</span> | |
<h3 style="color:{details['color']};">{details['name']}</h3> | |
</div> | |
<p>{details['description']}</p> | |
<pre style="background:#f0f0f0; padding:10px; border-radius:8px;">{details['example']}</pre> | |
</div> | |
""", unsafe_allow_html=True) | |
st.subheader("๐ฎ How Concepts Are Used in Your Game") | |
st.write(st.session_state.game_concept.get('code_concepts', 'These concepts power the mechanics of your game')) | |
if st.button("See the Game Code!", use_container_width=True): | |
st.session_state.active_tab = "code" | |
st.rerun() | |
# Code tab | |
elif st.session_state.active_tab == "code": | |
st.header("๐ป Game Code") | |
st.write("Here's the Python code for your game. You can run it on your computer!") | |
if st.session_state.game_code: | |
st.subheader("๐ฎ Game Implementation") | |
st.code(st.session_state.game_code, language="python") | |
# Download button | |
st.download_button( | |
label="Download Game Code", | |
data=st.session_state.game_code, | |
file_name="story_game.py", | |
mime="text/python", | |
use_container_width=True | |
) | |
st.info("๐ก How to run your game:") | |
st.markdown(""" | |
1. Install Python from [python.org](https://python.org) | |
2. Install Pygame: `pip install pygame` | |
3. Download the code above | |
4. Run it with: `python story_game.py` | |
""") | |
st.success("๐ When you run this code, you'll see your story come to life as a playable game!") | |
# Game preview | |
st.subheader("๐ฎ What Your Game Looks Like") | |
concept = st.session_state.concepts[0] if st.session_state.concepts else "loop" | |
st.image(GAME_EXAMPLES[concept]["image"], | |
caption="Your game will look similar to this", | |
use_container_width=True) | |
else: | |
st.warning("No game code generated yet!") | |
if st.button("Create Another Game!", use_container_width=True): | |
st.session_state.active_tab = "story" | |
st.session_state.story = "" | |
st.rerun() | |
if __name__ == "__main__": | |
main() |