awacke1 commited on
Commit
9ed88d8
·
verified ·
1 Parent(s): 17896c3

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +182 -102
app.py CHANGED
@@ -7,14 +7,16 @@ import time
7
  import pandas as pd
8
  import graphviz
9
  from scipy.sparse import csr_matrix
 
10
 
11
  # -----------------------------------------------------------------------------
12
- # Page Configuration
13
  # -----------------------------------------------------------------------------
14
  st.set_page_config(page_title="NeuroDungeon: Cortical Column Game", layout="wide")
 
15
 
16
  # -----------------------------------------------------------------------------
17
- # Initialize Game & Memory State
18
  # -----------------------------------------------------------------------------
19
  if 'game_state' not in st.session_state:
20
  st.session_state.game_state = {
@@ -25,7 +27,7 @@ if 'game_state' not in st.session_state:
25
  'exit': {'x': 15, 'y': 11},
26
  'overlord': {'x': 14, 'y': 10, 'hp': 50, 'atk': 5, 'alive': True},
27
  'players': {},
28
- 'story': [], # Memory events (episodic/semantic)
29
  'drawn_cards': 0,
30
  'turn': 0
31
  }
@@ -42,6 +44,14 @@ if 'deck' not in st.session_state:
42
  return deck
43
  st.session_state.deck = create_deck()
44
 
 
 
 
 
 
 
 
 
45
  # -----------------------------------------------------------------------------
46
  # Constants and UI Elements for Both Systems
47
  # -----------------------------------------------------------------------------
@@ -52,6 +62,20 @@ LOOT_EMOJIS = ["⚔️", "🛡️", "💰", "🔑"]
52
  TRAPS = ["⚡", "🕳️"]
53
  SUIT_PROPERTIES = {"Hearts": "heroic", "Diamonds": "treacherous", "Clubs": "fierce", "Spades": "enigmatic"}
54
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
55
  # -----------------------------------------------------------------------------
56
  # Initialize Hero if Not Already Set
57
  # -----------------------------------------------------------------------------
@@ -74,6 +98,28 @@ st.sidebar.subheader("🏆 Scores")
74
  for p, s in st.session_state.game_state['players'].items():
75
  st.sidebar.write(f"{p}: {s}")
76
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
77
  # -----------------------------------------------------------------------------
78
  # Utility Functions for Game Dynamics & Neural Decay
79
  # -----------------------------------------------------------------------------
@@ -129,26 +175,26 @@ for i in range(16):
129
  st.session_state.game_state['hex_grid'][i][j] = {'type': 'wall', 'emoji': '🧱', 'alive': False}
130
 
131
  # -----------------------------------------------------------------------------
132
- # p5.js Code for the HexCitySaga Game Rendering (with Narrative Integration)
133
  # -----------------------------------------------------------------------------
