awacke1's picture
Update app.py
9d12910 verified
raw
history blame
15.4 kB
import streamlit as st
from streamlit.components.v1 import html
import random
import json
import math
# Shared state for multiplayer
if 'game_state' not in st.session_state:
st.session_state.game_state = {
'hex_grid': [[{'type': 'empty', 'emoji': ''} for _ in range(15)] for _ in range(20)], # 20x15 hex grid
'buildings': [],
'players': {},
'train': {'x': 0, 'y': 5 * 40, 'dir': 1},
'monster_mode': False,
'current_monster': 'Godzilla',
'monster_x': 400,
'monster_y': 300
}
# Random player names
random_names = [
"SkyWalker", "ForestRanger", "CityBuilder", "MonsterTamer", "RailMaster",
"PlantWhisperer", "UrbanWizard", "NatureSage", "GridLord", "EcoWarrior"
]
# Story plot generator beginnings
story_beginnings = [
("๐Ÿ ", "In a bustling city nestled between towering mountains,"),
("๐Ÿƒ", "A lone figure darted through the streets as the sun began to set,"),
("๐Ÿง ", "Thoughts raced through their mind, planning the next move,"),
("๐Ÿ˜ฒ", "Shock rippled through the crowd as the ground trembled,"),
("๐Ÿ’ฌ", "'We need to act now!' someone shouted over the chaos,"),
("๐Ÿ”", "A closer look revealed hidden secrets in the cityโ€™s core,"),
("๐ŸŽฌ", "A train screeched into the station, unloading its mysteries,"),
("๐Ÿž๏ธ", "Lush greenery framed the skyline, a stark contrast to the urban sprawl,"),
("โฉ", "Events unfolded rapidly, leaving no time to breathe,"),
("๐Ÿ‘๏ธ", "The horizon glowed with an eerie, captivating light,"),
("๐Ÿ‘‚", "Whispers of wind carried tales of forgotten lands,"),
("๐Ÿ—ฃ๏ธ", "A juicy rumor spread like wildfire among the citizens,"),
("๐Ÿคธ", "With a simple leap, the adventure began anew,"),
("๐Ÿงฉ", "The essentials of survival lay scattered across the grid,"),
("๐Ÿšถ", "A slow walk revealed the cityโ€™s heartbeat,"),
("๐ŸŒŠ", "A journey unfolded, waves of change crashing in,"),
("๐ŸŽฏ", "Focus sharpened as the goal came into view,"),
("๐Ÿค”", "Neurotic thoughts plagued the plannerโ€™s restless mind,")
]
# Witty nature labels
nature_labels = [
"Whispering Winds", "Sassy Sunlight", "Grumpy Gravel", "Chatty Clouds",
"Pensive Pines", "Witty Wetlands", "Sneaky Streams", "Bouncy Breezes",
"Daring Dewdrops", "Cheeky Crags"
]
# Emoji palette
plants = ["๐ŸŒฑ", "๐ŸŒฒ", "๐ŸŒณ", "๐ŸŒด", "๐ŸŒต", "๐ŸŒพ", "๐ŸŒฟ", "๐Ÿ€", "๐Ÿ", "๐Ÿ‚"]
buildings = ["๐Ÿ ", "๐Ÿก", "๐Ÿข", "๐Ÿฃ", "๐Ÿค", "๐Ÿฅ", "๐Ÿฆ", "๐Ÿจ", "๐Ÿฉ", "๐Ÿช"]
creatures = ["๐Ÿพ", "๐Ÿฑ", "๐Ÿถ", "๐Ÿญ", "๐Ÿฐ", "๐ŸฆŠ", "๐Ÿป", "๐Ÿท", "๐Ÿฎ", "๐Ÿธ"]
# p5.js code for hex grid and gameplay
p5js_code = """
let hexSize = 40;
let hexGrid = [];
let buildings = [];
let train = { x: 0, y: 0, dir: 1 };
let monsterMode = false;
let currentMonster = 'Godzilla';
let monsterX, monsterY;
let angle = PI / 6;
let debugText = "Loading...";
let playerId = null;
const sqrt3 = Math.sqrt(3);
function setup() {
createCanvas(800, 600);
hexGrid = JSON.parse(document.getElementById('game_state').innerHTML);
buildings = JSON.parse(document.getElementById('buildings').innerHTML);
train = JSON.parse(document.getElementById('train').innerHTML);
monsterMode = JSON.parse(document.getElementById('monster_mode').innerHTML);
currentMonster = document.getElementById('current_monster').innerHTML;
monsterX = parseInt(document.getElementById('monster_x').innerHTML);
monsterY = parseInt(document.getElementById('monster_y').innerHTML);
playerId = document.getElementById('player_id').innerHTML;
debugText = `Player ${playerId} joined. Click to build!`;
// Set train track on row 5
for (let i = 0; i < hexGrid.length; i++) {
hexGrid[i][5].type = 'track';
}
}
function draw() {
background(220);
drawHexGrid();
drawBuildings();
drawTrain();
if (monsterMode) drawMonster();
fill(0);
textSize(12);
text(debugText, 10, 20);
}
function drawHexGrid() {
for (let i = 0; i < hexGrid.length; i++) {
for (let j = 0; j < hexGrid[i].length; j++) {
let x = i * hexSize * 1.5;
let y = j * hexSize * sqrt3 + (i % 2 === 1 ? hexSize * sqrt3 / 2 : 0);
let z = j * hexSize * sin(angle);
fill(hexGrid[i][j].type === 'track' ? 100 : 150, 200, 150);
stroke(0);
beginShape();
for (let k = 0; k < 6; k++) {
let angleRad = PI / 3 * k;
vertex(x + hexSize * cos(angleRad), y + hexSize * sin(angleRad) - z);
}
endShape(CLOSE);
if (hexGrid[i][j].emoji) {
textSize(20);
text(hexGrid[i][j].emoji, x - 10, y + 5 - z);
}
}
}
}
function drawBuildings() {
buildings.forEach(b => {
let x = b.x * hexSize * 1.5;
let y = b.y * hexSize * sqrt3 + (b.x % 2 === 1 ? hexSize * sqrt3 / 2 : 0);
let z = b.y * hexSize * sin(angle);
fill(b.color);
noStroke();
beginShape();
if (b.type === 'Residential') {
vertex(x + 10, y + 30 - z); vertex(x + 20, y + 10 - z); vertex(x + 30, y + 30 - z);
vertex(x + 25, y + 30 - z); vertex(x + 25, y + 35 - z); vertex(x + 15, y + 35 - z);
vertex(x + 15, y + 30 - z); vertex(x + 17, y + 25 - z); vertex(x + 23, y + 25 - z);
} else if (b.type === 'Commercial') {
vertex(x + 10, y + 35 - z); vertex(x + 15, y + 15 - z); vertex(x + 25, y + 15 - z);
vertex(x + 30, y + 35 - z); vertex(x + 27, y + 35 - z); vertex(x + 27, y + 20 - z);
vertex(x + 13, y + 20 - z); vertex(x + 13, y + 35 - z);
} else if (b.type === 'Industrial') {
vertex(x + 5, y + 35 - z); vertex(x + 15, y + 20 - z); vertex(x + 25, y + 20 - z);
vertex(x + 35, y + 35 - z); vertex(x + 30, y + 35 - z); vertex(x + 30, y + 15 - z);
vertex(x + 33, y + 15 - z); vertex(x + 33, y + 35 - z);
} else if (b.type === 'School') {
vertex(x + 5, y + 35 - z); vertex(x + 10, y + 20 - z); vertex(x + 30, y + 20 - z);
vertex(x + 35, y + 35 - z); vertex(x + 25, y + 35 - z); vertex(x + 25, y + 10 - z);
vertex(x + 27, y + 10 - z); vertex(x + 27, y + 35 - z);
} else if (b.type === 'PowerPlant') {
vertex(x + 5, y + 35 - z); vertex(x + 15, y + 15 - z); vertex(x + 25, y + 15 - z);
vertex(x + 35, y + 35 - z); vertex(x + 30, y + 35 - z); vertex(x + 30, y + 25 - z);
vertex(x + 20, y + 25 - z); vertex(x + 20, y + 35 - z);
}
endShape(CLOSE);
});
}
function drawTrain() {
let tx = train.x;
let ty = train.y;
let tz = train.y * sin(angle);
fill(150, 50, 50);
noStroke();
beginShape();
vertex(tx + 10, ty + 10 - tz); vertex(tx + 30, ty + 10 - tz); vertex(tx + 35, ty + 20 - tz);
vertex(tx + 30, ty + 30 - tz); vertex(tx + 10, ty + 30 - tz); vertex(tx + 5, ty + 20 - tz);
vertex(tx + 15, ty + 20 - tz); vertex(tx + 15, ty + 15 - tz); vertex(tx + 25, ty + 15 - tz);
vertex(tx + 25, ty + 20 - tz);
endShape(CLOSE);
train.x += train.dir * 2;
if (train.x > width || train.x < 0) train.dir *= -1;
}
function drawMonster() {
let mz = monsterY * sin(angle);
fill(255, 0, 0);
noStroke();
beginShape();
if (currentMonster === 'Godzilla') {
vertex(monsterX, monsterY + 40 - mz); vertex(monsterX + 10, monsterY + 20 - mz);
vertex(monsterX + 20, monsterY - mz); vertex(monsterX + 30, monsterY + 20 - mz);
vertex(monsterX + 40, monsterY + 40 - mz); vertex(monsterX + 35, monsterY + 50 - mz);
vertex(monsterX + 25, monsterY + 60 - mz); vertex(monsterX + 15, monsterY + 50 - mz);
vertex(monsterX + 20, monsterY + 40 - mz); vertex(monsterX + 25, monsterY + 30 - mz);
} else if (currentMonster === 'GiantRobot') {
vertex(monsterX, monsterY + 40 - mz); vertex(monsterX + 10, monsterY + 20 - mz);
vertex(monsterX + 15, monsterY - mz); vertex(monsterX + 25, monsterY - mz);
vertex(monsterX + 30, monsterY + 20 - mz); vertex(monsterX + 40, monsterY + 40 - mz);
vertex(monsterX + 35, monsterY + 50 - mz); vertex(monsterX + 20, monsterY + 60 - mz);
vertex(monsterX + 5, monsterY + 50 - mz); vertex(monsterX + 15, monsterY + 30 - mz);
}
endShape(CLOSE);
monsterX += random(-5, 5);
monsterY += random(-5, 5);
monsterX = constrain(monsterX, 0, width);
monsterY = constrain(monsterY, 0, height);
}
function mousePressed() {
let i = floor(mouseX / (hexSize * 1.5));
let j = floor((mouseY - (i % 2 === 1 ? hexSize * sqrt3 / 2 : 0)) / (hexSize * sqrt3));
if (i >= 0 && i < hexGrid.length && j >= 0 && j < hexGrid[0].length) {
if (hexGrid[i][j].type === 'empty' && !monsterMode) {
let selectedEmoji = document.getElementById('selected_emoji').innerHTML;
if (selectedEmoji.startsWith('๐Ÿ ') || selectedEmoji.startsWith('๐Ÿก') || selectedEmoji.startsWith('๐Ÿข') ||
selectedEmoji.startsWith('๐Ÿฃ') || selectedEmoji.startsWith('๐Ÿค') || selectedEmoji.startsWith('๐Ÿฅ') ||
selectedEmoji.startsWith('๐Ÿฆ') || selectedEmoji.startsWith('๐Ÿจ') || selectedEmoji.startsWith('๐Ÿฉ') ||
selectedEmoji.startsWith('๐Ÿช')) {
let types = ['Residential', 'Commercial', 'Industrial', 'School', 'PowerPlant'];
let colors = [[0, 200, 0], [0, 0, 200], [200, 200, 0], [200, 0, 200], [100, 100, 100]];
let idx = floor(random(5));
buildings.push({ x: i, y: j, type: types[idx], color: colors[idx], player: playerId });
hexGrid[i][j].type = 'building';
} else {
hexGrid[i][j].type = 'placed';
hexGrid[i][j].emoji = selectedEmoji;
}
debugText = `Player ${playerId} placed ${selectedEmoji} at (${i}, ${j})`;
updateState();
}
}
}
function keyPressed() {
if (key === 'm' || key === 'M') {
monsterMode = !monsterMode;
debugText = `Monster Mode: ${monsterMode}`;
updateState();
}
if (key === 'g' || key === 'G') {
currentMonster = 'Godzilla';
debugText = "Monster set to Godzilla";
updateState();
}
if (key === 'r' || key === 'R') {
currentMonster = 'GiantRobot';
debugText = "Monster set to Giant Robot";
updateState();
}
if (key === 't' || key === 'T') {
train.dir *= -1;
debugText = "Train direction reversed";
updateState();
}
}
function updateState() {
fetch('/update_state', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
hex_grid: hexGrid,
buildings: buildings,
train: train,
monster_mode: monsterMode,
current_monster: currentMonster,
monster_x: monsterX,
monster_y: monsterY,
player_id: playerId
})
});
}
"""
# Player selection
if 'player_id' not in st.session_state:
st.session_state.player_id = random.choice(random_names)
player_id = st.sidebar.selectbox("Choose Player", random_names, index=random_names.index(st.session_state.player_id))
st.session_state.player_id = player_id
# Story plot generator panel
story_html = f"""
<div style='background: #f9f9f9; padding: 10px; border-radius: 5px; margin-bottom: 10px;'>
<h3>๐Ÿ“œ Story Plot</h3>
<p>{random.choice(story_beginnings)[1]} {random.choice(nature_labels)} stirred the scene.</p>
</div>
"""
st.sidebar.markdown(story_html, unsafe_allow_html=True)
# Palette grid
st.sidebar.subheader("๐ŸŽจ Palette")
cols = st.sidebar.columns(5)
selected_emoji = st.session_state.get('selected_emoji', plants[0])
for i, emoji in enumerate(plants + buildings + creatures):
col = cols[i % 5]
if col.button(emoji, key=f"emoji_{i}"):
st.session_state.selected_emoji = emoji
selected_emoji = emoji
# Scoreboard
if player_id not in st.session_state.game_state['players']:
st.session_state.game_state['players'][player_id] = 0
for b in st.session_state.game_state['buildings']:
if b.get('player') == player_id:
st.session_state.game_state['players'][player_id] += 1
for i in range(len(st.session_state.game_state['hex_grid'])):
for j in range(len(st.session_state.game_state['hex_grid'][i])):
if st.session_state.game_state['hex_grid'][i][j]['type'] == 'placed':
st.session_state.game_state['players'][player_id] += 1
score_html = "<div style='background: #f9f9f9; padding: 10px; border-radius: 5px;'><h3>๐Ÿ† Scores</h3><ul>"
for p, s in st.session_state.game_state['players'].items():
score_html += f"<li>{p}: {s}</li>"
score_html += "</ul></div>"
st.sidebar.markdown(score_html, unsafe_allow_html=True)
# Main game panel
game_html = f"""
<!DOCTYPE html>
<html>
<head>
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.4.2/p5.min.js"></script>
<style>
body {{ font-family: Arial, sans-serif; margin: 0; padding: 0; background: #f0f0f0; }}
#controls {{ padding: 10px; background: #e0e0e0; }}
button {{ padding: 8px 16px; margin: 5px; background: #4CAF50; color: white; border: none; border-radius: 5px; cursor: pointer; }}
button:hover {{ background: #45a049; }}
select {{ padding: 8px; margin: 5px; border-radius: 5px; }}
</style>
</head>
<body>
<div id="controls">
<button onclick="toggleMonster()">Toggle Monster Mode (M)</button>
<select onchange="setMonster(this.value)">
<option value="Godzilla">Godzilla (G)</option>
<option value="GiantRobot">Giant Robot (R)</option>
</select>
<button onclick="reverseTrain()">Reverse Train (T)</button>
</div>
<div id="sketch-holder"></div>
<div id="game_state" style="display:none">{json.dumps(st.session_state.game_state['hex_grid'])}</div>
<div id="buildings" style="display:none">{json.dumps(st.session_state.game_state['buildings'])}</div>
<div id="train" style="display:none">{json.dumps(st.session_state.game_state['train'])}</div>
<div id="monster_mode" style="display:none">{json.dumps(st.session_state.game_state['monster_mode'])}</div>
<div id="current_monster" style="display:none">{st.session_state.game_state['current_monster']}</div>
<div id="monster_x" style="display:none">{st.session_state.game_state['monster_x']}</div>
<div id="monster_y" style="display:none">{st.session_state.game_state['monster_y']}</div>
<div id="player_id" style="display:none">{player_id}</div>
<div id="selected_emoji" style="display:none">{selected_emoji}</div>
<script>
{p5js_code}
function toggleMonster() {{ window.toggleMonsterMode(); }}
function setMonster(monster) {{ window.setMonster(monster); }}
function reverseTrain() {{ window.reverseTrain(); }}
</script>
</body>
</html>
"""
# Streamlit app layout
st.title("HexCity 2000 Multiplayer Adventure")
st.write(f"Player: {player_id}. Click to place {selected_emoji}. Use M/G/R/T keys.")
html(game_html, height=650)
# Update state function (mocked for Streamlit)
def update_state(data):
st.session_state.game_state['hex_grid'] = data['hex_grid']
st.session_state.game_state['buildings'] = data['buildings']
st.session_state.game_state['train'] = data['train']
st.session_state.game_state['monster_mode'] = data['monster_mode']
st.session_state.game_state['current_monster'] = data['current_monster']
st.session_state.game_state['monster_x'] = data['monster_x']
st.session_state.game_state['monster_y'] = data['monster_y']
if data['player_id'] not in st.session_state.game_state['players']:
st.session_state.game_state['players'][data['player_id']] = 0
st.session_state.game_state['players'][data['player_id']] += 1
st.rerun()
# Mock POST handler (for debugging)
if st.session_state.get('last_update'):
update_state(st.session_state['last_update'])