Spaces:
Running
Running
# app.py - Enhanced Version with New Design | |
import streamlit as st | |
import random | |
import re | |
import time | |
import base64 | |
import json | |
import requests | |
from PIL import Image | |
import io | |
import matplotlib.pyplot as plt | |
import numpy as np | |
# Configure Streamlit page | |
st.set_page_config( | |
page_title="StoryCoder - Play & Learn Coding", | |
page_icon="๐ฎ", | |
layout="wide", | |
initial_sidebar_state="expanded" | |
) | |
# Custom CSS with white background | |
st.markdown(""" | |
<style> | |
@import url('https://fonts.googleapis.com/css2?family=Fredoka+One&family=Comic+Neue:wght@700&display=swap'); | |
:root { | |
--primary: #8A2BE2; | |
--secondary: #9370DB; | |
--accent: #FFD700; | |
--dark: #4B0082; | |
--light: #FFFFFF; | |
--game-blue: #6A5ACD; | |
--game-purple: #9400D3; | |
} | |
body { | |
background: #FFFFFF; | |
font-family: 'Comic Neue', cursive; | |
margin: 0; | |
padding: 0; | |
min-height: 100vh; | |
} | |
.stApp { | |
background: #FFFFFF; | |
} | |
.game-card { | |
background: rgba(255, 255, 255, 0.95); | |
border-radius: 15px; | |
padding: 20px; | |
box-shadow: 0 4px 16px rgba(138, 43, 226, 0.1); | |
border: 1px solid #E0E0E0; | |
margin-bottom: 20px; | |
transition: all 0.3s; | |
} | |
.game-card:hover { | |
transform: translateY(-3px); | |
box-shadow: 0 8px 16px rgba(138, 43, 226, 0.15); | |
} | |
.header { | |
color: var(--dark); | |
font-family: 'Fredoka One', cursive; | |
} | |
.concept-card { | |
background: rgba(255, 255, 255, 0.95); | |
border-radius: 12px; | |
padding: 15px; | |
margin: 10px 0; | |
border-left: 4px solid var(--accent); | |
box-shadow: 0 2px 8px rgba(0,0,0,0.05); | |
} | |
.stButton>button { | |
background: linear-gradient(45deg, var(--primary), var(--game-purple)); | |
color: white; | |
border-radius: 12px; | |
padding: 10px 24px; | |
font-weight: bold; | |
font-size: 16px; | |
border: none; | |
transition: all 0.3s; | |
font-family: 'Fredoka One', cursive; | |
box-shadow: 0 4px 8px rgba(0,0,0,0.1); | |
} | |
.stButton>button:hover { | |
transform: scale(1.03); | |
box-shadow: 0 6px 12px rgba(138, 43, 226, 0.2); | |
} | |
.stTextInput>div>div>input { | |
border-radius: 12px; | |
padding: 12px; | |
border: 2px solid var(--accent); | |
font-size: 16px; | |
background: rgba(255, 255, 255, 0.95); | |
} | |
.tabs { | |
display: flex; | |
gap: 8px; | |
margin-bottom: 20px; | |
overflow-x: auto; | |
background: rgba(255, 255, 255, 0.95); | |
padding: 8px; | |
border-radius: 12px; | |
border: 1px solid #E0E0E0; | |
} | |
.tab { | |
padding: 10px 20px; | |
background: rgba(255, 255, 255, 0.95); | |
border-radius: 12px; | |
cursor: pointer; | |
font-weight: bold; | |
white-space: nowrap; | |
font-family: 'Fredoka One', cursive; | |
font-size: 14px; | |
transition: all 0.3s; | |
color: var(--dark); | |
border: 1px solid #E0E0E0; | |
} | |
.tab.active { | |
background: linear-gradient(45deg, var(--primary), var(--game-purple)); | |
color: white; | |
box-shadow: 0 4px 8px rgba(0,0,0,0.1); | |
} | |
.game-board { | |
display: grid; | |
grid-template-columns: repeat(8, 1fr); | |
grid-template-rows: repeat(8, 1fr); | |
gap: 4px; | |
width: 450px; | |
height: 450px; | |
margin: 0 auto; | |
background: #FFFFFF; | |
padding: 10px; | |
border-radius: 12px; | |
border: 1px solid #E0E0E0; | |
} | |
.cell { | |
display: flex; | |
justify-content: center; | |
align-items: center; | |
border-radius: 6px; | |
font-size: 28px; | |
background: #FFFFFF; | |
transition: all 0.2s; | |
cursor: pointer; | |
box-shadow: 0 2px 4px rgba(0,0,0,0.05); | |
border: 1px solid #F0F0F0; | |
} | |
.cell:hover { | |
transform: scale(1.03); | |
background: #F8F8F8; | |
} | |
.player { | |
background: linear-gradient(45deg, var(--primary), var(--game-blue)); | |
color: white; | |
box-shadow: 0 4px 8px rgba(0,0,0,0.1); | |
} | |
.goal { | |
background: linear-gradient(45deg, var(--accent), #FFA500); | |
box-shadow: 0 4px 8px rgba(0,0,0,0.1); | |
} | |
.obstacle { | |
background: linear-gradient(45deg, var(--secondary), #8A2BE2); | |
box-shadow: 0 4px 8px rgba(0,0,0,0.1); | |
} | |
.star { | |
background: linear-gradient(45deg, #FFD700, #FFA500); | |
box-shadow: 0 4px 8px rgba(0,0,0,0.1); | |
} | |
.portal { | |
background: linear-gradient(45deg, #9B5DE5, #6A0DAD); | |
box-shadow: 0 4px 8px rgba(0,0,0,0.1); | |
} | |
.game-controls { | |
display: flex; | |
justify-content: center; | |
gap: 12px; | |
margin: 20px 0; | |
} | |
.control-btn { | |
width: 60px; | |
height: 60px; | |
border-radius: 50%; | |
background: linear-gradient(45deg, var(--primary), var(--game-purple)); | |
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.1); | |
transition: all 0.2s; | |
} | |
.control-btn:hover { | |
transform: scale(1.08); | |
box-shadow: 0 6px 12px rgba(0,0,0,0.15); | |
} | |
.score-board { | |
background: #FFFFFF; | |
border-radius: 12px; | |
padding: 12px; | |
text-align: center; | |
font-family: 'Fredoka One', cursive; | |
font-size: 20px; | |
margin: 15px auto; | |
width: 280px; | |
box-shadow: 0 4px 8px rgba(0,0,0,0.05); | |
border: 1px solid #E0E0E0; | |
} | |
.level-indicator { | |
background: #FFFFFF; | |
border-radius: 12px; | |
padding: 8px 16px; | |
text-align: center; | |
font-family: 'Fredoka One', cursive; | |
font-size: 18px; | |
margin: 8px auto; | |
width: 180px; | |
box-shadow: 0 2px 6px rgba(0,0,0,0.05); | |
border: 1px solid #E0E0E0; | |
} | |
.theme-customizer { | |
background: #FFFFFF; | |
border-radius: 15px; | |
padding: 15px; | |
margin: 15px 0; | |
box-shadow: 0 4px 12px rgba(138, 43, 226, 0.1); | |
border: 1px solid #E0E0E0; | |
} | |
.customizer-title { | |
text-align: center; | |
font-family: 'Fredoka One', cursive; | |
color: var(--primary); | |
margin-bottom: 15px; | |
font-size: 20px; | |
} | |
.char-preview { | |
text-align: center; | |
font-size: 40px; | |
margin: 8px 0; | |
} | |
@media (max-width: 768px) { | |
.tabs { | |
flex-wrap: wrap; | |
} | |
.game-board { | |
width: 85vw; | |
height: 85vw; | |
} | |
.control-btn { | |
width: 50px; | |
height: 50px; | |
font-size: 20px; | |
} | |
} | |
.concept-buttons { | |
display: flex; | |
flex-wrap: wrap; | |
gap: 10px; | |
margin: 15px 0; | |
} | |
.concept-btn { | |
flex: 1 0 calc(33.333% - 10px); | |
min-width: 120px; | |
padding: 12px; | |
border-radius: 10px; | |
background: rgba(255, 255, 255, 0.95); | |
border: 1px solid #E0E0E0; | |
box-shadow: 0 2px 6px rgba(0,0,0,0.05); | |
transition: all 0.3s; | |
text-align: center; | |
cursor: pointer; | |
} | |
.concept-btn:hover { | |
transform: translateY(-3px); | |
box-shadow: 0 4px 8px rgba(138, 43, 226, 0.15); | |
border-color: var(--primary); | |
} | |
.concept-btn.active { | |
background: linear-gradient(45deg, var(--primary), var(--game-purple)); | |
color: white; | |
box-shadow: 0 4px 8px rgba(0,0,0,0.1); | |
} | |
.example-box { | |
border: 1px solid #E0E0E0; | |
border-radius: 12px; | |
padding: 15px; | |
margin-bottom: 15px; | |
transition: all 0.3s; | |
cursor: pointer; | |
} | |
.example-box:hover { | |
transform: translateY(-3px); | |
box-shadow: 0 4px 12px rgba(138, 43, 226, 0.1); | |
border-color: var(--primary); | |
} | |
.toast { | |
position: fixed; | |
bottom: 20px; | |
left: 50%; | |
transform: translateX(-50%); | |
background: rgba(0, 0, 0, 0.8); | |
color: white; | |
padding: 10px 20px; | |
border-radius: 25px; | |
z-index: 1000; | |
font-family: 'Fredoka One', cursive; | |
animation: fadeInOut 2s ease-in-out; | |
} | |
@keyframes fadeInOut { | |
0% { opacity: 0; bottom: 0; } | |
20% { opacity: 1; bottom: 20px; } | |
80% { opacity: 1; bottom: 20px; } | |
100% { opacity: 0; bottom: 0; } | |
} | |
</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 '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: | |
reset_game_state() | |
if 'player_char' not in st.session_state: | |
st.session_state.player_char = "๐ฆธ" | |
if 'goal_char' not in st.session_state: | |
st.session_state.goal_char = "๐" | |
if 'obstacle_char' not in st.session_state: | |
st.session_state.obstacle_char = "๐ชจ" | |
if 'current_level' not in st.session_state: | |
st.session_state.current_level = 1 | |
if 'total_levels' not in st.session_state: | |
st.session_state.total_levels = 3 | |
if 'selected_concept' not in st.session_state: | |
st.session_state.selected_concept = None | |
if 'toast_message' not in st.session_state: | |
st.session_state.toast_message = None | |
# Concept database | |
CONCEPTS = { | |
"loop": { | |
"name": "Loop", | |
"emoji": "๐", | |
"description": "Loops repeat actions multiple times", | |
"example": "for i in range(5):\n print('Hello!')", | |
"color": "#8A2BE2", | |
"game_example": "Repeating jumps 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": "#9370DB", | |
"game_example": "Choosing 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": "#FFD700", | |
"game_example": "Creating a jump function used multiple times" | |
}, | |
"variable": { | |
"name": "Variable", | |
"emoji": "๐ฆ", | |
"description": "Variables store information", | |
"example": "score = 10\nplayer = 'Alex'", | |
"color": "#6A5ACD", | |
"game_example": "Keeping track of collected stars" | |
}, | |
"list": { | |
"name": "List", | |
"emoji": "๐", | |
"description": "Lists store collections of items", | |
"example": "fruits = ['apple', 'banana', 'orange']", | |
"color": "#9400D3", | |
"game_example": "Storing collected treasures in a backpack" | |
} | |
} | |
# 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 | |
def generate_game_scenario(story, concepts): | |
"""Generate a game scenario based on the story and concepts""" | |
# 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 or buttons | |
2. Collect stars while avoiding obstacles | |
3. Reach the goal to win | |
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 Style: Clean and colorful game world with simple characters. | |
""" | |
# Generate game code explanation | |
def generate_game_explanation(story, concepts, game_scenario): | |
"""Generate explanation of game code""" | |
# 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 fun game world! | |
""" | |
def reset_game_state(): | |
st.session_state.game_state = { | |
"player_pos": [0, 0], | |
"goal_pos": [7, 7], | |
"obstacles": [[2, 2], [3, 4], [5, 3], [4, 6], [6, 5]], | |
"stars": [[1, 1], [3, 3], [5, 5], [7, 1]], | |
"score": 0, | |
"moves": 0, | |
"game_over": False, | |
"portals": { | |
"A": {"in": [6, 6], "out": [1, 7]}, | |
"B": {"in": [0, 3], "out": [7, 3]} | |
} | |
} | |
# Handle player movement | |
def move_player(direction): | |
"""Update player position based on movement direction""" | |
state = st.session_state.game_state | |
player_pos = state["player_pos"] | |
goal_pos = state["goal_pos"] | |
obstacles = state["obstacles"] | |
stars = state["stars"] | |
portals = state["portals"] | |
new_pos = player_pos.copy() | |
if direction == "up" and player_pos[0] > 0: | |
new_pos[0] -= 1 | |
elif direction == "down" and player_pos[0] < 7: | |
new_pos[0] += 1 | |
elif direction == "left" and player_pos[1] > 0: | |
new_pos[1] -= 1 | |
elif direction == "right" and player_pos[1] < 7: | |
new_pos[1] += 1 | |
# Check if new position is valid | |
if new_pos != player_pos: | |
# Check for obstacle collision | |
if new_pos not in obstacles: | |
state["player_pos"] = new_pos | |
state["moves"] += 1 | |
# Check for portal | |
for portal, positions in portals.items(): | |
if new_pos == positions["in"]: | |
state["player_pos"] = positions["out"].copy() | |
state["moves"] += 1 # Count portal as a move | |
break | |
# Check for goal collision | |
if new_pos == goal_pos: | |
state["score"] += 20 | |
state["game_over"] = True | |
st.session_state.toast_message = "๐ You reached the goal! Well done!" | |
# Check for star collection | |
elif new_pos in stars: | |
state["score"] += 5 | |
state["stars"].remove(new_pos) | |
st.session_state.toast_message = "โญ You collected a star! +5 points" | |
st.session_state.game_state = state | |
# Create a playable game in the browser | |
def create_playable_game(): | |
"""Create an interactive grid-based game""" | |
st.subheader("๐ฎ Play Your Game Now!") | |
state = st.session_state.game_state | |
player_pos = state["player_pos"] | |
goal_pos = state["goal_pos"] | |
obstacles = state["obstacles"] | |
stars = state["stars"] | |
portals = state["portals"] | |
# Level indicator | |
st.markdown(f""" | |
<div class="level-indicator"> | |
Level: {st.session_state.current_level}/{st.session_state.total_levels} | |
</div> | |
""", unsafe_allow_html=True) | |
# Score board | |
st.markdown(f""" | |
<div class="score-board"> | |
Stars Collected: {state["score"]} | Moves: {state["moves"]} | |
</div> | |
""", unsafe_allow_html=True) | |
# Game board | |
st.markdown("<div class='game-board'>", unsafe_allow_html=True) | |
# Create grid cells | |
for row in range(8): | |
cols = st.columns(8) | |
for col in range(8): | |
pos = [row, col] | |
cell_class = "cell" | |
cell_content = "" | |
if pos == player_pos: | |
cell_class += " player" | |
cell_content = st.session_state.player_char | |
elif pos == goal_pos: | |
cell_class += " goal" | |
cell_content = st.session_state.goal_char | |
elif pos in obstacles: | |
cell_class += " obstacle" | |
cell_content = st.session_state.obstacle_char | |
elif pos in stars: | |
cell_class += " star" | |
cell_content = "โญ" | |
else: | |
# Check for portals | |
for portal, positions in portals.items(): | |
if pos == positions["in"] or pos == positions["out"]: | |
cell_class += " portal" | |
cell_content = "๐" | |
break | |
with cols[col]: | |
st.markdown(f"<div class='{cell_class}'>{cell_content}</div>", unsafe_allow_html=True) | |
st.markdown("</div>", unsafe_allow_html=True) | |
# Game controls | |
st.markdown("<div class='game-controls'>", unsafe_allow_html=True) | |
col1, col2, col3, col4 = st.columns(4) | |
with col1: | |
st.button("โ", key="up", on_click=move_player, args=("up",), use_container_width=True) | |
with col2: | |
st.button("โ", key="left", on_click=move_player, args=("left",), use_container_width=True) | |
with col3: | |
st.button("โ", key="down", on_click=move_player, args=("down",), use_container_width=True) | |
with col4: | |
st.button("โ", key="right", on_click=move_player, args=("right",), use_container_width=True) | |
st.markdown("</div>", unsafe_allow_html=True) | |
# Reset button | |
st.button("๐ Reset Game", on_click=reset_game_state, use_container_width=True) | |
# Next level button | |
if state["game_over"]: | |
if st.session_state.current_level < st.session_state.total_levels: | |
if st.button("Next Level โ", use_container_width=True): | |
st.session_state.current_level += 1 | |
reset_game_state() | |
st.rerun() | |
# Theme customizer section | |
def theme_customizer(): | |
"""Theme customization section""" | |
with st.container(): | |
st.markdown("<div class='theme-customizer'>", unsafe_allow_html=True) | |
st.markdown("<div class='customizer-title'>๐จ Customize Your Game Theme</div>", unsafe_allow_html=True) | |
col1, col2, col3 = st.columns(3) | |
with col1: | |
st.session_state.player_char = st.selectbox( | |
"Player Character", | |
["๐ฆธ", "๐จโ๐", "๐งโโ๏ธ", "๐ฑ", "๐", "๐ฆ"], | |
index=0 | |
) | |
st.markdown(f"<div class='char-preview'>{st.session_state.player_char}</div>", unsafe_allow_html=True) | |
with col2: | |
st.session_state.goal_char = st.selectbox( | |
"Goal Character", | |
["๐", "๐ฐ", "๐ฉ", "๐ฏ", "๐"], | |
index=0 | |
) | |
st.markdown(f"<div class='char-preview'>{st.session_state.goal_char}</div>", unsafe_allow_html=True) | |
with col3: | |
st.session_state.obstacle_char = st.selectbox( | |
"Obstacle Character", | |
["๐ชจ", "๐ต", "๐ฅ", "๐", "๐ณ"], | |
index=0 | |
) | |
st.markdown(f"<div class='char-preview'>{st.session_state.obstacle_char}</div>", unsafe_allow_html=True) | |
# Level selector with visible options | |
st.session_state.current_level = st.select_slider( | |
"Select Difficulty Level", | |
options=[1, 2, 3, 4, 5], | |
value=st.session_state.current_level | |
) | |
st.markdown("</div>", unsafe_allow_html=True) | |
# Show toast message | |
def show_toast(): | |
"""Show a toast message if one exists""" | |
if st.session_state.toast_message: | |
st.markdown(f"<div class='toast'>{st.session_state.toast_message}</div>", unsafe_allow_html=True) | |
# Clear message after showing | |
st.session_state.toast_message = None | |
# Main application function | |
def main(): | |
init_session_state() | |
st.title("๐ฎ StoryCoder - Play & Learn Coding!") | |
st.markdown("Turn stories into games, and games into coding skills!", | |
unsafe_allow_html=True) | |
# Theme customizer | |
theme_customizer() | |
# 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, | |
disabled=not st.session_state.story or st.session_state.loading): | |
st.session_state.active_tab = "game" | |
with col3: | |
if st.button("๐ Concepts", use_container_width=True, | |
disabled=not st.session_state.concepts or st.session_state.loading): | |
st.session_state.active_tab = "concepts" | |
with col4: | |
if st.button("๐ป Game Code", use_container_width=True, | |
disabled=not st.session_state.game_code or st.session_state.loading): | |
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.active_tab = "story" | |
reset_game_state() | |
st.session_state.toast_message = "โจ New story started! Create your adventure." | |
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") | |
# Show examples | |
st.subheader("โจ Story Examples") | |
col1, col2, col3 = st.columns(3) | |
examples = { | |
"Space Explorer": "An astronaut needs to collect 3 stars while avoiding asteroids in space.", | |
"Jungle Adventure": "A monkey swings through trees to collect bananas before sunset.", | |
"Dragon Quest": "A dragon flies through clouds to collect magic crystals in a mystical land." | |
} | |
with col1: | |
with st.container(): | |
st.markdown('<div class="example-box">', unsafe_allow_html=True) | |
st.subheader("๐ Space Explorer") | |
st.code(examples["Space Explorer"], language="text") | |
if st.button("Copy Story", key="copy_space", use_container_width=True): | |
st.session_state.story = examples["Space Explorer"] | |
st.session_state.toast_message = "๐ Space story copied to clipboard!" | |
st.markdown('</div>', unsafe_allow_html=True) | |
with col2: | |
with st.container(): | |
st.markdown('<div class="example-box">', unsafe_allow_html=True) | |
st.subheader("๐ฟ Jungle Adventure") | |
st.code(examples["Jungle Adventure"], language="text") | |
if st.button("Copy Story", key="copy_jungle", use_container_width=True): | |
st.session_state.story = examples["Jungle Adventure"] | |
st.session_state.toast_message = "๐ฟ Jungle story copied to clipboard!" | |
st.markdown('</div>', unsafe_allow_html=True) | |
with col3: | |
with st.container(): | |
st.markdown('<div class="example-box">', unsafe_allow_html=True) | |
st.subheader("๐ Dragon Quest") | |
st.code(examples["Dragon Quest"], language="text") | |
if st.button("Copy Story", key="copy_dragon", use_container_width=True): | |
st.session_state.story = examples["Dragon Quest"] | |
st.session_state.toast_message = "๐ Dragon story copied to clipboard!" | |
st.markdown('</div>', unsafe_allow_html=True) | |
st.write("Write a short story (2-5 sentences) and I'll turn it into a playable game!") | |
story = st.text_area( | |
"Your story:", | |
height=150, | |
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 game scenario..."): | |
st.session_state.game_scenario = generate_game_scenario( | |
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 | |
) | |
# Generate simple game code | |
keywords = re.findall(r'\b\w{4,}\b', st.session_state.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 st.session_state.concepts]) | |
game_code = f""" | |
# {player_char}'s Adventure: {st.session_state.story[:20]}... | |
# Teaches: {concept_emojis} {", ".join([CONCEPTS[c]['name'] for c in st.session_state.concepts])} | |
import pygame | |
import random | |
import sys | |
# Game setup and code would go here... | |
# This is a simplified representation of the actual game code | |
""" | |
st.session_state.game_code = game_code | |
reset_game_state() | |
st.session_state.active_tab = "game" | |
st.session_state.loading = False | |
st.session_state.toast_message = "๐ฎ Game created successfully! Time to play." | |
st.rerun() | |
# 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) | |
# 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) | |
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: | |
# Concept buttons instead of dropdown | |
st.markdown("<div class='concept-buttons'>", unsafe_allow_html=True) | |
for concept in st.session_state.concepts: | |
details = CONCEPTS[concept] | |
is_active = st.session_state.selected_concept == concept | |
btn_class = "concept-btn active" if is_active else "concept-btn" | |
if st.button( | |
f"{details['emoji']} {details['name']}", | |
key=f"concept_{concept}", | |
use_container_width=True | |
): | |
st.session_state.selected_concept = concept if not is_active else None | |
st.markdown("</div>", unsafe_allow_html=True) | |
# Show explanation for selected concept | |
if st.session_state.selected_concept: | |
details = CONCEPTS[st.session_state.selected_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) | |
else: | |
st.info("๐ Click on a concept button above to learn more about it") | |
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 | |
) | |
# Code explanation | |
st.subheader("๐ง Code Explanation") | |
st.markdown(f""" | |
<div class="game-card"> | |
<p>This code creates a game based on your story: <em>{st.session_state.story[:50]}...</em></p> | |
<p>Key programming concepts used:</p> | |
<ul> | |
<li><strong>Variables:</strong> Used to track score and positions</li> | |
<li><strong>Conditionals:</strong> Check collisions and game rules</li> | |
<li><strong>Functions:</strong> Organize game mechanics into reusable blocks</li> | |
<li><strong>Loops:</strong> The game loop runs continuously</li> | |
<li><strong>Lists:</strong> Store obstacles and collectible stars</li> | |
</ul> | |
<p>To run this game, you'll need to install PyGame and Python on your computer.</p> | |
</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() | |
# Show toast messages | |
show_toast() | |
if __name__ == "__main__": | |
main() |