Spaces:
Running
Running
# app.py - Fixed Version with Free Models | |
import streamlit as st | |
import os | |
import time | |
import random | |
import json | |
import base64 | |
import requests | |
import re | |
from PIL import Image | |
import io | |
import matplotlib.pyplot as plt | |
from transformers import pipeline, set_seed | |
# 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 game-themed UI | |
st.markdown(""" | |
<style> | |
@import url('https://fonts.googleapis.com/css2?family=Fredoka+One&family=Comic+Neue:wght@700&display=swap'); | |
:root { | |
--primary: #FF6B6B; | |
--secondary: #4ECDC4; | |
--accent: #FFD166; | |
--dark: #1A535C; | |
--light: #F7FFF7; | |
--game-blue: #118AB2; | |
--game-purple: #9B5DE5; | |
} | |
body { | |
background: linear-gradient(135deg, #E8F4F8 0%, #FFDEE9 100%); | |
font-family: 'Comic Neue', cursive; | |
} | |
.stApp { | |
background: url('https://www.transparenttextures.com/patterns/cartographer.png'); | |
} | |
.game-card { | |
background-color: white; | |
border-radius: 20px; | |
padding: 25px; | |
box-shadow: 0 8px 32px rgba(26, 83, 92, 0.2); | |
border: 4px solid var(--game-purple); | |
margin-bottom: 25px; | |
transition: all 0.3s; | |
} | |
.game-card:hover { | |
transform: translateY(-5px); | |
box-shadow: 0 12px 24px rgba(26, 83, 92, 0.3); | |
} | |
.header { | |
color: var(--dark); | |
font-family: 'Fredoka One', cursive; | |
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 12px rgba(0,0,0,0.1); | |
} | |
.stButton>button { | |
background: linear-gradient(45deg, var(--game-purple), var(--game-blue)); | |
color: white; | |
border-radius: 50px; | |
padding: 12px 28px; | |
font-weight: bold; | |
font-size: 20px; | |
border: none; | |
transition: all 0.3s; | |
font-family: 'Fredoka One', cursive; | |
} | |
.stButton>button:hover { | |
transform: scale(1.05); | |
box-shadow: 0 8px 16px rgba(0,0,0,0.2); | |
} | |
.stTextInput>div>div>input { | |
border-radius: 20px; | |
padding: 14px; | |
border: 3px solid var(--accent); | |
font-size: 18px; | |
} | |
.tabs { | |
display: flex; | |
gap: 10px; | |
margin-bottom: 20px; | |
overflow-x: auto; | |
background: rgba(255,255,255,0.7); | |
padding: 10px; | |
border-radius: 20px; | |
} | |
.tab { | |
padding: 12px 24px; | |
background-color: var(--accent); | |
border-radius: 15px; | |
cursor: pointer; | |
font-weight: bold; | |
white-space: nowrap; | |
font-family: 'Fredoka One', cursive; | |
font-size: 18px; | |
transition: all 0.3s; | |
} | |
.tab.active { | |
background: linear-gradient(45deg, var(--game-purple), var(--game-blue)); | |
color: white; | |
box-shadow: 0 4px 8px rgba(0,0,0,0.1); | |
} | |
.game-preview { | |
border: 4px solid var(--game-blue); | |
border-radius: 20px; | |
overflow: hidden; | |
background: linear-gradient(135deg, #a2d2ff, #bde0fe); | |
padding: 20px; | |
margin: 20px 0; | |
} | |
.character { | |
font-size: 64px; | |
text-align: center; | |
margin: 20px 0; | |
text-shadow: 4px 4px 8px rgba(0,0,0,0.2); | |
} | |
.game-container { | |
background: rgba(255,255,255,0.9); | |
border-radius: 20px; | |
padding: 30px; | |
box-shadow: 0 8px 32px rgba(0,0,0,0.1); | |
margin: 20px 0; | |
} | |
.code-block { | |
background: #2b2d42; | |
color: #f8f9fa; | |
border-radius: 15px; | |
padding: 20px; | |
font-family: 'Courier New', monospace; | |
overflow-x: auto; | |
margin: 20px 0; | |
} | |
.concept-emoji { | |
font-size: 36px; | |
margin-right: 15px; | |
vertical-align: middle; | |
} | |
.game-title { | |
font-family: 'Fredoka One', cursive; | |
color: var(--game-purple); | |
text-align: center; | |
font-size: 32px; | |
margin-bottom: 20px; | |
text-shadow: 2px 2px 4px rgba(0,0,0,0.1); | |
} | |
@media (max-width: 768px) { | |
.tabs { | |
flex-wrap: wrap; | |
} | |
.character { | |
font-size: 48px; | |
} | |
} | |
</style> | |
""", unsafe_allow_html=True) | |
# Initialize session state | |
def init_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 'game_scenario' not in st.session_state: | |
st.session_state.game_scenario = "" | |
if 'game_code' not in st.session_state: | |
st.session_state.game_code = "" | |
if 'game_explanation' not in st.session_state: | |
st.session_state.game_explanation = "" | |
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" | |
if 'loading' not in st.session_state: | |
st.session_state.loading = False | |
# Concept database | |
CONCEPTS = { | |
"loop": { | |
"name": "Loop", | |
"emoji": "๐", | |
"description": "Loops repeat actions multiple times", | |
"example": "for i in range(5):\n print('Hello!')", | |
"color": "#FF9E6D", | |
"game_example": "Repeating a jump 5 times to cross a river" | |
}, | |
"conditional": { | |
"name": "Conditional", | |
"emoji": "โ", | |
"description": "Conditionals make decisions in code", | |
"example": "if sunny:\n go_outside()\nelse:\n stay_inside()", | |
"color": "#4ECDC4", | |
"game_example": "Choosing different paths based on obstacles" | |
}, | |
"function": { | |
"name": "Function", | |
"emoji": "โจ", | |
"description": "Functions are reusable blocks of code", | |
"example": "def greet(name):\n print(f'Hello {name}!')", | |
"color": "#FFD166", | |
"game_example": "Creating a jump function used multiple times" | |
}, | |
"variable": { | |
"name": "Variable", | |
"emoji": "๐ฆ", | |
"description": "Variables store information", | |
"example": "score = 10\nplayer = 'Alex'", | |
"color": "#FF6B6B", | |
"game_example": "Keeping track of collected stars" | |
}, | |
"list": { | |
"name": "List", | |
"emoji": "๐", | |
"description": "Lists store collections of items", | |
"example": "fruits = ['apple', 'banana', 'orange']", | |
"color": "#1A535C", | |
"game_example": "Storing collected treasures in a backpack" | |
} | |
} | |
# Load text generation model | |
def load_text_generator(): | |
"""Load a lightweight text generation model""" | |
try: | |
return pipeline('text-generation', model='gpt2', framework='pt', device=-1) | |
except: | |
return None | |
# Analyze story and extract concepts | |
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", "decide"]): | |
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", "score"]): | |
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)) | |
# Generate game scenario using free models | |
def generate_game_scenario(story, concepts): | |
"""Generate a game scenario based on the story and concepts""" | |
# Create a simple template-based scenario | |
concept_names = [CONCEPTS[c]['name'] for c in concepts] | |
concept_list = ", ".join(concept_names) | |
return f""" | |
Game Title: {story[:15]} Adventure | |
Game Objective: Complete challenges based on the story: {story[:100]}... | |
Characters: | |
- Hero: The main character from your story | |
- Helper: A friendly guide who explains coding concepts | |
- Villain: A character that creates obstacles (if applicable) | |
Game Mechanics: | |
1. The player controls the hero using arrow keys | |
2. Collect items mentioned in the story | |
3. Avoid obstacles and solve simple puzzles | |
4. Helper characters appear to teach {concept_list} | |
Coding Concepts: This game teaches {concept_list} through: | |
- Using loops to repeat actions | |
- Making decisions with conditionals | |
- Creating reusable functions | |
- Tracking progress with variables | |
- Managing collections with lists | |
Visual Description: Colorful 3D world with cartoon-style characters, vibrant landscapes, and magical effects. | |
""" | |
# Generate game code explanation | |
def generate_game_explanation(story, concepts, game_scenario): | |
"""Generate explanation of game code""" | |
# Create simple explanation based on concepts | |
concept_explanations = "\n".join( | |
[f"- {CONCEPTS[c]['name']}: {CONCEPTS[c]['game_example']}" for c in concepts] | |
) | |
return f""" | |
In this game based on your story "{story[:20]}...", we use programming concepts to make it work: | |
{concept_explanations} | |
As you play the game, think about: | |
1. How the game uses these concepts to create challenges | |
2. How you might change the code to make the game easier or harder | |
The code brings your story to life in a 3D game world! | |
""" | |
# Generate simple game code | |
def generate_game_code(story, concepts): | |
"""Generate simple PyGame code for the game""" | |
# Generate a unique game based on story keywords | |
keywords = re.findall(r'\b\w{4,}\b', story)[:3] | |
player_char = keywords[0] if keywords else "hero" | |
collect_item = keywords[1] if len(keywords) > 1 else "star" | |
obstacle = keywords[2] if len(keywords) > 2 else "rock" | |
return f""" | |
# {story[:30]} Adventure Game | |
import pygame | |
import random | |
import sys | |
# Initialize pygame | |
pygame.init() | |
# Game setup | |
WIDTH, HEIGHT = 800, 600 | |
screen = pygame.display.set_mode((WIDTH, HEIGHT)) | |
pygame.display.set_caption("{player_char.capitalize()} Adventure") | |
clock = pygame.time.Clock() | |
# Colors | |
BACKGROUND = (173, 216, 230) # Light blue | |
PLAYER_COLOR = (255, 0, 0) # Red | |
GOAL_COLOR = (255, 223, 0) # Gold | |
OBSTACLE_COLOR = (139, 69, 19) # Brown | |
TEXT_COLOR = (0, 0, 0) # Black | |
# Player setup | |
player_size = 50 | |
player_x = 100 | |
player_y = HEIGHT // 2 | |
player_speed = 5 | |
# Goal setup | |
goal_size = 40 | |
goal_x = WIDTH - 150 | |
goal_y = HEIGHT // 2 | |
# Variables concept: Tracking score | |
score = 0 | |
font = pygame.font.SysFont(None, 36) | |
# List concept: Creating obstacles | |
obstacles = [] | |
for i in range(5): | |
obstacles.append([ | |
random.randint(200, WIDTH - 100), | |
random.randint(50, HEIGHT - 100), | |
random.randint(20, 50), | |
random.randint(20, 50) | |
]) | |
# Game loop | |
running = True | |
while running: | |
# Event handling | |
for event in pygame.event.get(): | |
if event.type == pygame.QUIT: | |
running = False | |
# Player movement | |
keys = pygame.key.get_pressed() | |
if keys[pygame.K_UP]: | |
player_y -= player_speed | |
if keys[pygame.K_DOWN]: | |
player_y += player_speed | |
if keys[pygame.K_LEFT]: | |
player_x -= player_speed | |
if keys[pygame.K_RIGHT]: | |
player_x += player_speed | |
# Boundary checking | |
player_x = max(0, min(WIDTH - player_size, player_x)) | |
player_y = max(0, min(HEIGHT - player_size, player_y)) | |
# Collision detection with goal | |
player_rect = pygame.Rect(player_x, player_y, player_size, player_size) | |
goal_rect = pygame.Rect(goal_x, goal_y, goal_size, goal_size) | |
# Conditional concept: Check for collision | |
if player_rect.colliderect(goal_rect): | |
# Function concept: Increase score | |
score += 1 | |
# Move goal to new position | |
goal_x = random.randint(100, WIDTH - 100) | |
goal_y = random.randint(50, HEIGHT - 100) | |
# Drawing | |
screen.fill(BACKGROUND) | |
# Draw obstacles | |
for obstacle in obstacles: | |
pygame.draw.rect(screen, OBSTACLE_COLOR, | |
(obstacle[0], obstacle[1], obstacle[2], obstacle[3])) | |
# Draw player and goal | |
pygame.draw.rect(screen, PLAYER_COLOR, | |
(player_x, player_y, player_size, player_size)) | |
pygame.draw.rect(screen, GOAL_COLOR, | |
(goal_x, goal_y, goal_size, goal_size)) | |
# Display score | |
score_text = font.render(f"{collect_item.capitalize()}s: {{score}}", True, TEXT_COLOR) | |
screen.blit(score_text, (20, 20)) | |
# Display story title | |
title_text = font.render(f"{player_char.capitalize()} Adventure", True, TEXT_COLOR) | |
screen.blit(title_text, (WIDTH // 2 - 100, 20)) | |
# Display instructions | |
help_text = font.render("Arrow keys to move - Collect the gold squares!", True, TEXT_COLOR) | |
screen.blit(help_text, (WIDTH // 2 - 200, HEIGHT - 40)) | |
# Update display | |
pygame.display.flip() | |
clock.tick(60) | |
pygame.quit() | |
sys.exit() | |
""" | |
# Generate game preview visualization | |
def generate_game_preview(): | |
"""Generate a visual preview of the game""" | |
try: | |
# Create a simple visualization | |
fig, ax = plt.subplots(figsize=(10, 6)) | |
ax.set_facecolor('#a2d2ff') | |
ax.set_xlim(0, 10) | |
ax.set_ylim(0, 6) | |
# Draw game elements | |
ax.text(5, 5, "Your Adventure", fontsize=20, ha='center', color='#9b5de5') | |
ax.plot([1, 9], [3, 3], 'k-', linewidth=2) # Ground | |
# Player character | |
ax.plot(2, 3.5, 'ro', markersize=15) | |
ax.text(2, 4, 'Player', ha='center') | |
# Goal | |
ax.plot(8, 3.5, 'yo', markersize=15) | |
ax.text(8, 4, 'Goal', ha='center') | |
# Obstacles | |
for i in range(3): | |
x = random.uniform(3, 7) | |
y = random.uniform(3.2, 4) | |
ax.plot(x, y, 's', color='#8d99ae', markersize=12) | |
ax.text(x, y+0.3, 'Obstacle', ha='center', fontsize=8) | |
# Path | |
ax.plot([2, 8], [3.5, 3.5], 'y--', linewidth=2) | |
ax.axis('off') | |
ax.set_title("Game Preview - Move player to collect goals while avoiding obstacles", fontsize=14) | |
# Save to bytes | |
buf = io.BytesIO() | |
plt.savefig(buf, format='png', dpi=100, bbox_inches='tight') | |
buf.seek(0) | |
return buf | |
except Exception as e: | |
st.error(f"Preview generation error: {str(e)}") | |
return None | |
# Main application function | |
def main(): | |
init_session_state() | |
st.title("๐ฎ StoryCoder - Learn Coding Through Games!") | |
st.subheader("Turn your story into a 3D game and discover coding secrets!") | |
# Create tabs | |
st.markdown('<div class="tabs">', unsafe_allow_html=True) | |
col1, col2, col3, col4, col5 = st.columns(5) | |
with col1: | |
if st.button("๐ Create Story", use_container_width=True): | |
st.session_state.active_tab = "story" | |
with col2: | |
if st.button("๐ฎ Play Game", use_container_width=True): | |
st.session_state.active_tab = "game" | |
with col3: | |
if st.button("๐ Concepts", use_container_width=True): | |
st.session_state.active_tab = "concepts" | |
with col4: | |
if st.button("๐ป Game Code", use_container_width=True): | |
st.session_state.active_tab = "code" | |
with col5: | |
if st.button("๐ New Story", use_container_width=True): | |
st.session_state.story = "" | |
st.session_state.concepts = [] | |
st.session_state.game_scenario = "" | |
st.session_state.game_code = "" | |
st.session_state.game_explanation = "" | |
st.session_state.game_preview = None | |
st.session_state.active_tab = "story" | |
st.markdown('</div>', unsafe_allow_html=True) | |
# 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 3D game!") | |
story = st.text_area( | |
"Your story:", | |
height=200, | |
placeholder="Once upon a time, a brave knight had to collect 5 magical stars in a castle...", | |
value=st.session_state.story, | |
key="story_input" | |
) | |
if st.button("Create Game!", 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 | |
st.session_state.loading = True | |
with st.spinner("๐ง Analyzing your story for coding concepts..."): | |
st.session_state.concepts = analyze_story(story) | |
with st.spinner("๐ฎ Creating your 3D game scenario..."): | |
st.session_state.game_scenario = generate_game_scenario( | |
story, st.session_state.concepts | |
) | |
with st.spinner("๐ป Generating game code..."): | |
st.session_state.game_code = generate_game_code( | |
story, st.session_state.concepts | |
) | |
with st.spinner("๐ Creating coding explanations..."): | |
st.session_state.game_explanation = generate_game_explanation( | |
story, st.session_state.concepts, st.session_state.game_scenario | |
) | |
with st.spinner("๐ผ๏ธ Generating game preview..."): | |
st.session_state.game_preview = generate_game_preview() | |
st.session_state.active_tab = "game" | |
st.session_state.loading = False | |
st.rerun() | |
# Show examples | |
st.subheader("โจ Story Examples") | |
col1, col2, col3 = st.columns(3) | |
with col1: | |
st.caption("Space Explorer") | |
st.code('"An astronaut needs to collect 3 stars while avoiding asteroids"', language="text") | |
st.image("https://i.imgur.com/7zQY1eE.gif", | |
use_column_width=True, | |
caption="Space Explorer Game") | |
with col2: | |
st.caption("Jungle Adventure") | |
st.code('"A monkey swings through trees to collect bananas before sunset"', language="text") | |
st.image("https://i.imgur.com/5X8jYAy.gif", | |
use_column_width=True, | |
caption="Jungle Adventure Game") | |
with col3: | |
st.caption("Dragon Quest") | |
st.code('"A dragon flies through clouds to collect magic crystals"', language="text") | |
st.image("https://i.imgur.com/9zJkQ7P.gif", | |
use_column_width=True, | |
caption="Dragon Quest Game") | |
# Game tab | |
elif st.session_state.active_tab == "game": | |
st.header("๐ฎ Your Story Game") | |
if not st.session_state.story: | |
st.warning("Please create a story first!") | |
st.session_state.active_tab = "story" | |
st.rerun() | |
# Display game scenario | |
st.subheader("๐ Game Scenario") | |
st.markdown(f'<div class="game-card">{st.session_state.game_scenario}</div>', unsafe_allow_html=True) | |
# 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: | |
st.info("Game preview visualization") | |
st.image("https://i.imgur.com/7zQY1eE.gif", use_container_width=True) | |
# Game explanation | |
st.subheader("๐ How This Game Teaches Coding") | |
st.markdown(f'<div class="game-card">{st.session_state.game_explanation}</div>', unsafe_allow_html=True) | |
# Play instructions | |
st.subheader("โถ๏ธ How to Play") | |
st.markdown(""" | |
<div class="game-card"> | |
<ol> | |
<li>Download the game code from the <b>Game Code</b> tab</li> | |
<li>Install Python from <a href="https://python.org" target="_blank">python.org</a></li> | |
<li>Install PyGame: <code>pip install pygame</code></li> | |
<li>Run the game: <code>python your_game.py</code></li> | |
<li>Use arrow keys to move your character!</li> | |
</ol> | |
</div> | |
""", unsafe_allow_html=True) | |
if st.button("Learn Coding Concepts", 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("Your game teaches 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 'collect'.") | |
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> | |
<p><b>In your game:</b> {details['game_example']}</p> | |
<pre style="background:#f0f0f0; padding:10px; border-radius:8px;">{details['example']}</pre> | |
</div> | |
""", unsafe_allow_html=True) | |
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. Download it and run on your computer!") | |
if st.session_state.game_code: | |
# Display code with syntax highlighting | |
st.subheader("Your Game Code") | |
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 | |
) | |
# Game running instructions | |
st.subheader("๐ฅ๏ธ How to Run Your Game") | |
st.markdown(""" | |
<div class="game-card"> | |
<ol> | |
<li>Install Python from <a href="https://python.org" target="_blank">python.org</a></li> | |
<li>Install PyGame: Open command prompt and type <code>pip install pygame</code></li> | |
<li>Save the game code to a file named <code>my_game.py</code></li> | |
<li>Run the game: <code>python my_game.py</code></li> | |
<li>Use arrow keys to play!</li> | |
</ol> | |
</div> | |
""", unsafe_allow_html=True) | |
# What to expect | |
st.subheader("๐ฎ What to Expect When Playing") | |
st.markdown(""" | |
<div class="game-card"> | |
<ul> | |
<li>Move your character (red square) with arrow keys</li> | |
<li>Collect the gold squares to increase your score</li> | |
<li>Avoid the brown obstacles</li> | |
<li>See how programming concepts make the game work!</li> | |
</ul> | |
</div> | |
""", unsafe_allow_html=True) | |
else: | |
st.warning("No game code generated yet!") | |
if st.button("Create Another Story!", use_container_width=True): | |
st.session_state.active_tab = "story" | |
st.rerun() | |
if __name__ == "__main__": | |
main() |