Spaces:
Sleeping
Sleeping
import streamlit as st | |
from streamlit.components.v1 import html | |
import random | |
import json | |
import math | |
# Initialize game state | |
if 'game_state' not in st.session_state: | |
st.session_state.game_state = { | |
'hex_grid': [[{'type': 'empty', 'emoji': ''} for _ in range(12)] for _ in range(16)], # Smaller 16x12 grid for better visibility | |
'buildings': [], | |
'players': {}, | |
'train': {'x': 0, 'y': 5, 'dir': 1}, # Using grid coordinates | |
'monster_mode': False, | |
'current_monster': 'Godzilla', | |
'monster_x': 8, | |
'monster_y': 6, | |
'last_update': None | |
} | |
# Constants and assets | |
HEX_SIZE = 40 | |
random_names = ["SkyWalker", "ForestRanger", "CityBuilder", "MonsterTamer", "RailMaster"] | |
plants = ["π±", "π²", "π³", "π΄", "π΅"] | |
buildings = ["π ", "π‘", "π’", "π₯", "π¦"] | |
creatures = ["πΎ", "π±", "πΆ", "π", "π°"] | |
# Updated p5.js code | |
p5js_code = """ | |
const HEX_SIZE = 40; | |
const SQRT_3 = Math.sqrt(3); | |
let hexGrid = []; | |
let buildings = []; | |
let train = {}; | |
let monsterMode = false; | |
let currentMonster = ''; | |
let monsterX, monsterY; | |
let playerId; | |
function setup() { | |
createCanvas(640, 480); // Adjusted canvas size for 16x12 grid | |
updateFromState(); | |
} | |
function updateFromState() { | |
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; | |
} | |
function draw() { | |
background(220); | |
drawHexGrid(); | |
drawBuildings(); | |
drawTrain(); | |
if (monsterMode) drawMonster(); | |
// Train movement | |
train.x += train.dir * 0.05; | |
if (train.x >= hexGrid.length || train.x < 0) train.dir *= -1; | |
} | |
function drawHexGrid() { | |
for (let i = 0; i < hexGrid.length; i++) { | |
for (let j = 0; j < hexGrid[i].length; j++) { | |
let x = i * HEX_SIZE * 1.5; | |
let y = j * HEX_SIZE * SQRT_3 + (i % 2 ? HEX_SIZE * SQRT_3 / 2 : 0); | |
fill(hexGrid[i][j].type === 'track' ? '#808080' : '#90EE90'); | |
stroke(0); | |
drawHex(x, y); | |
if (hexGrid[i][j].emoji) { | |
textSize(20); | |
text(hexGrid[i][j].emoji, x - 10, y + 5); | |
} | |
} | |
} | |
} | |
function drawHex(x, y) { | |
beginShape(); | |
for (let a = 0; a < 6; a++) { | |
let angle = TWO_PI / 6 * a; | |
vertex(x + HEX_SIZE * cos(angle), y + HEX_SIZE * sin(angle)); | |
} | |
endShape(CLOSE); | |
} | |
function drawBuildings() { | |
buildings.forEach(b => { | |
let x = b.x * HEX_SIZE * 1.5; | |
let y = b.y * HEX_SIZE * SQRT_3 + (b.x % 2 ? HEX_SIZE * SQRT_3 / 2 : 0); | |
fill(b.color[0], b.color[1], b.color[2]); | |
noStroke(); | |
ellipse(x, y, HEX_SIZE * 0.8); | |
textSize(20); | |
text(b.emoji, x - 10, y + 5); | |
}); | |
} | |
function drawTrain() { | |
let x = train.x * HEX_SIZE * 1.5; | |
let y = train.y * HEX_SIZE * SQRT_3; | |
fill(150, 50, 50); | |
rect(x - 15, y - 10, 30, 20); | |
text('π', x - 10, y + 5); | |
} | |
function drawMonster() { | |
let x = monsterX * HEX_SIZE * 1.5; | |
let y = monsterY * HEX_SIZE * SQRT_3 + (monsterX % 2 ? HEX_SIZE * SQRT_3 / 2 : 0); | |
fill(255, 0, 0); | |
ellipse(x, y, HEX_SIZE * 1.2); | |
text(currentMonster === 'Godzilla' ? 'π¦' : 'π€', x - 10, y + 5); | |
} | |
function mousePressed() { | |
let i = Math.floor(mouseX / (HEX_SIZE * 1.5)); | |
let j = Math.floor((mouseY - (i % 2 ? HEX_SIZE * SQRT_3 / 2 : 0)) / (HEX_SIZE * SQRT_3)); | |
if (i >= 0 && i < hexGrid.length && j >= 0 && j < hexGrid[0].length && hexGrid[i][j].type === 'empty') { | |
let emoji = document.getElementById('selected_emoji').innerHTML; | |
if (!monsterMode) { | |
if (emoji.startsWith('π ') || emoji.startsWith('π‘') || emoji.startsWith('π’') || | |
emoji.startsWith('π₯') || emoji.startsWith('π¦')) { | |
buildings.push({ | |
x: i, | |
y: j, | |
emoji: emoji, | |
color: [random(100, 255), random(100, 255), random(100, 255)], | |
player: playerId | |
}); | |
hexGrid[i][j].type = 'building'; | |
} else { | |
hexGrid[i][j].type = 'placed'; | |
hexGrid[i][j].emoji = emoji; | |
} | |
updateState(); | |
} | |
} | |
} | |
function keyPressed() { | |
if (key === 'm' || key === 'M') monsterMode = !monsterMode; | |
if (key === 'g' || key === 'G') currentMonster = 'Godzilla'; | |
if (key === 'r' || key === 'R') currentMonster = 'GiantRobot'; | |
if (key === 't' || key === 'T') train.dir *= -1; | |
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 | |
}) | |
}); | |
} | |
""" | |
# UI Components | |
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 | |
# Emoji palette | |
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): | |
if cols[i % 5].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 | |
st.sidebar.subheader("π Scores") | |
for p, s in st.session_state.game_state['players'].items(): | |
st.sidebar.write(f"{p}: {s}") | |
# Game HTML | |
game_html = f""" | |
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.4.2/p5.min.js"></script> | |
<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}</script> | |
""" | |
# Main layout | |
st.title("HexCity Adventure") | |
st.write(f"Player: {player_id}. Click to place {selected_emoji}. Keys: M (monster), G/R (monster type), T (train)") | |
html(game_html, height=500) | |
# State update handler | |
def update_state(data): | |
st.session_state.game_state.update({ | |
'hex_grid': data['hex_grid'], | |
'buildings': data['buildings'], | |
'train': data['train'], | |
'monster_mode': data['monster_mode'], | |
'current_monster': data['current_monster'], | |
'monster_x': data['monster_x'], | |
'monster_y': data['monster_y'], | |
'last_update': data | |
}) | |
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() | |
# Initialize tracks | |
for i in range(len(st.session_state.game_state['hex_grid'])): | |
st.session_state.game_state['hex_grid'][i][5]['type'] = 'track' |