134
- p5js_code = """
135
- const HEX_SIZE = 40;
136
  const SQRT_3 = Math.sqrt(3);
137
  let hexGrid = [];
138
- let heroes = {};
139
  let monsters = [];
140
  let loot = [];
141
- let exit = {};
142
- let overlord = {};
143
  let playerId;
144
  let story = [];
145
 
146
- function setup() {
147
  createCanvas(640, 480);
148
  updateFromState();
149
- }
150
 
151
- function updateFromState() {
152
  hexGrid = JSON.parse(document.getElementById('hex_grid').innerHTML);
153
  heroes = JSON.parse(document.getElementById('heroes').innerHTML);
154
  monsters = JSON.parse(document.getElementById('monsters').innerHTML);
@@ -157,46 +203,59 @@ function updateFromState() {
157
  overlord = JSON.parse(document.getElementById('overlord').innerHTML);
158
  playerId = document.getElementById('player_id').innerHTML;
159
  story = JSON.parse(document.getElementById('story').innerHTML);
160
- }
161
 
162
- function draw() {
163
  background(169, 169, 169);
 
 
 
 
 
 
 
 
 
 
 
 
164
  drawHexGrid();
165
  drawLoot();
166
  drawHeroes();
167
  drawMonsters();
168
  drawExit();
169
  drawOverlord();
 
170
  drawStory();
171
- }
172
 
173
- function drawHexGrid() {
174
- for (let i = 0; i < hexGrid.length; i++) {
175
- for (let j = 0; j < hexGrid[i].length; j++) {
176
  let x = i * HEX_SIZE * 1.5;
177
  let y = j * HEX_SIZE * SQRT_3 + (i % 2 ? HEX_SIZE * SQRT_3 / 2 : 0);
178
  fill(hexGrid[i][j].type === 'wall' ? '#555' : hexGrid[i][j].alive ? '#90EE90' : '#A9A9A9');
179
  stroke(0);
180
  drawHex(x, y);
181
- if (hexGrid[i][j].emoji) {
182
  textSize(20);
183
  text(hexGrid[i][j].emoji, x - 10, y + 5);
184
- }
185
- }
186
- }
187
- }
188
 
189
- function drawHex(x, y) {
190
  beginShape();
191
- for (let a = 0; a < 6; a++) {
192
  let angle = TWO_PI / 6 * a;
193
  vertex(x + HEX_SIZE * cos(angle), y + HEX_SIZE * sin(angle));
194
- }
195
  endShape(CLOSE);
196
- }
197
 
198
- function drawHeroes() {
199
- Object.keys(heroes).forEach(h => {
200
  let hero = heroes[h];
201
  let x = hero.x * HEX_SIZE * 1.5;
202
  let y = hero.y * HEX_SIZE * SQRT_3 + (hero.x % 2 ? HEX_SIZE * SQRT_3 / 2 : 0);
@@ -204,71 +263,86 @@ function drawHeroes() {
204
  ellipse(x, y, HEX_SIZE * 0.8);
205
  textSize(14);
206
  text(h.slice(0,2), x - 10, y + 5);
207
- });
208
- }
209
 
210
- function drawMonsters() {
211
- monsters.forEach(m => {
212
  let x = m.x * HEX_SIZE * 1.5;
213
  let y = m.y * HEX_SIZE * SQRT_3 + (m.x % 2 ? HEX_SIZE * SQRT_3 / 2 : 0);
214
  fill(255, 0, 0);
215
  ellipse(x, y, HEX_SIZE * 0.8);
216
  textSize(20);
217
  text(m.type === 'Godzilla' ? '🦖' : '🤖', x - 10, y + 5);
218
- });
219
- }
220
 
221
- function drawLoot() {
222
- loot.forEach(l => {
223
  let x = l.x * HEX_SIZE * 1.5;
224
  let y = l.y * HEX_SIZE * SQRT_3 + (l.x % 2 ? HEX_SIZE * SQRT_3 / 2 : 0);
225
  fill(255, 215, 0);
226
  ellipse(x, y, HEX_SIZE * 0.6);
227
  textSize(20);
228
  text(l.type, x - 10, y + 5);
229
- });
230
- }
231
 
232
- function drawExit() {
233
  let x = exit.x * HEX_SIZE * 1.5;
234
  let y = exit.y * HEX_SIZE * SQRT_3 + (exit.x % 2 ? HEX_SIZE * SQRT_3 / 2 : 0);
235
  fill(0, 191, 255);
236
  ellipse(x, y, HEX_SIZE * 0.8);
237
  textSize(20);
238
  text('🚪', x - 10, y + 5);
239
- }
240
 
241
- function drawOverlord() {
242
- if (overlord.alive) {
243
  let x = overlord.x * HEX_SIZE * 1.5;
244
  let y = overlord.y * HEX_SIZE * SQRT_3 + (overlord.x % 2 ? HEX_SIZE * SQRT_3 / 2 : 0);
245
  fill(139, 0, 139);
246
  ellipse(x, y, HEX_SIZE * 1.2);
247
  textSize(20);
248
  text('👑', x - 10, y + 5);
249
- }
250
- }
251
 
252
- function drawStory() {
253
  fill(0);
254
  textSize(14);
255
- for (let i = 0; i < Math.min(story.length, 5); i++) {
256
  text(story[story.length - 1 - i], 10, 20 + i * 20);
257
- }
258
- }
259
-
260
- function mousePressed() {
261
- let i = Math.floor(mouseX / (HEX_SIZE * 1.5));
262
- let j = Math.floor((mouseY - (i % 2 ? HEX_SIZE * SQRT_3 / 2 : 0)) / (HEX_SIZE * SQRT_3));
263
- if (i >= 0 && i < hexGrid.length && j >= 0 && j < hexGrid[0].length) {
264
- let hero = heroes[playerId];
265
- if (hexGrid[i][j].type === 'empty' && Math.abs(hero.x - i) <= 1 && Math.abs(hero.y - j) <= 1) {
266
- moveHero(i, j);
267
- }
268
- }
269
- }
270
-
271
- function keyPressed() {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
272
  let hero = heroes[playerId];
273
  if (key === 'w' || key === 'W') moveHero(hero.x, hero.y - 1);
274
  if (key === 's' || key === 'S') moveHero(hero.x, hero.y + 1);
@@ -276,25 +350,25 @@ function keyPressed() {
276
  if (key === 'd' || key === 'D') moveHero(hero.x + 1, hero.y);
277
  if (key === 'q' || key === 'Q') moveHero(hero.x - 1, hero.y - 1);
278
  if (key === 'e' || key === 'E') moveHero(hero.x + 1, hero.y - 1);
279
- }
280
 
281
- function moveHero(newX, newY) {
282
  if (newX < 0 || newX >= hexGrid.length || newY < 0 || newY >= hexGrid[0].length) return;
283
  let hero = heroes[playerId];
284
  let cell = hexGrid[newX][newY];
285
- if (cell.type === 'empty' || cell.type === 'plant') {
286
  hero.x = newX;
287
  hero.y = newY;
288
  handleInteractions();
289
  updateState();
290
- }
291
- }
292
 
293
- function handleInteractions() {
294
  let hero = heroes[playerId];
295
  // Loot Interaction
296
  let lootIdx = loot.findIndex(l => l.x === hero.x && l.y === hero.y);
297
- if (lootIdx !== -1) {
298
  let item = loot[lootIdx].type;
299
  if (item === '⚔️') hero.atk += 2;
300
  if (item === '🛡️') hero.def += 2;
@@ -302,66 +376,67 @@ function handleInteractions() {
302
  if (item === '🔑') hero.gear.push('🔑');
303
  loot.splice(lootIdx, 1);
304
  addStory('Loot', item);
305
- }
306
  // Trap Interaction
307
- if (hexGrid[hero.x][hero.y].type === 'trap') {
308
  hero.hp -= Math.floor(random(1, 5));
309
  addStory('Trap', hexGrid[hero.x][hero.y].emoji);
310
- }
311
  // Monster Combat
312
  let monsterIdx = monsters.findIndex(m => m.x === hero.x && m.y === hero.y);
313
- if (monsterIdx !== -1) {
314
  let monster = monsters[monsterIdx];
315
  let damage = Math.max(0, hero.atk - 1);
316
  monster.hp -= damage;
317
  hero.hp -= Math.max(0, monster.atk - hero.def);
318
- if (monster.hp <= 0) {
319
  monsters.splice(monsterIdx, 1);
320
  hero.xp += 10;
321
  addStory('Monster', monster.type + ' slain');
322
- }
323
- }
324
  // Overlord Combat
325
- if (overlord.alive && hero.x === overlord.x && hero.y === overlord.y) {
326
  let damage = Math.max(0, hero.atk - 2);
327
  overlord.hp -= damage;
328
  hero.hp -= Math.max(0, overlord.atk - hero.def);
329
- if (overlord.hp <= 0) {
330
  overlord.alive = false;
331
  hero.xp += 50;
332
  addStory('Monster', 'Overlord defeated');
333
- }
334
- }
335
  // Exit Check
336
- if (hero.x === exit.x && hero.y === exit.y && hero.gear.includes('🔑')) {
337
  alert('Victory! You escaped the Hex Dungeon!');
338
  resetGame();
339
- }
340
  // Level Up
341
- if (hero.xp >= hero.level * 20) {
342
  hero.level += 1;
343
  hero.hp += 5;
344
  hero.atk += 1;
345
  hero.def += 1;
346
  addStory('Hero', 'leveled up');
347
- }
348
- }
349
 
350
- function addStory(type, word) {
351
  let deck = JSON.parse(document.getElementById('deck').innerHTML);
352
- let cardIndex = st.session_state.drawn_cards % 52;
353
  let card = deck[cardIndex];
354
  let suit = card[0];
355
- let sentence = `A ${SUIT_PROPERTIES[suit]} event: ${type.toLowerCase()} ${word}`;
356
  story.push(sentence);
357
- st.session_state.drawn_cards += 1;
358
- }
359
 
360
- function updateState() {
361
- fetch('/update_state', {
 
362
  method: 'POST',
363
- headers: { 'Content-Type': 'application/json' },
364
- body: JSON.stringify({
365
  hex_grid: hexGrid,
366
  heroes: heroes,
367
  monsters: monsters,
@@ -370,13 +445,13 @@ function updateState() {
370
  overlord: overlord,
371
  story: story,
372
  player_id: playerId
373
- })
374
- });
375
- }
376
  """
