File size: 15,379 Bytes
c83e1a9
 
a59f32b
 
9d12910
c83e1a9
a59f32b
 
 
9d12910
a59f32b
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
9d12910
c83e1a9
9d12910
 
c83e1a9
 
 
 
 
a59f32b
c1b75cc
a59f32b
9d12910
c83e1a9
 
 
9d12910
a59f32b
 
 
 
 
 
 
 
9d12910
 
 
 
c83e1a9
 
 
 
9d12910
c83e1a9
 
 
c1b75cc
a59f32b
 
c83e1a9
 
9d12910
 
 
 
 
 
 
c83e1a9
45f7f6d
9d12910
 
 
 
45f7f6d
9d12910
a59f32b
9d12910
a59f32b
c83e1a9
 
 
 
 
 
9d12910
 
 
c83e1a9
 
 
c1b75cc
45f7f6d
 
 
c1b75cc
45f7f6d
 
 
c1b75cc
45f7f6d
 
 
c1b75cc
45f7f6d
 
 
c1b75cc
45f7f6d
 
 
c83e1a9
 
 
 
 
 
45f7f6d
 
 
c83e1a9
 
 
45f7f6d
 
 
 
c83e1a9
 
 
 
 
 
45f7f6d
 
c83e1a9
 
c1b75cc
45f7f6d
 
 
 
 
c1b75cc
45f7f6d
 
 
 
 
c83e1a9
 
 
 
 
 
 
 
 
9d12910
 
 
 
a59f32b
 
 
 
 
 
 
 
9d12910
 
a59f32b
9d12910
 
a59f32b
 
 
c1b75cc
c83e1a9
 
 
45f7f6d
c1b75cc
 
 
a59f32b
c1b75cc
 
 
 
a59f32b
c1b75cc
 
 
 
a59f32b
c1b75cc
 
 
 
a59f32b
c1b75cc
45f7f6d
c83e1a9
a59f32b
 
 
 
 
9d12910
a59f32b
 
 
 
 
 
 
 
 
 
c83e1a9
 
a59f32b
 
 
 
 
 
9d12910
 
 
 
 
 
 
 
a59f32b
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
9d12910
 
 
 
 
a59f32b
9d12910
 
 
a59f32b
9d12910
 
c83e1a9
 
 
 
 
9d12910
 
 
c83e1a9
9d12910
c83e1a9
 
 
 
45f7f6d
c83e1a9
45f7f6d
 
c83e1a9
45f7f6d
c83e1a9
 
9d12910
a59f32b
 
 
 
 
 
 
 
c83e1a9
 
 
 
45f7f6d
 
 
c83e1a9
 
 
 
 
9d12910
 
a59f32b
9d12910
a59f32b
9d12910
a59f32b
9d12910
a59f32b
 
 
 
 
 
 
 
 
9d12910
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
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'])