Spaces:
Running
Running
# app.py - Enhanced Version with Groq API & Playable Games | |
import streamlit as st | |
import os | |
import time | |
import random | |
import json | |
import re | |
import base64 | |
import requests | |
from PIL import Image | |
import io | |
import matplotlib.pyplot as plt | |
import numpy as np | |
from groq import Groq | |
from streamlit_js_eval import streamlit_js_eval | |
# 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 with new color scheme | |
st.markdown(""" | |
<style> | |
@import url('https://fonts.googleapis.com/css2?family=Fredoka+One&family=Comic+Neue:wght@700&display=swap'); | |
:root { | |
--primary: #6A67CE; | |
--secondary: #FF7C7C; | |
--accent: #FDD85D; | |
--dark: #2D3250; | |
--light: #F0F0F0; | |
--game-blue: #1A8CD8; | |
--game-purple: #9B5DE5; | |
} | |
body { | |
background: linear-gradient(135deg, #E6F7FF 0%, #FFEEF6 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(45, 50, 80, 0.15); | |
border: 4px solid var(--primary); | |
margin-bottom: 25px; | |
transition: all 0.3s; | |
} | |
.game-card:hover { | |
transform: translateY(-5px); | |
box-shadow: 0 12px 24px rgba(45, 50, 80, 0.2); | |
} | |
.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, #f5f5ff); | |
border-radius: 15px; | |
padding: 15px; | |
margin: 10px 0; | |
border-left: 5px solid var(--accent); | |
box-shadow: 0 4px 12px rgba(0,0,0,0.08); | |
} | |
.stButton>button { | |
background: linear-gradient(45deg, var(--primary), var(--game-blue)); | |
color: white; | |
border-radius: 50px; | |
padding: 12px 28px; | |
font-weight: bold; | |
font-size: 18px; | |
border: none; | |
transition: all 0.3s; | |
font-family: 'Fredoka One', cursive; | |
} | |
.stButton>button:hover { | |
transform: scale(1.05); | |
box-shadow: 0 8px 16px rgba(106, 103, 206, 0.3); | |
} | |
.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(--light); | |
border-radius: 15px; | |
cursor: pointer; | |
font-weight: bold; | |
white-space: nowrap; | |
font-family: 'Fredoka One', cursive; | |
font-size: 16px; | |
transition: all 0.3s; | |
color: var(--dark); | |
} | |
.tab.active { | |
background: linear-gradient(45deg, var(--primary), var(--game-blue)); | |
color: white; | |
box-shadow: 0 4px 8px rgba(0,0,0,0.1); | |
} | |
.game-preview { | |
border: 4px solid var(--primary); | |
border-radius: 20px; | |
overflow: hidden; | |
background: linear-gradient(135deg, #d0e5ff, #e0d1ff); | |
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(--primary); | |
text-align: center; | |
font-size: 32px; | |
margin-bottom: 20px; | |
text-shadow: 2px 2px 4px rgba(0,0,0,0.1); | |
} | |
.game-canvas { | |
width: 100%; | |
height: 400px; | |
background: #d0e5ff; | |
border-radius: 15px; | |
margin: 20px 0; | |
position: relative; | |
overflow: hidden; | |
} | |
.player { | |
position: absolute; | |
width: 40px; | |
height: 40px; | |
background: var(--primary); | |
border-radius: 50%; | |
transition: all 0.1s; | |
} | |
.goal { | |
position: absolute; | |
width: 30px; | |
height: 30px; | |
background: var(--accent); | |
border-radius: 50%; | |
box-shadow: 0 0 10px var(--accent); | |
} | |
.obstacle { | |
position: absolute; | |
background: var(--secondary); | |
border-radius: 5px; | |
} | |
.game-controls { | |
display: flex; | |
justify-content: center; | |
gap: 10px; | |
margin: 20px 0; | |
} | |
.control-btn { | |
width: 60px; | |
height: 60px; | |
border-radius: 50%; | |
background: var(--primary); | |
color: white; | |
display: flex; | |
align-items: center; | |
justify-content: center; | |
font-size: 24px; | |
cursor: pointer; | |
border: none; | |
box-shadow: 0 4px 8px rgba(0,0,0,0.2); | |
} | |
@media (max-width: 768px) { | |
.tabs { | |
flex-wrap: wrap; | |
} | |
.character { | |
font-size: 48px; | |
} | |
.control-btn { | |
width: 50px; | |
height: 50px; | |
font-size: 20px; | |
} | |
} | |
</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 | |
if 'game_state' not in st.session_state: | |
st.session_state.game_state = { | |
"player_x": 100, | |
"player_y": 200, | |
"score": 0, | |
"goal_x": 600, | |
"goal_y": 200, | |
"obstacles": [], | |
"game_active": False | |
} | |
if 'groq_api_key' not in st.session_state: | |
st.session_state.groq_api_key = "" | |
# Concept database | |
CONCEPTS = { | |
"loop": { | |
"name": "Loop", | |
"emoji": "๐", | |
"description": "Loops repeat actions multiple times", | |
"example": "for i in range(5):\n print('Jump!')", | |
"color": "#6A67CE", | |
"game_example": "Repeating jumps to cross a river" | |
}, | |
"conditional": { | |
"name": "Conditional", | |
"emoji": "โ", | |
"description": "Conditionals make decisions in code", | |
"example": "if key_found:\n open_door()\nelse:\n keep_searching()", | |
"color": "#FF7C7C", | |
"game_example": "Choosing paths based on obstacles" | |
}, | |
"function": { | |
"name": "Function", | |
"emoji": "โจ", | |
"description": "Functions are reusable blocks of code", | |
"example": "def collect_star():\n score += 1\n play_sound()", | |
"color": "#FDD85D", | |
"game_example": "Creating a jump function used multiple times" | |
}, | |
"variable": { | |
"name": "Variable", | |
"emoji": "๐ฆ", | |
"description": "Variables store information", | |
"example": "score = 0\nplayer_health = 100", | |
"color": "#1A8CD8", | |
"game_example": "Tracking collected stars" | |
}, | |
"list": { | |
"name": "List", | |
"emoji": "๐", | |
"description": "Lists store collections of items", | |
"example": "inventory = ['sword', 'shield', 'potion']", | |
"color": "#4CAF50", | |
"game_example": "Storing collected treasures" | |
} | |
} | |
# Initialize Groq client | |
def get_groq_client(): | |
try: | |
if st.session_state.groq_api_key: | |
return Groq(api_key=st.session_state.groq_api_key) | |
return None | |
except: | |
return None | |
# Analyze story and extract concepts | |
def analyze_story(story): | |
"""Analyze story and identify programming concepts""" | |
story_lower = story.lower() | |
detected_concepts = [] | |
# Improved concept detection | |
# Check for loops | |
if any(word in story_lower for word in ["times", "repeat", "again", "multiple", "each", "every"]): | |
detected_concepts.append("loop") | |
# Check for conditionals | |
if any(word in story_lower for word in ["if", "when", "unless", "whether", "decide", "choice", "otherwise"]): | |
detected_concepts.append("conditional") | |
# Check for functions | |
if any(word in story_lower for word in ["make", "create", "do", "perform", "cast", "action", "use"]): | |
detected_concepts.append("function") | |
# Check for variables | |
if any(word in story_lower for word in ["is", "has", "set to", "value", "score", "count", "number"]): | |
detected_concepts.append("variable") | |
# Check for lists | |
if any(word in story_lower for word in ["and", "many", "several", "collection", "items", "group", "set of"]): | |
detected_concepts.append("list") | |
return list(set(detected_concepts)) if detected_concepts else ["variable", "function"] | |
# Generate game scenario using Groq | |
def generate_game_scenario(story, concepts): | |
"""Generate a game scenario based on the story and concepts""" | |
try: | |
client = get_groq_client() | |
if client: | |
concept_names = [CONCEPTS[c]['name'] for c in concepts] | |
concept_list = ", ".join(concept_names) | |
system_prompt = ( | |
"You are an expert in creating educational games for children aged 6-12. " | |
"Create a simple 3D-style game scenario based on the child's story. " | |
"The game should teach programming concepts through gameplay. " | |
"Structure your response with these sections:\n" | |
"Game Title: ...\n" | |
"Game Objective: ...\n" | |
"Characters: ...\n" | |
"Game Mechanics: ...\n" | |
"Coding Concepts: Explain how these programming concepts are used: " + concept_list + "\n" | |
"Visual Description: Describe the game world visually\n" | |
"Keep it under 200 words and fun for kids." | |
) | |
response = client.chat.completions.create( | |
model="llama3-70b-8192", | |
messages=[ | |
{"role": "system", "content": system_prompt}, | |
{"role": "user", "content": story} | |
], | |
temperature=0.8, | |
max_tokens=500 | |
) | |
return response.choices[0].message.content | |
except Exception as e: | |
st.error(f"Game scenario generation error: {str(e)}") | |
# Fallback template | |
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 your 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. Move your character using arrow keys | |
2. Collect items mentioned in your story | |
3. Avoid obstacles and solve 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""" | |
try: | |
client = get_groq_client() | |
if client: | |
concept_names = [CONCEPTS[c]['name'] for c in concepts] | |
concept_list = ", ".join(concept_names) | |
system_prompt = ( | |
"Explain how the game code implements programming concepts in a way " | |
"a child aged 6-12 can understand. Use simple analogies and relate to the story. " | |
"Structure your response with:\n" | |
"Introduction: ...\n" | |
"Concept 1: ... (with example from the game)\n" | |
"Concept 2: ... (with example from the game)\n" | |
"Conclusion: ...\n" | |
"Keep it under 300 words and engaging for kids." | |
) | |
response = client.chat.completions.create( | |
model="llama3-70b-8192", | |
messages=[ | |
{"role": "system", "content": system_prompt}, | |
{"role": "user", "content": f"Story: {story}\nGame Scenario: {game_scenario}\nConcepts: {concept_list}"} | |
], | |
temperature=0.7, | |
max_tokens=600 | |
) | |
return response.choices[0].message.content | |
except Exception as e: | |
st.error(f"Explanation generation error: {str(e)}") | |
# Fallback explanation | |
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""" | |
# Extract keywords from story | |
keywords = re.findall(r'\b\w{4,}\b', story)[:3] | |
player_char = keywords[0].capitalize() if keywords else "Hero" | |
collect_item = keywords[1] if len(keywords) > 1 else "star" | |
obstacle = keywords[2] if len(keywords) > 2 else "rock" | |
# Get concept emojis | |
concept_emojis = "".join([CONCEPTS[c]['emoji'] for c in concepts]) | |
return f""" | |
# {player_char}'s Adventure: {story[:20]}... | |
# Teaches: {concept_emojis} {", ".join([CONCEPTS[c]['name'] for c in concepts])} | |
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}'s Adventure") | |
clock = pygame.time.Clock() | |
# Colors | |
BACKGROUND = (230, 240, 255) # Light blue | |
PLAYER_COLOR = (106, 103, 206) # Purple | |
GOAL_COLOR = (253, 216, 93) # Yellow | |
OBSTACLE_COLOR = (255, 124, 124) # Coral | |
TEXT_COLOR = (45, 50, 80) # Dark blue | |
# Player setup | |
player_size = 40 | |
player_x = 100 | |
player_y = HEIGHT // 2 | |
player_speed = 5 | |
# Goal setup | |
goal_size = 30 | |
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(30, 70), | |
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] or keys[pygame.K_w]: | |
player_y -= player_speed | |
if keys[pygame.K_DOWN] or keys[pygame.K_s]: | |
player_y += player_speed | |
if keys[pygame.K_LEFT] or keys[pygame.K_a]: | |
player_x -= player_speed | |
if keys[pygame.K_RIGHT] or keys[pygame.K_d]: | |
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.circle(screen, GOAL_COLOR, | |
(goal_x + goal_size//2, goal_y + goal_size//2), goal_size//2) | |
# 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}'s Adventure: {story[:20]}...", True, TEXT_COLOR) | |
screen.blit(title_text, (WIDTH // 2 - 150, 20)) | |
# Display concepts | |
concepts_text = font.render(f"Teaches: {', '.join([CONCEPTS[c]['name'] for c in concepts])}", True, TEXT_COLOR) | |
screen.blit(concepts_text, (20, HEIGHT - 40)) | |
# Display instructions | |
help_text = font.render("Arrow keys to move - Collect the yellow circles!", True, TEXT_COLOR) | |
screen.blit(help_text, (WIDTH // 2 - 200, HEIGHT - 80)) | |
# Update display | |
pygame.display.flip() | |
clock.tick(60) | |
pygame.quit() | |
sys.exit() | |
""" | |
# Generate game preview visualization | |
def generate_game_preview(story): | |
"""Generate a visual preview of the game""" | |
try: | |
# Extract keywords for theme | |
theme = "space" if "space" in story.lower() else "jungle" if "jungle" in story.lower() else "fantasy" | |
# Create a simple visualization | |
fig, ax = plt.subplots(figsize=(10, 6)) | |
if theme == "space": | |
bg_color = '#0B0B2B' | |
player_color = '#6A67CE' | |
goal_color = '#FDD85D' | |
obstacle_color = '#FF7C7C' | |
title = "Space Adventure" | |
elif theme == "jungle": | |
bg_color = '#143D2C' | |
player_color = '#6A67CE' | |
goal_color = '#FDD85D' | |
obstacle_color = '#D35400' | |
title = "Jungle Adventure" | |
else: | |
bg_color = '#3A015C' | |
player_color = '#6A67CE' | |
goal_color = '#FDD85D' | |
obstacle_color = '#FF7C7C' | |
title = "Fantasy Quest" | |
ax.set_facecolor(bg_color) | |
ax.set_xlim(0, 10) | |
ax.set_ylim(0, 6) | |
# Draw game elements | |
ax.text(5, 5, title, fontsize=20, ha='center', color='white') | |
ax.plot([1, 9], [1, 1], 'w-', linewidth=2) # Ground | |
# Player character | |
ax.plot(2, 2, 'o', markersize=15, color=player_color) | |
ax.text(2, 2.7, 'You', ha='center', color='white', fontsize=12) | |
# Goal | |
ax.plot(8, 2, 'o', markersize=12, color=goal_color) | |
ax.text(8, 2.7, 'Goal', ha='center', color='white', fontsize=12) | |
# Obstacles | |
for i in range(3): | |
x = random.uniform(3, 7) | |
y = random.uniform(1.5, 2.5) | |
ax.plot(x, y, 's', markersize=15, color=obstacle_color) | |
ax.text(x, y+0.4, 'Obstacle', ha='center', color='white', fontsize=8) | |
# Path | |
ax.plot([2, 8], [2, 2], 'y--', linewidth=1, alpha=0.5) | |
ax.axis('off') | |
ax.set_title("Game Preview", fontsize=16, color='white') | |
# Save to bytes | |
buf = io.BytesIO() | |
plt.savefig(buf, format='png', dpi=100, bbox_inches='tight', facecolor=bg_color) | |
buf.seek(0) | |
return buf | |
except Exception as e: | |
st.error(f"Preview generation error: {str(e)}") | |
return None | |
# Create a playable game in the browser | |
def create_playable_game(): | |
"""Create an interactive game using Streamlit components""" | |
st.subheader("๐ฎ Play Your Game in the Browser!") | |
# Initialize game state | |
if 'game_state' not in st.session_state: | |
st.session_state.game_state = { | |
"player_x": 100, | |
"player_y": 200, | |
"score": 0, | |
"goal_x": 600, | |
"goal_y": 200, | |
"obstacles": [ | |
{"x": 300, "y": 150, "w": 80, "h": 30}, | |
{"x": 400, "y": 250, "w": 60, "h": 40}, | |
{"x": 200, "y": 300, "w": 100, "h": 25} | |
], | |
"game_active": True | |
} | |
state = st.session_state.game_state | |
# Game canvas | |
st.markdown(f""" | |
<div class="game-canvas"> | |
<div class="player" style="left:{state['player_x']}px; top:{state['player_y']}px;"></div> | |
<div class="goal" style="left:{state['goal_x']}px; top:{state['goal_y']}px;"></div> | |
{''.join([ | |
f'<div class="obstacle" style="left:{obs["x"]}px; top:{obs["y"]}px; width:{obs["w"]}px; height:{obs["h"]}px;"></div>' | |
for obs in state['obstacles'] | |
])} | |
</div> | |
<div style="text-align:center; font-size:24px; font-family:'Fredoka One'; color: var(--dark);"> | |
Stars Collected: {state['score']} | |
</div> | |
""", unsafe_allow_html=True) | |
# Game controls | |
st.markdown(""" | |
<div class="game-controls"> | |
<button class="control-btn" onclick="movePlayer('up')">โ</button> | |
<div> | |
<button class="control-btn" onclick="movePlayer('left')">โ</button> | |
<button class="control-btn" style="margin:0 10px;" onclick="movePlayer('down')">โ</button> | |
<button class="control-btn" onclick="movePlayer('right')">โ</button> | |
</div> | |
</div> | |
<script> | |
function movePlayer(direction) { | |
Streamlit.setComponentValue(direction); | |
} | |
</script> | |
""", unsafe_allow_html=True) | |
# Handle movement | |
direction = streamlit_js_eval(js_expressions="parent.document.querySelector('.game-controls').lastDirection", want_output=True) | |
if direction: | |
if direction == "up" and state['player_y'] > 20: | |
state['player_y'] -= 20 | |
elif direction == "down" and state['player_y'] < 360: | |
state['player_y'] += 20 | |
elif direction == "left" and state['player_x'] > 20: | |
state['player_x'] -= 20 | |
elif direction == "right" and state['player_x'] < 740: | |
state['player_x'] += 20 | |
# Check for collision with goal | |
if (abs(state['player_x'] - state['goal_x']) < 40 and | |
abs(state['player_y'] - state['goal_y']) < 40): | |
state['score'] += 1 | |
state['goal_x'] = random.randint(100, 700) | |
state['goal_y'] = random.randint(50, 350) | |
st.session_state.game_state = state | |
st.rerun() | |
# Main application function | |
def main(): | |
init_session_state() | |
# Sidebar for API key | |
with st.sidebar: | |
st.header("โ๏ธ Settings") | |
st.session_state.groq_api_key = st.text_input( | |
"Enter Groq API Key (optional):", | |
type="password", | |
help="Get a free key from https://console.groq.com/keys" | |
) | |
st.caption("Using Groq API will create more creative and personalized games!") | |
st.divider() | |
st.caption("Made with โค๏ธ for kids learning to code") | |
st.caption("v2.0 | StoryCoder") | |
st.title("๐ฎ StoryCoder - Learn Coding Through Games!") | |
st.subheader("Turn your story into a playable 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(story) | |
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) | |
example_images = { | |
"Space Explorer": "https://images.unsplash.com/photo-1462331940025-496dfbfc7564?w=500", | |
"Jungle Adventure": "https://images.unsplash.com/photo-1546182990-dffeafbe841d?w=500", | |
"Dragon Quest": "https://images.unsplash.com/photo-1541414779316-9564c1f85944?w=500" | |
} | |
with col1: | |
st.caption("Space Explorer") | |
st.code('"An astronaut needs to collect 3 stars while avoiding asteroids"', language="text") | |
st.image(example_images["Space Explorer"], | |
use_container_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(example_images["Jungle Adventure"], | |
use_container_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(example_images["Dragon Quest"], | |
use_container_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://images.unsplash.com/photo-1542751110-97427bbecf20?w=500", use_container_width=True) | |
# Playable game | |
create_playable_game() | |
# 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 the Full Game") | |
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 or WASD 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 or WASD 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 (purple square) with arrow keys</li> | |
<li>Collect the yellow circles to increase your score</li> | |
<li>Avoid the coral 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() |