377
 
378
  # -----------------------------------------------------------------------------
379
- # Multi-Tab UI: Cross Breeding Cortical Theory and Dungeon Crawl
380
  # -----------------------------------------------------------------------------
381
  tabs = st.tabs(["Cortical Theory", "HexCitySaga", "Memory Integration", "Self Reward NPS", "Extra UI"])
382
 
@@ -419,9 +494,9 @@ with tabs[0]:
419
  # --- Tab 2: HexCitySaga: Dungeon Crawl Game ---
420
  with tabs[1]:
421
  st.header("HexCitySaga: Dungeon Crawl")
422
- st.write(f"Hero: **{player_id}**. Use WASD/QE to move. Goal: escape (🚪) with the key (🔑) or defeat the Overlord (👑).")
423
- game_html = f"""
424
- <script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.4.2/p5.min.js"></script>
425
  <div id="hex_grid" style="display:none">{json.dumps(st.session_state.game_state['hex_grid'])}</div>
426
  <div id="heroes" style="display:none">{json.dumps(st.session_state.game_state['heroes'])}</div>
427
  <div id="monsters" style="display:none">{json.dumps(st.session_state.game_state['monsters'])}</div>
@@ -431,6 +506,12 @@ with tabs[1]:
431
  <div id="player_id" style="display:none">{player_id}</div>
432
  <div id="story" style="display:none">{json.dumps(st.session_state.game_state['story'])}</div>
433
  <div id="deck" style="display:none">{json.dumps(st.session_state.deck)}</div>
 
 
 
 
 
 
434
  <script>{p5js_code}</script>
435
  """
