Spaces:
Sleeping
Sleeping
import streamlit as st | |
import os | |
import random | |
import time | |
from PIL import Image | |
import json | |
from datetime import datetime | |
from pathlib import Path | |
import base64 | |
from io import BytesIO | |
from urllib.parse import urlencode | |
import numpy as np | |
# Constants for game board | |
GRID_WIDTH = 16 | |
GRID_HEIGHT = 9 | |
REFRESH_RATE = 5 # seconds | |
INTERACTION_RADIUS = 2 # tiles | |
POINTS_PER_INTERACTION = 1 | |
# Fantasy name generator | |
def generate_fantasy_name(): | |
prefixes = ['Aer', 'Bal', 'Cal', 'Dor', 'El', 'Fae', 'Gor', 'Hel', 'Il', 'Jor', | |
'Kal', 'Lyr', 'Mel', 'Nym', 'Oro', 'Pyr', 'Qar', 'Ryn', 'Syl', 'Tyr'] | |
suffixes = ['ian', 'or', 'ion', 'us', 'ix', 'ar', 'en', 'yr', 'el', 'an', | |
'is', 'ax', 'on', 'ir', 'ex', 'az', 'er', 'eth', 'ys', 'ix'] | |
return random.choice(prefixes) + random.choice(suffixes) | |
def get_query_params(): | |
"""Get game state from URL query parameters""" | |
query_params = st.experimental_get_query_params() | |
if 'player_name' in query_params: | |
st.session_state.player_name = query_params['player_name'][0] | |
st.session_state.position = { | |
'x': int(query_params.get('x', [random.randint(0, GRID_WIDTH - 1)])[0]), | |
'y': int(query_params.get('y', [random.randint(0, GRID_HEIGHT - 1)])[0]) | |
} | |
st.session_state.character_stats = { | |
'STR': int(query_params.get('STR', [10])[0]), | |
'DEX': int(query_params.get('DEX', [10])[0]), | |
'CON': int(query_params.get('CON', [10])[0]), | |
'INT': int(query_params.get('INT', [10])[0]), | |
'WIS': int(query_params.get('WIS', [10])[0]), | |
'CHA': int(query_params.get('CHA', [10])[0]), | |
'HP': int(query_params.get('HP', [20])[0]), | |
'MAX_HP': int(query_params.get('MAX_HP', [40])[0]), | |
'score': int(query_params.get('score', [0])[0]), | |
'created_at': float(query_params.get('created_at', [time.time()])[0]) | |
} | |
def update_query_params(): | |
"""Update URL query parameters with current game state""" | |
if st.session_state.player_name and st.session_state.character_stats: | |
params = { | |
'player_name': st.session_state.player_name, | |
'x': st.session_state.position['x'], | |
'y': st.session_state.position['y'], | |
**st.session_state.character_stats | |
} | |
st.experimental_set_query_params(**params) | |
# Initialize session state | |
if 'initialized' not in st.session_state: | |
st.session_state.initialized = False | |
st.session_state.game_state = { | |
'players': {}, | |
'chat_messages': [], | |
'last_sync': time.time() | |
} | |
st.session_state.player_name = None | |
st.session_state.character_stats = None | |
st.session_state.position = { | |
'x': random.randint(0, GRID_WIDTH - 1), | |
'y': random.randint(0, GRID_HEIGHT - 1) | |
} | |
st.session_state.last_move = time.time() | |
st.session_state.nearby_players = [] | |
get_query_params() | |
def save_player_state(): | |
"""Save player state to JSON file""" | |
if st.session_state.player_name: | |
player_data = { | |
'name': st.session_state.player_name, | |
'position': st.session_state.position, | |
'stats': st.session_state.character_stats, | |
'last_update': time.time() | |
} | |
os.makedirs('players', exist_ok=True) | |
with open(f"players/{st.session_state.player_name}.json", 'w') as f: | |
json.dump(player_data, f) | |
def load_all_players(): | |
"""Load all player states from JSON files""" | |
players = {} | |
if os.path.exists('players'): | |
for filename in os.listdir('players'): | |
if filename.endswith('.json'): | |
with open(f"players/{filename}", 'r') as f: | |
player_data = json.load(f) | |
# Only include active players (updated in last minute) | |
if time.time() - player_data['last_update'] < 60: | |
players[player_data['name']] = player_data | |
return players | |
def calculate_distance(pos1, pos2): | |
"""Calculate Manhattan distance between two positions with wrapping""" | |
dx = min(abs(pos1['x'] - pos2['x']), GRID_WIDTH - abs(pos1['x'] - pos2['x'])) | |
dy = min(abs(pos1['y'] - pos2['y']), GRID_HEIGHT - abs(pos1['y'] - pos2['y'])) | |
return dx + dy | |
def update_position(direction): | |
"""Update player position with wrapping""" | |
if direction == "up": | |
st.session_state.position['y'] = (st.session_state.position['y'] - 1) % GRID_HEIGHT | |
elif direction == "down": | |
st.session_state.position['y'] = (st.session_state.position['y'] + 1) % GRID_HEIGHT | |
elif direction == "left": | |
st.session_state.position['x'] = (st.session_state.position['x'] - 1) % GRID_WIDTH | |
elif direction == "right": | |
st.session_state.position['x'] = (st.session_state.position['x'] + 1) % GRID_WIDTH | |
st.session_state.last_move = time.time() | |
save_player_state() | |
update_query_params() | |
def update_nearby_players(): | |
"""Update list of nearby players and calculate score gains""" | |
all_players = load_all_players() | |
nearby = [] | |
score_gain = 0 | |
for player_name, player_data in all_players.items(): | |
if player_name != st.session_state.player_name: | |
distance = calculate_distance(st.session_state.position, player_data['position']) | |
if distance <= INTERACTION_RADIUS: | |
nearby.append({ | |
'name': player_name, | |
'distance': distance, | |
'score': player_data['stats']['score'] | |
}) | |
score_gain += POINTS_PER_INTERACTION | |
if score_gain > 0: | |
st.session_state.character_stats['score'] += score_gain | |
save_player_state() | |
update_query_params() | |
st.session_state.nearby_players = nearby | |
def create_game_board(): | |
"""Create and display the game board""" | |
all_players = load_all_players() | |
# Create placeholder images for tiles | |
tile_image = Image.new('RGB', (50, 50), color='green') | |
player_image = Image.new('RGB', (50, 50), color='blue') | |
other_player_image = Image.new('RGB', (50, 50), color='red') | |
# Create columns for each row | |
for y in range(GRID_HEIGHT): | |
cols = st.columns(GRID_WIDTH) | |
for x in range(GRID_WIDTH): | |
current_pos = {'x': x, 'y': y} | |
# Check if any player is on this tile | |
player_here = None | |
for player_name, player_data in all_players.items(): | |
if (player_data['position']['x'] == x and | |
player_data['position']['y'] == y): | |
player_here = player_name | |
# Display appropriate image and tooltip | |
if x == st.session_state.position['x'] and y == st.session_state.position['y']: | |
cols[x].image(player_image, use_column_width=True) | |
cols[x].markdown(f"**You** ({st.session_state.character_stats['score']} pts)") | |
elif player_here: | |
cols[x].image(other_player_image, use_column_width=True) | |
cols[x].markdown(f"**{player_here}** ({all_players[player_here]['stats']['score']} pts)") | |
else: | |
cols[x].image(tile_image, use_column_width=True) | |
def main(): | |
# Update nearby players on refresh | |
update_nearby_players() | |
# Sidebar for player info and controls | |
st.sidebar.title("Player Info") | |
# Player name handling | |
if st.session_state.player_name is None: | |
default_name = generate_fantasy_name() | |
player_name = st.sidebar.text_input("Enter your name or use generated name:", | |
value=default_name) | |
if st.sidebar.button("Start Playing"): | |
st.session_state.player_name = player_name | |
if st.session_state.character_stats is None: | |
st.session_state.character_stats = roll_stats() | |
save_player_state() | |
update_query_params() | |
st.rerun() | |
else: | |
# Display nearby players | |
st.sidebar.markdown("### Nearby Players") | |
for player in st.session_state.nearby_players: | |
st.sidebar.markdown( | |
f"**{player['name']}** - {player['distance']} tiles away - {player['score']} pts" | |
) | |
# Movement controls | |
st.sidebar.markdown("### Movement Controls") | |
move_cols = st.sidebar.columns(3) | |
if move_cols[1].button("⬆️", key="up"): | |
update_position("up") | |
st.rerun() | |
cols = st.sidebar.columns(3) | |
if cols[0].button("⬅️", key="left"): | |
update_position("left") | |
st.rerun() | |
if cols[1].button("⬇️", key="down"): | |
update_position("down") | |
st.rerun() | |
if cols[2].button("➡️", key="right"): | |
update_position("right") | |
st.rerun() | |
# Display bookmark URL | |
st.sidebar.markdown("### Save Your Progress") | |
st.sidebar.markdown( | |
"Bookmark this URL to continue where you left off:" | |
) | |
st.sidebar.code(st.experimental_get_query_params()) | |
# Main game area | |
st.title("Multiplayer Tile Game") | |
# Display game board | |
create_game_board() | |
# Auto-refresh logic | |
if (time.time() - st.session_state.last_move) > REFRESH_RATE: | |
st.session_state.last_move = time.time() | |
save_player_state() | |
st.rerun() | |
if __name__ == "__main__": | |
main() |