awacke1's picture
Update app.py
8e5d581 verified
raw
history blame
12.2 kB
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()}
words = user_input.lower().split()
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"]
dialogues = re.findall(r'"[^"]*"', user_input)
augmented_lists["Dialogue"].extend(dialogues)
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:
augmented_lists["Thoughts"].append(word)
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 visualization with p5.js
def generate_card_visualization(suit, rank, story_class, word, property):
# Parameterize animation based on card properties
num_balls = rank * 5 # More balls for higher rank
jelly_size = {"Hearts": 40, "Diamonds": 50, "Clubs": 30, "Spades": 60}[suit] # Suit affects jellyfish size
rotation_speed = {"Location": 0.005, "Actions": 0.01, "Thoughts": 0.003, "Emotions": 0.007, "Dialogue": 0.009}[story_class]
hue_base = {"Hearts": 0, "Diamonds": 120, "Clubs": 240, "Spades": 300}[suit] # Suit affects color
html_code = f"""
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.6.0/p5.min.js"></script>
<style>
body {{ margin: 0; padding: 0; overflow: hidden; background: black; }}
#p5-container {{ display: flex; justify-content: center; align-items: center; }}
</style>
</head>
<body>
<div id="p5-container"></div>
<script>
let balls = [];
let jellyfish;
const sphereRadius = 150;
let sphereCenter;
let rotationAngle = 0;
const numBalls = {num_balls};
function setup() {{
let canvas = createCanvas(400, 400);
canvas.parent('p5-container');
sphereCenter = createVector(width/2, height/2);
colorMode(HSB, 360, 100, 100, 1);
for (let i = 0; i < numBalls; i++) {{
balls.push(new Ball());
}}
jellyfish = new Jellyfish();
}}
function draw() {{
background(0, 0, 0, 0.1);
rotationAngle += {rotation_speed};
push();
translate(sphereCenter.x, sphereCenter.y);
rotate(rotationAngle);
noFill();
stroke(255);
strokeWeight(2);
ellipse(0, 0, sphereRadius * 2, sphereRadius * 2);
for (let ball of balls) {{
ball.update();
ball.checkBoundaryCollision();
ball.display();
}}
jellyfish.update();
jellyfish.checkBoundaryCollision();
jellyfish.display();
pop();
// Card info overlay
fill(255, 255, 255, 0.8);
noStroke();
rect(0, 0, width, 60);
fill(0);
textSize(16);
textAlign(CENTER);
text("{rank} of {suit} - {story_class}: {word} ({property})", width/2, 30);
}}
class Ball {{
constructor() {{
this.r = 5;
let angle = random(TWO_PI);
let rad = random(sphereRadius - this.r);
this.pos = createVector(rad * cos(angle), rad * sin(angle));
let speed = random(1, 3);
let vAngle = random(TWO_PI);
this.vel = createVector(speed * cos(vAngle), speed * vAngle);
this.col = color({hue_base}, 100, 100);
}}
update() {{ this.pos.add(this.vel); }}
checkBoundaryCollision() {{
let d = this.pos.mag();
if (d + this.r > sphereRadius) {{
let normal = this.pos.copy().normalize();
let dot = this.vel.dot(normal);
this.vel.sub(p5.Vector.mult(normal, 2 * dot));
this.pos = normal.mult(sphereRadius - this.r);
}}
}}
display() {{
noStroke();
fill(this.col);
ellipse(this.pos.x, this.pos.y, this.r * 2, this.r * 2);
}}
}}
class Jellyfish {{
constructor() {{
this.size = {jelly_size};
this.pos = createVector(random(-sphereRadius + this.size, sphereRadius - this.size),
random(-sphereRadius + this.size, sphereRadius - this.size));
let speed = random(1, 2);
let angle = random(TWO_PI);
this.vel = createVector(speed * cos(angle), speed * sin(angle));
this.t = 0;
}}
update() {{ this.pos.add(this.vel); this.t += 0.05; }}
checkBoundaryCollision() {{
if (this.pos.mag() + this.size > sphereRadius) {{
let normal = this.pos.copy().normalize();
let dot = this.vel.dot(normal);
this.vel.sub(p5.Vector.mult(normal, 2 * dot));
this.pos = normal.mult(sphereRadius - this.size);
}}
}}
display() {{
push();
translate(this.pos.x, this.pos.y);
strokeWeight(1.5);
for (let y = 99; y < 300; y += 4) {{
for (let x = 99; x < 300; x += 2) {{
let res = jellyA(x, y, this.t);
let px = res[0] - 200;
let py = res[1] - 200;
stroke(getJellyColor(x, y, this.t));
point(px, py);
}}
}}
pop();
}}
}}
function jellyA(x, y, t) {{
let k = x / 8 - 25;
let e = y / 8 - 25;
let d = (k * k + e * e) / 99;
let q = x / 3 + k * 0.5 / cos(y * 5) * sin(d * d - t);
let c = d / 2 - t / 8;
let xPos = q * sin(c) + e * sin(d + k - t) + 200;
let yPos = (q + y / 8 + d * 9) * cos(c) + 200;
return [xPos, yPos];
}}
function getJellyColor(x, y, t) {{
let hue = (sin(t / 2) * 360 + x / 3 + y / 3) % 360;
let saturation = 70 + sin(t) * 30;
let brightness = 50 + cos(t / 2) * 20;
return color(hue, saturation, brightness, 0.5);
}}
</script>
</body>
</html>
"""
return html_code
# 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]
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 Animated Game", page_icon="🎴", layout="wide")
st.title("🎴 StoryForge: An Animated 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 visualization
col1, col2 = st.columns([2, 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]
# Generate and display animated visualization
viz_html = generate_card_visualization(suit, rank, story_class, word, property)
st.components.v1.html(viz_html, height=420, scrolling=False)
# Generate story sentence
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")
if __name__ == "__main__":
main()