436
  st.components.v1.html(game_html, height=500)
@@ -444,7 +525,6 @@ with tabs[2]:
444
  edges = []
445
  for idx, event in enumerate(st.session_state.game_state['story']):
446
  node_label = f"Event {idx+1}"
447
- # Shorten the event text for node labels if needed
448
  safe_event = event.replace('"', '\'')
449
  nodes.append(f'"{node_label}" [label="{safe_event}"];')
450
  if idx > 0:
 
7
  import pandas as pd
8
  import graphviz
9
  from scipy.sparse import csr_matrix
10
+ from streamlit_autorefresh import st_autorefresh
11
 
12
  # -----------------------------------------------------------------------------
13
+ # Page Configuration & Auto-Refresh (every 5 seconds)
14
  # -----------------------------------------------------------------------------
15
  st.set_page_config(page_title="NeuroDungeon: Cortical Column Game", layout="wide")
16
+ st_autorefresh(interval=5000, limit=1000, key="game_refresh")
17
 
18
  # -----------------------------------------------------------------------------
19
+ # Shared Game State Initialization (simulate shared memory)
20
  # -----------------------------------------------------------------------------
21
  if 'game_state' not in st.session_state:
22
  st.session_state.game_state = {
 
27
  'exit': {'x': 15, 'y': 11},
28
  'overlord': {'x': 14, 'y': 10, 'hp': 50, 'atk': 5, 'alive': True},
29
  'players': {},
30
+ 'story': [],
31
  'drawn_cards': 0,
32
  'turn': 0
33
  }
 
44
  return deck
45
  st.session_state.deck = create_deck()
46
 
47
+ # -----------------------------------------------------------------------------
48
+ # Emoji Paintbrush Mode State (shared via hidden DOM elements)
49
+ # -----------------------------------------------------------------------------
50
+ if 'painting_mode' not in st.session_state:
51
+ st.session_state.painting_mode = False
52
+ if 'selected_emoji' not in st.session_state:
53
+ st.session_state.selected_emoji = ""
54
+
55
  # -----------------------------------------------------------------------------
56
  # Constants and UI Elements for Both Systems
57
  # -----------------------------------------------------------------------------
 
62
  TRAPS = ["⚡", "🕳️"]
63
  SUIT_PROPERTIES = {"Hearts": "heroic", "Diamonds": "treacherous", "Clubs": "fierce", "Spades": "enigmatic"}
64
 
