Update app.py
Browse files
app.py
CHANGED
@@ -2,11 +2,12 @@ import streamlit as st
|
|
2 |
from streamlit.components.v1 import html
|
3 |
import random
|
4 |
import json
|
|
|
5 |
|
6 |
# Shared state for multiplayer
|
7 |
if 'game_state' not in st.session_state:
|
8 |
st.session_state.game_state = {
|
9 |
-
'
|
10 |
'buildings': [],
|
11 |
'players': {},
|
12 |
'train': {'x': 0, 'y': 5 * 40, 'dir': 1},
|
@@ -56,10 +57,10 @@ plants = ["๐ฑ", "๐ฒ", "๐ณ", "๐ด", "๐ต", "๐พ", "๐ฟ", "๐", "๐"
|
|
56 |
buildings = ["๐ ", "๐ก", "๐ข", "๐ฃ", "๐ค", "๐ฅ", "๐ฆ", "๐จ", "๐ฉ", "๐ช"]
|
57 |
creatures = ["๐พ", "๐ฑ", "๐ถ", "๐ญ", "๐ฐ", "๐ฆ", "๐ป", "๐ท", "๐ฎ", "๐ธ"]
|
58 |
|
59 |
-
# p5.js code
|
60 |
p5js_code = """
|
61 |
-
let
|
62 |
-
let
|
63 |
let buildings = [];
|
64 |
let train = { x: 0, y: 0, dir: 1 };
|
65 |
let monsterMode = false;
|
@@ -68,10 +69,11 @@ let monsterX, monsterY;
|
|
68 |
let angle = PI / 6;
|
69 |
let debugText = "Loading...";
|
70 |
let playerId = null;
|
|
|
71 |
|
72 |
function setup() {
|
73 |
createCanvas(800, 600);
|
74 |
-
|
75 |
buildings = JSON.parse(document.getElementById('buildings').innerHTML);
|
76 |
train = JSON.parse(document.getElementById('train').innerHTML);
|
77 |
monsterMode = JSON.parse(document.getElementById('monster_mode').innerHTML);
|
@@ -80,11 +82,15 @@ function setup() {
|
|
80 |
monsterY = parseInt(document.getElementById('monster_y').innerHTML);
|
81 |
playerId = document.getElementById('player_id').innerHTML;
|
82 |
debugText = `Player ${playerId} joined. Click to build!`;
|
|
|
|
|
|
|
|
|
83 |
}
|
84 |
|
85 |
function draw() {
|
86 |
background(220);
|
87 |
-
|
88 |
drawBuildings();
|
89 |
drawTrain();
|
90 |
if (monsterMode) drawMonster();
|
@@ -93,23 +99,23 @@ function draw() {
|
|
93 |
text(debugText, 10, 20);
|
94 |
}
|
95 |
|
96 |
-
function
|
97 |
-
for (let i = 0; i <
|
98 |
-
for (let j = 0; j <
|
99 |
-
let x = i *
|
100 |
-
let y = j *
|
101 |
-
let z = j *
|
102 |
-
fill(
|
103 |
stroke(0);
|
104 |
beginShape();
|
105 |
-
|
106 |
-
|
107 |
-
|
108 |
-
|
109 |
endShape(CLOSE);
|
110 |
-
if (
|
111 |
textSize(20);
|
112 |
-
text(
|
113 |
}
|
114 |
}
|
115 |
}
|
@@ -117,9 +123,9 @@ function drawGrid() {
|
|
117 |
|
118 |
function drawBuildings() {
|
119 |
buildings.forEach(b => {
|
120 |
-
let x = b.x *
|
121 |
-
let y = b.y *
|
122 |
-
let z = b.y *
|
123 |
fill(b.color);
|
124 |
noStroke();
|
125 |
beginShape();
|
@@ -190,10 +196,10 @@ function drawMonster() {
|
|
190 |
}
|
191 |
|
192 |
function mousePressed() {
|
193 |
-
let i = floor(mouseX /
|
194 |
-
let j = floor(mouseY /
|
195 |
-
if (i >= 0 && i <
|
196 |
-
if (
|
197 |
let selectedEmoji = document.getElementById('selected_emoji').innerHTML;
|
198 |
if (selectedEmoji.startsWith('๐ ') || selectedEmoji.startsWith('๐ก') || selectedEmoji.startsWith('๐ข') ||
|
199 |
selectedEmoji.startsWith('๐ฃ') || selectedEmoji.startsWith('๐ค') || selectedEmoji.startsWith('๐ฅ') ||
|
@@ -202,10 +208,11 @@ function mousePressed() {
|
|
202 |
let types = ['Residential', 'Commercial', 'Industrial', 'School', 'PowerPlant'];
|
203 |
let colors = [[0, 200, 0], [0, 0, 200], [200, 200, 0], [200, 0, 200], [100, 100, 100]];
|
204 |
let idx = floor(random(5));
|
205 |
-
buildings.push({ x: i, y: j, type: types[idx], color: colors[idx] });
|
206 |
-
|
207 |
} else {
|
208 |
-
|
|
|
209 |
}
|
210 |
debugText = `Player ${playerId} placed ${selectedEmoji} at (${i}, ${j})`;
|
211 |
updateState();
|
@@ -241,7 +248,7 @@ function updateState() {
|
|
241 |
method: 'POST',
|
242 |
headers: { 'Content-Type': 'application/json' },
|
243 |
body: JSON.stringify({
|
244 |
-
|
245 |
buildings: buildings,
|
246 |
train: train,
|
247 |
monster_mode: monsterMode,
|
@@ -260,16 +267,19 @@ if 'player_id' not in st.session_state:
|
|
260 |
player_id = st.sidebar.selectbox("Choose Player", random_names, index=random_names.index(st.session_state.player_id))
|
261 |
st.session_state.player_id = player_id
|
262 |
|
263 |
-
# Story plot generator
|
264 |
-
|
265 |
-
|
266 |
-
|
|
|
|
|
|
|
|
|
267 |
|
268 |
# Palette grid
|
269 |
st.sidebar.subheader("๐จ Palette")
|
270 |
cols = st.sidebar.columns(5)
|
271 |
selected_emoji = st.session_state.get('selected_emoji', plants[0])
|
272 |
-
|
273 |
for i, emoji in enumerate(plants + buildings + creatures):
|
274 |
col = cols[i % 5]
|
275 |
if col.button(emoji, key=f"emoji_{i}"):
|
@@ -282,27 +292,28 @@ if player_id not in st.session_state.game_state['players']:
|
|
282 |
for b in st.session_state.game_state['buildings']:
|
283 |
if b.get('player') == player_id:
|
284 |
st.session_state.game_state['players'][player_id] += 1
|
285 |
-
for i in range(len(st.session_state.game_state['
|
286 |
-
for j in range(len(st.session_state.game_state['
|
287 |
-
if st.session_state.game_state['
|
288 |
-
|
289 |
-
|
290 |
-
st.sidebar.subheader("๐ Scores")
|
291 |
for p, s in st.session_state.game_state['players'].items():
|
292 |
-
|
|
|
|
|
293 |
|
294 |
-
#
|
295 |
-
|
296 |
<!DOCTYPE html>
|
297 |
<html>
|
298 |
<head>
|
299 |
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.4.2/p5.min.js"></script>
|
300 |
<style>
|
301 |
-
body {{ font-family: Arial, sans-serif; margin: 0; padding:
|
302 |
-
#controls {{
|
303 |
-
button {{ padding:
|
304 |
button:hover {{ background: #45a049; }}
|
305 |
-
select {{ padding:
|
306 |
</style>
|
307 |
</head>
|
308 |
<body>
|
@@ -315,7 +326,7 @@ html_content = f"""
|
|
315 |
<button onclick="reverseTrain()">Reverse Train (T)</button>
|
316 |
</div>
|
317 |
<div id="sketch-holder"></div>
|
318 |
-
<div id="game_state" style="display:none">{json.dumps(st.session_state.game_state['
|
319 |
<div id="buildings" style="display:none">{json.dumps(st.session_state.game_state['buildings'])}</div>
|
320 |
<div id="train" style="display:none">{json.dumps(st.session_state.game_state['train'])}</div>
|
321 |
<div id="monster_mode" style="display:none">{json.dumps(st.session_state.game_state['monster_mode'])}</div>
|
@@ -336,16 +347,14 @@ html_content = f"""
|
|
336 |
</html>
|
337 |
"""
|
338 |
|
339 |
-
# Streamlit app
|
340 |
-
st.title("
|
341 |
st.write(f"Player: {player_id}. Click to place {selected_emoji}. Use M/G/R/T keys.")
|
|
|
342 |
|
343 |
-
|
344 |
-
|
345 |
-
# Note: Streamlit doesn't natively support POST endpoints, so this is a placeholder for state update logic
|
346 |
-
# In a real app, you'd need a backend (e.g., Flask) to handle POST requests for true multiplayer sync
|
347 |
def update_state(data):
|
348 |
-
st.session_state.game_state['
|
349 |
st.session_state.game_state['buildings'] = data['buildings']
|
350 |
st.session_state.game_state['train'] = data['train']
|
351 |
st.session_state.game_state['monster_mode'] = data['monster_mode']
|
@@ -355,4 +364,8 @@ def update_state(data):
|
|
355 |
if data['player_id'] not in st.session_state.game_state['players']:
|
356 |
st.session_state.game_state['players'][data['player_id']] = 0
|
357 |
st.session_state.game_state['players'][data['player_id']] += 1
|
358 |
-
st.rerun()
|
|
|
|
|
|
|
|
|
|
2 |
from streamlit.components.v1 import html
|
3 |
import random
|
4 |
import json
|
5 |
+
import math
|
6 |
|
7 |
# Shared state for multiplayer
|
8 |
if 'game_state' not in st.session_state:
|
9 |
st.session_state.game_state = {
|
10 |
+
'hex_grid': [[{'type': 'empty', 'emoji': ''} for _ in range(15)] for _ in range(20)], # 20x15 hex grid
|
11 |
'buildings': [],
|
12 |
'players': {},
|
13 |
'train': {'x': 0, 'y': 5 * 40, 'dir': 1},
|
|
|
57 |
buildings = ["๐ ", "๐ก", "๐ข", "๐ฃ", "๐ค", "๐ฅ", "๐ฆ", "๐จ", "๐ฉ", "๐ช"]
|
58 |
creatures = ["๐พ", "๐ฑ", "๐ถ", "๐ญ", "๐ฐ", "๐ฆ", "๐ป", "๐ท", "๐ฎ", "๐ธ"]
|
59 |
|
60 |
+
# p5.js code for hex grid and gameplay
|
61 |
p5js_code = """
|
62 |
+
let hexSize = 40;
|
63 |
+
let hexGrid = [];
|
64 |
let buildings = [];
|
65 |
let train = { x: 0, y: 0, dir: 1 };
|
66 |
let monsterMode = false;
|
|
|
69 |
let angle = PI / 6;
|
70 |
let debugText = "Loading...";
|
71 |
let playerId = null;
|
72 |
+
const sqrt3 = Math.sqrt(3);
|
73 |
|
74 |
function setup() {
|
75 |
createCanvas(800, 600);
|
76 |
+
hexGrid = JSON.parse(document.getElementById('game_state').innerHTML);
|
77 |
buildings = JSON.parse(document.getElementById('buildings').innerHTML);
|
78 |
train = JSON.parse(document.getElementById('train').innerHTML);
|
79 |
monsterMode = JSON.parse(document.getElementById('monster_mode').innerHTML);
|
|
|
82 |
monsterY = parseInt(document.getElementById('monster_y').innerHTML);
|
83 |
playerId = document.getElementById('player_id').innerHTML;
|
84 |
debugText = `Player ${playerId} joined. Click to build!`;
|
85 |
+
// Set train track on row 5
|
86 |
+
for (let i = 0; i < hexGrid.length; i++) {
|
87 |
+
hexGrid[i][5].type = 'track';
|
88 |
+
}
|
89 |
}
|
90 |
|
91 |
function draw() {
|
92 |
background(220);
|
93 |
+
drawHexGrid();
|
94 |
drawBuildings();
|
95 |
drawTrain();
|
96 |
if (monsterMode) drawMonster();
|
|
|
99 |
text(debugText, 10, 20);
|
100 |
}
|
101 |
|
102 |
+
function drawHexGrid() {
|
103 |
+
for (let i = 0; i < hexGrid.length; i++) {
|
104 |
+
for (let j = 0; j < hexGrid[i].length; j++) {
|
105 |
+
let x = i * hexSize * 1.5;
|
106 |
+
let y = j * hexSize * sqrt3 + (i % 2 === 1 ? hexSize * sqrt3 / 2 : 0);
|
107 |
+
let z = j * hexSize * sin(angle);
|
108 |
+
fill(hexGrid[i][j].type === 'track' ? 100 : 150, 200, 150);
|
109 |
stroke(0);
|
110 |
beginShape();
|
111 |
+
for (let k = 0; k < 6; k++) {
|
112 |
+
let angleRad = PI / 3 * k;
|
113 |
+
vertex(x + hexSize * cos(angleRad), y + hexSize * sin(angleRad) - z);
|
114 |
+
}
|
115 |
endShape(CLOSE);
|
116 |
+
if (hexGrid[i][j].emoji) {
|
117 |
textSize(20);
|
118 |
+
text(hexGrid[i][j].emoji, x - 10, y + 5 - z);
|
119 |
}
|
120 |
}
|
121 |
}
|
|
|
123 |
|
124 |
function drawBuildings() {
|
125 |
buildings.forEach(b => {
|
126 |
+
let x = b.x * hexSize * 1.5;
|
127 |
+
let y = b.y * hexSize * sqrt3 + (b.x % 2 === 1 ? hexSize * sqrt3 / 2 : 0);
|
128 |
+
let z = b.y * hexSize * sin(angle);
|
129 |
fill(b.color);
|
130 |
noStroke();
|
131 |
beginShape();
|
|
|
196 |
}
|
197 |
|
198 |
function mousePressed() {
|
199 |
+
let i = floor(mouseX / (hexSize * 1.5));
|
200 |
+
let j = floor((mouseY - (i % 2 === 1 ? hexSize * sqrt3 / 2 : 0)) / (hexSize * sqrt3));
|
201 |
+
if (i >= 0 && i < hexGrid.length && j >= 0 && j < hexGrid[0].length) {
|
202 |
+
if (hexGrid[i][j].type === 'empty' && !monsterMode) {
|
203 |
let selectedEmoji = document.getElementById('selected_emoji').innerHTML;
|
204 |
if (selectedEmoji.startsWith('๐ ') || selectedEmoji.startsWith('๐ก') || selectedEmoji.startsWith('๐ข') ||
|
205 |
selectedEmoji.startsWith('๐ฃ') || selectedEmoji.startsWith('๐ค') || selectedEmoji.startsWith('๐ฅ') ||
|
|
|
208 |
let types = ['Residential', 'Commercial', 'Industrial', 'School', 'PowerPlant'];
|
209 |
let colors = [[0, 200, 0], [0, 0, 200], [200, 200, 0], [200, 0, 200], [100, 100, 100]];
|
210 |
let idx = floor(random(5));
|
211 |
+
buildings.push({ x: i, y: j, type: types[idx], color: colors[idx], player: playerId });
|
212 |
+
hexGrid[i][j].type = 'building';
|
213 |
} else {
|
214 |
+
hexGrid[i][j].type = 'placed';
|
215 |
+
hexGrid[i][j].emoji = selectedEmoji;
|
216 |
}
|
217 |
debugText = `Player ${playerId} placed ${selectedEmoji} at (${i}, ${j})`;
|
218 |
updateState();
|
|
|
248 |
method: 'POST',
|
249 |
headers: { 'Content-Type': 'application/json' },
|
250 |
body: JSON.stringify({
|
251 |
+
hex_grid: hexGrid,
|
252 |
buildings: buildings,
|
253 |
train: train,
|
254 |
monster_mode: monsterMode,
|
|
|
267 |
player_id = st.sidebar.selectbox("Choose Player", random_names, index=random_names.index(st.session_state.player_id))
|
268 |
st.session_state.player_id = player_id
|
269 |
|
270 |
+
# Story plot generator panel
|
271 |
+
story_html = f"""
|
272 |
+
<div style='background: #f9f9f9; padding: 10px; border-radius: 5px; margin-bottom: 10px;'>
|
273 |
+
<h3>๐ Story Plot</h3>
|
274 |
+
<p>{random.choice(story_beginnings)[1]} {random.choice(nature_labels)} stirred the scene.</p>
|
275 |
+
</div>
|
276 |
+
"""
|
277 |
+
st.sidebar.markdown(story_html, unsafe_allow_html=True)
|
278 |
|
279 |
# Palette grid
|
280 |
st.sidebar.subheader("๐จ Palette")
|
281 |
cols = st.sidebar.columns(5)
|
282 |
selected_emoji = st.session_state.get('selected_emoji', plants[0])
|
|
|
283 |
for i, emoji in enumerate(plants + buildings + creatures):
|
284 |
col = cols[i % 5]
|
285 |
if col.button(emoji, key=f"emoji_{i}"):
|
|
|
292 |
for b in st.session_state.game_state['buildings']:
|
293 |
if b.get('player') == player_id:
|
294 |
st.session_state.game_state['players'][player_id] += 1
|
295 |
+
for i in range(len(st.session_state.game_state['hex_grid'])):
|
296 |
+
for j in range(len(st.session_state.game_state['hex_grid'][i])):
|
297 |
+
if st.session_state.game_state['hex_grid'][i][j]['type'] == 'placed':
|
298 |
+
st.session_state.game_state['players'][player_id] += 1
|
299 |
+
score_html = "<div style='background: #f9f9f9; padding: 10px; border-radius: 5px;'><h3>๐ Scores</h3><ul>"
|
|
|
300 |
for p, s in st.session_state.game_state['players'].items():
|
301 |
+
score_html += f"<li>{p}: {s}</li>"
|
302 |
+
score_html += "</ul></div>"
|
303 |
+
st.sidebar.markdown(score_html, unsafe_allow_html=True)
|
304 |
|
305 |
+
# Main game panel
|
306 |
+
game_html = f"""
|
307 |
<!DOCTYPE html>
|
308 |
<html>
|
309 |
<head>
|
310 |
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.4.2/p5.min.js"></script>
|
311 |
<style>
|
312 |
+
body {{ font-family: Arial, sans-serif; margin: 0; padding: 0; background: #f0f0f0; }}
|
313 |
+
#controls {{ padding: 10px; background: #e0e0e0; }}
|
314 |
+
button {{ padding: 8px 16px; margin: 5px; background: #4CAF50; color: white; border: none; border-radius: 5px; cursor: pointer; }}
|
315 |
button:hover {{ background: #45a049; }}
|
316 |
+
select {{ padding: 8px; margin: 5px; border-radius: 5px; }}
|
317 |
</style>
|
318 |
</head>
|
319 |
<body>
|
|
|
326 |
<button onclick="reverseTrain()">Reverse Train (T)</button>
|
327 |
</div>
|
328 |
<div id="sketch-holder"></div>
|
329 |
+
<div id="game_state" style="display:none">{json.dumps(st.session_state.game_state['hex_grid'])}</div>
|
330 |
<div id="buildings" style="display:none">{json.dumps(st.session_state.game_state['buildings'])}</div>
|
331 |
<div id="train" style="display:none">{json.dumps(st.session_state.game_state['train'])}</div>
|
332 |
<div id="monster_mode" style="display:none">{json.dumps(st.session_state.game_state['monster_mode'])}</div>
|
|
|
347 |
</html>
|
348 |
"""
|
349 |
|
350 |
+
# Streamlit app layout
|
351 |
+
st.title("HexCity 2000 Multiplayer Adventure")
|
352 |
st.write(f"Player: {player_id}. Click to place {selected_emoji}. Use M/G/R/T keys.")
|
353 |
+
html(game_html, height=650)
|
354 |
|
355 |
+
# Update state function (mocked for Streamlit)
|
|
|
|
|
|
|
356 |
def update_state(data):
|
357 |
+
st.session_state.game_state['hex_grid'] = data['hex_grid']
|
358 |
st.session_state.game_state['buildings'] = data['buildings']
|
359 |
st.session_state.game_state['train'] = data['train']
|
360 |
st.session_state.game_state['monster_mode'] = data['monster_mode']
|
|
|
364 |
if data['player_id'] not in st.session_state.game_state['players']:
|
365 |
st.session_state.game_state['players'][data['player_id']] = 0
|
366 |
st.session_state.game_state['players'][data['player_id']] += 1
|
367 |
+
st.rerun()
|
368 |
+
|
369 |
+
# Mock POST handler (for debugging)
|
370 |
+
if st.session_state.get('last_update'):
|
371 |
+
update_state(st.session_state['last_update'])
|