Spaces:
Sleeping
Sleeping
Update app.py
Browse files
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 |
-
#
|
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': [],
|
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
|
133 |
# -----------------------------------------------------------------------------
|
134 |
-
p5js_code = """
|
135 |
-
const HEX_SIZE =
|
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 |
-
|
262 |
-
let
|
263 |
-
if (
|
264 |
-
let
|
265 |
-
|
266 |
-
|
267 |
-
|
268 |
-
|
269 |
-
|
270 |
-
|
271 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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 |
-
|
358 |
-
}
|
359 |
|
360 |
-
function updateState() {
|
361 |
-
|
|
|
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
|
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
|
423 |
-
|
424 |
-
|
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:
|