65
+ # A palette of ~60 emojis representing various world objects:
66
+ emoji_palette = [
67
+ "🏠", "🏢", "🏬", "🏭", "🏰", "🏯", "🏟️", "🏡",
68
+ "🚗", "🚕", "🚙", "🚌", "🚎", "🚓", "🚑", "🚒",
69
+ "🚐", "🚚", "🚛", "🚜", "🛵", "🚲", "🛴", "🚀",
70
+ "✈️", "⛵", "🚤", "🛳️", "🚂", "🚆", "🚊", "🚉",
71
+ "👨‍💼", "👩‍💼", "👨‍🔧", "👩‍🔧", "👨‍⚕️", "👩‍⚕️",
72
+ "👮‍♂️", "👮‍♀️", "👷‍♂️", "👷‍♀️", "💂‍♂️", "💂‍♀️",
73
+ "🐶", "🐱", "🐭", "🐹", "🐰", "🦊", "🐻", "🐼",
74
+ "🐨", "🐯", "🦁", "🐮", "🐷", "🐸", "🐵", "🐔",
75
+ "🐧", "🐦", "🌳", "🌴", "🌵", "🍎", "🍌", "🍉",
76
+ "🍇", "🍓", "🍔", "🍕"
77
+ ]
78
+
79
  # -----------------------------------------------------------------------------
80
  # Initialize Hero if Not Already Set
81
  # -----------------------------------------------------------------------------
 
98
  for p, s in st.session_state.game_state['players'].items():
99
  st.sidebar.write(f"{p}: {s}")
100
 
101
+ # -----------------------------------------------------------------------------
102
+ # Emoji Paintbrush Sidebar Palette
103
+ # -----------------------------------------------------------------------------
104
+ st.sidebar.subheader("🎨 Emoji Paintbrush")
105
+ cols = st.sidebar.columns(10)
106
+ # Display the palette in a grid (10 per row)
107
+ for idx, emoji in enumerate(emoji_palette):
108
+ col = cols[idx % 10]
109
+ if col.button(emoji, key=f"emoji_{idx}"):
110
+ st.session_state.painting_mode = True
111
+ st.session_state.selected_emoji = emoji
112
+ st.sidebar.info(f"Painting Mode: {emoji}")
113
+ # Break out if you want one-click activation per emoji.
114
+ # Every 10 emojis, refresh the row columns
115
+ if (idx + 1) % 10 == 0 and idx + 1 < len(emoji_palette):
116
+ cols = st.sidebar.columns(10)
117
+
118
+ if st.sidebar.button("Cancel Paint Mode"):
119
+ st.session_state.painting_mode = False
120
+ st.session_state.selected_emoji = ""
121
+ st.sidebar.info("Painting Mode Disabled")
122
+
123
  # -----------------------------------------------------------------------------
124
  # Utility Functions for Game Dynamics & Neural Decay
125
  # -----------------------------------------------------------------------------
 
175
  st.session_state.game_state['hex_grid'][i][j] = {'type': 'wall', 'emoji': '🧱', 'alive': False}
176
 
177
  # -----------------------------------------------------------------------------
178
+ # p5.js Code for Rendering the Game (with Recenter & Painting Mode)
179
  # -----------------------------------------------------------------------------
