Spaces:
Sleeping
Sleeping
import streamlit as st | |
import random | |
import re | |
from gtts import gTTS | |
from PIL import Image, ImageDraw, ImageFont | |
import io | |
import base64 | |
# Default word lists for storytelling classes | |
default_word_lists = { | |
"Location": ["quiet town", "small village", "city", "forest", "mountain"], | |
"Actions": ["walking", "pedaling", "running", "dancing", "exploring"], | |
"Thoughts": ["chasing shadows", "what if", "brilliance of years", "echoes", "secrets"], | |
"Emotions": ["joy", "pain", "trembling smile", "storm", "silent art"], | |
"Dialogue": ["\"Keep moving, dare to feel;\"", "\"Am I chasing shadows?\"", "\"The dawn awaits!\"", "\"I love you.\"", "\"Letโs go!\""] | |
} | |
# Suit properties for narrative flavor | |
suit_properties = { | |
"Hearts": "emotional or romantic", | |
"Diamonds": "wealthy or luxurious", | |
"Clubs": "conflict or struggle", | |
"Spades": "mysterious or dangerous" | |
} | |
# Sentence templates for story generation | |
sentence_templates = { | |
"Location": "The story unfolded in a {property} {word}.", | |
"Actions": "Suddenly, a {property} {word} changed everything.", | |
"Thoughts": "A {property} thought, '{word}', crossed their mind.", | |
"Emotions": "A {property} wave of {word} surged through them.", | |
"Dialogue": "Someone spoke with a {property} tone: {word}" | |
} | |
# Pure Python function to augment word lists from user input | |
def augment_word_lists(user_input): | |
augmented_lists = {key: list(set(val)) for key, val in default_word_lists.items()} | |
# Split input into words | |
words = user_input.lower().split() | |
# Simple heuristic lists for categorization | |
location_keywords = ["town", "village", "city", "forest", "mountain", "place", "land"] | |
action_keywords = ["walk", "run", "dance", "pedal", "explore", "move", "jump"] | |
emotion_keywords = ["joy", "pain", "smile", "storm", "fear", "love", "anger"] | |
# Extract dialogues with regex | |
dialogues = re.findall(r'"[^"]*"', user_input) | |
augmented_lists["Dialogue"].extend(dialogues) | |
# Categorize words | |
for word in words: | |
if any(keyword in word for keyword in location_keywords): | |
augmented_lists["Location"].append(word) | |
elif any(keyword in word for keyword in action_keywords): | |
augmented_lists["Actions"].append(word) | |
elif any(keyword in word for keyword in emotion_keywords): | |
augmented_lists["Emotions"].append(word) | |
elif "?" in word or "what" in word or "why" in word: # Simple thought detection | |
augmented_lists["Thoughts"].append(word) | |
# Remove duplicates | |
for key in augmented_lists: | |
augmented_lists[key] = list(set(augmented_lists[key])) | |
return augmented_lists | |
# Create a 52-card deck | |
def create_deck(): | |
suits = ["Hearts", "Diamonds", "Clubs", "Spades"] | |
ranks = list(range(1, 14)) | |
deck = [(suit, rank) for suit in suits for rank in ranks] | |
random.shuffle(deck) | |
return deck | |
# Assign cards to classes | |
def assign_card_to_class(card_index): | |
if 0 <= card_index < 10: | |
return "Location" | |
elif 10 <= card_index < 20: | |
return "Actions" | |
elif 20 <= card_index < 30: | |
return "Thoughts" | |
elif 30 <= card_index < 40: | |
return "Emotions" | |
else: | |
return "Dialogue" | |
# Generate card image | |
def generate_card_image(suit, rank, story_class, word, property): | |
img = Image.new("RGB", (200, 300), color="white") | |
draw = ImageDraw.Draw(img) | |
font = ImageFont.load_default() | |
draw.text((10, 10), f"{rank} of {suit}", fill="black", font=font) | |
draw.text((10, 50), f"Class: {story_class}", fill="black", font=font) | |
draw.text((10, 90), f"Word: {word}", fill="black", font=font) | |
draw.text((10, 130), f"Property: {property}", fill="black", font=font) | |
buffer = io.BytesIO() | |
img.save(buffer, format="PNG") | |
return buffer.getvalue() | |
# Generate story sentence | |
def generate_story_sentence(story_class, word, property): | |
return sentence_templates[story_class].format(word=word, property=property) | |
# Generate song lyrics | |
def generate_song_lyrics(story_text): | |
words = story_text.split() | |
key_elements = [word for word in words if len(word) > 3][:12] # Simple filter for ~60 seconds | |
lyrics = "\n".join([f"{key_elements[i]} {key_elements[i+1]}" for i in range(0, len(key_elements)-1, 2)]) | |
return lyrics | |
# Main app | |
def main(): | |
st.set_page_config(page_title="StoryForge: The Game", page_icon="๐ด", layout="wide") | |
st.title("๐ด StoryForge: A Storytelling Adventure ๐ด") | |
# User input | |
st.markdown("## ๐ Your Story Seed") | |
user_input = st.text_area("Paste your story inspiration here:", height=200) | |
# Session state initialization | |
if "augmented_lists" not in st.session_state: | |
st.session_state.augmented_lists = default_word_lists | |
if "deck" not in st.session_state: | |
st.session_state.deck = create_deck() | |
if "story" not in st.session_state: | |
st.session_state.story = [] | |
if "drawn_cards" not in st.session_state: | |
st.session_state.drawn_cards = 0 | |
# Process input | |
if st.button("Start Game"): | |
if user_input: | |
st.session_state.augmented_lists = augment_word_lists(user_input) | |
st.session_state.deck = create_deck() | |
st.session_state.story = [] | |
st.session_state.drawn_cards = 0 | |
st.success("Game started! Draw your first card.") | |
# Draw card and story display | |
col1, col2 = st.columns([1, 3]) | |
with col1: | |
if st.button("Draw Card") and st.session_state.drawn_cards < 52: | |
card_index = st.session_state.drawn_cards | |
suit, rank = st.session_state.deck[card_index] | |
story_class = assign_card_to_class(card_index) | |
word = random.choice(st.session_state.augmented_lists[story_class]) | |
property = suit_properties[suit] | |
card_image = generate_card_image(suit, rank, story_class, word, property) | |
st.image(card_image, caption=f"Card {card_index + 1}") | |
sentence = generate_story_sentence(story_class, word, property) | |
st.session_state.story.append(sentence) | |
st.session_state.drawn_cards += 1 | |
with col2: | |
st.markdown("### ๐ Your Story Unfolds") | |
if st.session_state.story: | |
st.write("\n".join(st.session_state.story)) | |
# Song generation | |
if st.session_state.drawn_cards == 52: | |
full_story = "\n".join(st.session_state.story) | |
st.markdown("### ๐ต Story Song") | |
lyrics = generate_song_lyrics(full_story) | |
st.write(lyrics) | |
tts = gTTS(text=lyrics, lang="en") | |
audio_file = "story_song.mp3" | |
tts.save(audio_file) | |
st.audio(audio_file, format="audio/mp3") | |
# Enhanced UI with HTML/CSS | |
st.markdown(""" | |
## ๐ฎ Game Board | |
<div style="background-color: #f0f0f0; padding: 20px; border-radius: 10px; text-align: center;"> | |
<h3 style="color: #333;">Draw cards to weave your epic tale!</h3> | |
<button style="background-color: #ff9900; color: white; border: none; padding: 10px 20px; border-radius: 5px; cursor: pointer;" | |
onmouseover="this.style.backgroundColor='#cc7700'" | |
onmouseout="this.style.backgroundColor='#ff9900'"> | |
Hover for a surprise! | |
</button> | |
</div> | |
""", unsafe_allow_html=True) | |
# Star layout | |
st.markdown("## โญ Five Pillars of Storytelling โญ") | |
star_html = """ | |
<div style="position: relative; width: 400px; height: 400px; margin: auto; border: 1px dashed #aaa; border-radius: 50%;"> | |
<div style="position: absolute; top: 50px; left: 200px; transform: translate(-50%, -50%); text-align: center;"> | |
<div style="font-size: 2em;">๐ </div><div><b>Location</b></div> | |
</div> | |
<div style="position: absolute; top: 154px; left: 57px; transform: translate(-50%, -50%); text-align: center;"> | |
<div style="font-size: 2em;">๐</div><div><b>Actions</b></div> | |
</div> | |
<div style="position: absolute; top: 321px; left: 112px; transform: translate(-50%, -50%); text-align: center;"> | |
<div style="font-size: 2em;">๐ง </div><div><b>Thoughts</b></div> | |
</div> | |
<div style="position: absolute; top: 321px; left: 288px; transform: translate(-50%, -50%); text-align: center;"> | |
<div style="font-size: 2em;">๐ฒ</div><div><b>Emotions</b></div> | |
</div> | |
<div style="position: absolute; top: 154px; left: 343px; transform: translate(-50%, -50%); text-align: center;"> | |
<div style="font-size: 2em;">๐ฌ</div><div><b>Dialogue</b></div> | |
</div> | |
</div> | |
""" | |
st.markdown(star_html, unsafe_allow_html=True) | |
if __name__ == "__main__": | |
main() |