180
+ p5js_code = f"""
181
+ const HEX_SIZE = {HEX_SIZE};
182
  const SQRT_3 = Math.sqrt(3);
183
  let hexGrid = [];
184
+ let heroes = {{}};
185
  let monsters = [];
186
  let loot = [];
187
+ let exit = {{}};
188
+ let overlord = {{}};
189
  let playerId;
190
  let story = [];
191
 
192
+ function setup() {{
193
  createCanvas(640, 480);
194
  updateFromState();
195
+ }}
196
 
197
+ function updateFromState() {{
198
  hexGrid = JSON.parse(document.getElementById('hex_grid').innerHTML);
199
  heroes = JSON.parse(document.getElementById('heroes').innerHTML);
200
  monsters = JSON.parse(document.getElementById('monsters').innerHTML);
 
203
  overlord = JSON.parse(document.getElementById('overlord').innerHTML);
204
  playerId = document.getElementById('player_id').innerHTML;
205
  story = JSON.parse(document.getElementById('story').innerHTML);
206
+ }}
207
 
208
+ function draw() {{
209
  background(169, 169, 169);
210
+ push();
211
+ // Recenter the board so the hero is at canvas center
212
+ let hero = heroes[playerId];
213
+ if (hero) {{
214
+ let centerX = width / 2;
215
+ let centerY = height / 2;
216
+ let heroX = hero.x * HEX_SIZE * 1.5;
217
+ let heroY = hero.y * HEX_SIZE * SQRT_3 + (hero.x % 2 ? HEX_SIZE * SQRT_3 / 2 : 0);
218
+ let offsetX = centerX - heroX;
219
+ let offsetY = centerY - heroY;
220
+ translate(offsetX, offsetY);
221
+ }}
222
  drawHexGrid();
223
  drawLoot();
224
  drawHeroes();
225
  drawMonsters();
226
  drawExit();
227
  drawOverlord();
228
+ pop();
229
  drawStory();
230
+ }}
231
 
232
+ function drawHexGrid() {{
233
+ for (let i = 0; i < hexGrid.length; i++) {{
234
+ for (let j = 0; j < hexGrid[i].length; j++) {{
235
  let x = i * HEX_SIZE * 1.5;
236
  let y = j * HEX_SIZE * SQRT_3 + (i % 2 ? HEX_SIZE * SQRT_3 / 2 : 0);
237
  fill(hexGrid[i][j].type === 'wall' ? '#555' : hexGrid[i][j].alive ? '#90EE90' : '#A9A9A9');
238
  stroke(0);
239
  drawHex(x, y);
240
+ if (hexGrid[i][j].emoji) {{
241
  textSize(20);
242
  text(hexGrid[i][j].emoji, x - 10, y + 5);
243
+ }}
244
+ }}
245
+ }}
246
+ }}
247
 
248
+ function drawHex(x, y) {{
249
  beginShape();
250
+ for (let a = 0; a < 6; a++) {{
251
  let angle = TWO_PI / 6 * a;
252
  vertex(x + HEX_SIZE * cos(angle), y + HEX_SIZE * sin(angle));
253
+ }}
254
  endShape(CLOSE);
255
+ }}
256
 
257
+ function drawHeroes() {{
258
+ Object.keys(heroes).forEach(h => {{
259
  let hero = heroes[h];
260
  let x = hero.x * HEX_SIZE * 1.5;
261
  let y = hero.y * HEX_SIZE * SQRT_3 + (hero.x % 2 ? HEX_SIZE * SQRT_3 / 2 : 0);
 
263
  ellipse(x, y, HEX_SIZE * 0.8);
264
  textSize(14);
265
  text(h.slice(0,2), x - 10, y + 5);
266
+ }});
267
+ }}
268
 
269
+ function drawMonsters() {{
270
+ monsters.forEach(m => {{
271
  let x = m.x * HEX_SIZE * 1.5;
272
  let y = m.y * HEX_SIZE * SQRT_3 + (m.x % 2 ? HEX_SIZE * SQRT_3 / 2 : 0);
273
  fill(255, 0, 0);
274
  ellipse(x, y, HEX_SIZE * 0.8);
275
  textSize(20);
276
  text(m.type === 'Godzilla' ? '🦖' : '🤖', x - 10, y + 5);
277
+ }});
278
+ }}
279
 
280
+ function drawLoot() {{
281
+ loot.forEach(l => {{
282
  let x = l.x * HEX_SIZE * 1.5;
283
  let y = l.y * HEX_SIZE * SQRT_3 + (l.x % 2 ? HEX_SIZE * SQRT_3 / 2 : 0);
284
  fill(255, 215, 0);
285
  ellipse(x, y, HEX_SIZE * 0.6);
286
  textSize(20);
287
  text(l.type, x - 10, y + 5);
288
+ }});
289
+ }}
290
 
291
+ function drawExit() {{
292
  let x = exit.x * HEX_SIZE * 1.5;
293
  let y = exit.y * HEX_SIZE * SQRT_3 + (exit.x % 2 ? HEX_SIZE * SQRT_3 / 2 : 0);
294
  fill(0, 191, 255);
295
  ellipse(x, y, HEX_SIZE * 0.8);
296
  textSize(20);
297
  text('🚪', x - 10, y + 5);
298
+ }}
299
 
300
+ function drawOverlord() {{
301
+ if (overlord.alive) {{
302
  let x = overlord.x * HEX_SIZE * 1.5;
303
  let y = overlord.y * HEX_SIZE * SQRT_3 + (overlord.x % 2 ? HEX_SIZE * SQRT_3 / 2 : 0);
304
  fill(139, 0, 139);
305
  ellipse(x, y, HEX_SIZE * 1.2);
306
  textSize(20);
307
  text('👑', x - 10, y + 5);
308
+ }}
309
+ }}
310
 
311
+ function drawStory() {{
312
  fill(0);
313
  textSize(14);
314
+ for (let i = 0; i < Math.min(story.length, 5); i++) {{
315
  text(story[story.length - 1 - i], 10, 20 + i * 20);
316
+ }}
317
+ }}
318
+
319
+ function mousePressed() {{
320
+ // Check if we're in painting mode (shared via hidden div)
321
+ let paintingMode = document.getElementById('painting_mode').innerHTML;
322
+ if (paintingMode === "true") {{
323
+ let i = Math.floor(mouseX / (HEX_SIZE * 1.5));
324
+ let j = Math.floor((mouseY - (i % 2 ? HEX_SIZE * SQRT_3 / 2 : 0)) / (HEX_SIZE * SQRT_3));
325
+ if (i >= 0 && i < hexGrid.length && j >= 0 && j < hexGrid[0].length) {{
326
+ let selectedEmoji = document.getElementById('selected_emoji').innerHTML;
327
+ hexGrid[i][j].emoji = selectedEmoji;
328
+ hexGrid[i][j].type = 'organism';
329
+ hexGrid[i][j].alive = true;
330
+ updateState();
331
+ }}
332
+ }} else {{
333
+ // Default behavior: move hero if adjacent cell is empty
334
+ let i = Math.floor(mouseX / (HEX_SIZE * 1.5));
335
+ let j = Math.floor((mouseY - (i % 2 ? HEX_SIZE * SQRT_3 / 2 : 0)) / (HEX_SIZE * SQRT_3));
336
+ if (i >= 0 && i < hexGrid.length && j >= 0 && j < hexGrid[0].length) {{
337
+ let hero = heroes[playerId];
338
+ if (hexGrid[i][j].type === 'empty' && Math.abs(hero.x - i) <= 1 && Math.abs(hero.y - j) <= 1) {{
339
+ moveHero(i, j);
340
+ }}
341
+ }}
342
+ }}
343
+ }}
344
+
345
+ function keyPressed() {{
346
  let hero = heroes[playerId];
347
  if (key === 'w' || key === 'W') moveHero(hero.x, hero.y - 1);
348
  if (key === 's' || key === 'S') moveHero(hero.x, hero.y + 1);
 
350
  if (key === 'd' || key === 'D') moveHero(hero.x + 1, hero.y);
351
  if (key === 'q' || key === 'Q') moveHero(hero.x - 1, hero.y - 1);
352
  if (key === 'e' || key === 'E') moveHero(hero.x + 1, hero.y - 1);
353
+ }}
354
 
355
+ function moveHero(newX, newY) {{
356
  if (newX < 0 || newX >= hexGrid.length || newY < 0 || newY >= hexGrid[0].length) return;
357
  let hero = heroes[playerId];
358
  let cell = hexGrid[newX][newY];
359
+ if (cell.type === 'empty' || cell.type === 'plant') {{
360
  hero.x = newX;
361
  hero.y = newY;
362
  handleInteractions();
363
  updateState();
364
+ }}
365
+ }}
366
 
367
+ function handleInteractions() {{
368
  let hero = heroes[playerId];
369
  // Loot Interaction
370
  let lootIdx = loot.findIndex(l => l.x === hero.x && l.y === hero.y);
371
+ if (lootIdx !== -1) {{
372
  let item = loot[lootIdx].type;
373
  if (item === '⚔️') hero.atk += 2;
374
  if (item === '🛡️') hero.def += 2;
 
376
  if (item === '🔑') hero.gear.push('🔑');
377
  loot.splice(lootIdx, 1);
378
  addStory('Loot', item);
379
+ }}
380
  // Trap Interaction
381
+ if (hexGrid[hero.x][hero.y].type === 'trap') {{
382
  hero.hp -= Math.floor(random(1, 5));
383
  addStory('Trap', hexGrid[hero.x][hero.y].emoji);
384
+ }}
385
  // Monster Combat
386
  let monsterIdx = monsters.findIndex(m => m.x === hero.x && m.y === hero.y);
387
+ if (monsterIdx !== -1) {{
388
  let monster = monsters[monsterIdx];
389
  let damage = Math.max(0, hero.atk - 1);
390
  monster.hp -= damage;
391
  hero.hp -= Math.max(0, monster.atk - hero.def);
392
+ if (monster.hp <= 0) {{
393
  monsters.splice(monsterIdx, 1);
394
  hero.xp += 10;
395
  addStory('Monster', monster.type + ' slain');
396
+ }}
397
+ }}
398
  // Overlord Combat
399
+ if (overlord.alive && hero.x === overlord.x && hero.y === overlord.y) {{
400
  let damage = Math.max(0, hero.atk - 2);
401
  overlord.hp -= damage;
402
  hero.hp -= Math.max(0, overlord.atk - hero.def);
403
+ if (overlord.hp <= 0) {{
404
  overlord.alive = false;
405
  hero.xp += 50;
406
  addStory('Monster', 'Overlord defeated');
407
+ }}
408
+ }}
409
  // Exit Check
410
+ if (hero.x === exit.x && hero.y === exit.y && hero.gear.includes('🔑')) {{
411
  alert('Victory! You escaped the Hex Dungeon!');
412
  resetGame();
413
+ }}
414
  // Level Up
415
+ if (hero.xp >= hero.level * 20) {{
416
  hero.level += 1;
417
  hero.hp += 5;
418
  hero.atk += 1;
419
  hero.def += 1;
420
  addStory('Hero', 'leveled up');
421
+ }}
422
+ }}
423
 
424
+ function addStory(type, word) {{
425
  let deck = JSON.parse(document.getElementById('deck').innerHTML);
426
+ let cardIndex = {{"drawn": st.session_state.game_state['drawn_cards']}}.drawn % 52;
427
  let card = deck[cardIndex];
428
  let suit = card[0];
429
+ let sentence = `A ${{SUIT_PROPERTIES[suit]}} event: ${{type.toLowerCase()}} ${{word}}`;
430
  story.push(sentence);
431
+ // In a full implementation, update the drawn_cards counter server-side.
432
+ }}
433
 
434
+ function updateState() {{
435
+ // Send the updated state back to Streamlit.
436
+ fetch('/update_state', {{
437
  method: 'POST',
438
+ headers: {{ 'Content-Type': 'application/json' }},
439
+ body: JSON.stringify({{
440
  hex_grid: hexGrid,
441
  heroes: heroes,
442
  monsters: monsters,
 
445
  overlord: overlord,
446
  story: story,
447
  player_id: playerId
448
+ }})
449
+ }});
450
+ }}
451
  """
452
 
453
  # -----------------------------------------------------------------------------
454
+ # Multi-Tab UI: Cross Breeding Cortical Theory & Dungeon Crawl
455
  # -----------------------------------------------------------------------------
456
  tabs = st.tabs(["Cortical Theory", "HexCitySaga", "Memory Integration", "Self Reward NPS", "Extra UI"])
457
 
 
494
  # --- Tab 2: HexCitySaga: Dungeon Crawl Game ---
495
  with tabs[1]:
496
  st.header("HexCitySaga: Dungeon Crawl")
497
+ st.write(f"Hero: **{player_id}**. Use WASD/QE to move or click on a hex (if in painting mode) to add a new organism.")
498
+ # The following hidden divs provide shared state info to p5.js:
499
+ hidden_divs = f"""
500
  <div id="hex_grid" style="display:none">{json.dumps(st.session_state.game_state['hex_grid'])}</div>
501
  <div id="heroes" style="display:none">{json.dumps(st.session_state.game_state['heroes'])}</div>
502
  <div id="monsters" style="display:none">{json.dumps(st.session_state.game_state['monsters'])}</div>
 
506
  <div id="player_id" style="display:none">{player_id}</div>
507
  <div id="story" style="display:none">{json.dumps(st.session_state.game_state['story'])}</div>
508
  <div id="deck" style="display:none">{json.dumps(st.session_state.deck)}</div>
509
+ <div id="painting_mode" style="display:none">{str(st.session_state.painting_mode).lower()}</div>
510
+ <div id="selected_emoji" style="display:none">{st.session_state.selected_emoji}</div>
511
+ """
512
+ game_html = f"""
513
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.4.2/p5.min.js"></script>
514
+ {hidden_divs}
515
  <script>{p5js_code}</script>
516
  """
517
  st.components.v1.html(game_html, height=500)
 
525
  edges = []
526
  for idx, event in enumerate(st.session_state.game_state['story']):
527
  node_label = f"Event {idx+1}"
 
528
  safe_event = event.replace('"', '\'')
529
  nodes.append(f'"{node_label}" [label="{safe_event}"];')
530
  if idx > 0: