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 import numpy as np GRID_WIDTH = 16 GRID_HEIGHT = 9 REFRESH_RATE = 5 INTERACTION_RADIUS = 2 POINTS_PER_INTERACTION = 1 @st.cache_resource(show_spinner="Loading game resources...") def get_game_images(): return { 'tile': Image.new('RGB', (50, 50), color='green'), 'player': Image.new('RGB', (50, 50), color='blue'), 'other_player': Image.new('RGB', (50, 50), color='red') } @st.cache_data def get_name_components(): return { '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'] } @st.cache_data(ttl=5) def load_all_players(timestamp): players = {} if os.path.exists('players'): for filename in os.listdir('players'): if filename.endswith('.json'): with open(f"players/{filename}", 'r') as f: try: player_data = json.load(f) if time.time() - player_data['last_update'] < 60: players[player_data['name']] = player_data except json.JSONDecodeError: continue return {'players': players, 'last_update': time.time()} def clear_caches(): st.cache_resource.clear() st.cache_data.clear() def generate_fantasy_name(): name_parts = get_name_components() return random.choice(name_parts['prefixes']) + random.choice(name_parts['suffixes']) def load_game_state(): if 'player_name' in st.query_params: st.session_state.player_name = st.query_params.player_name st.session_state.position = { 'x': int(st.query_params.get('x', random.randint(0, GRID_WIDTH - 1))), 'y': int(st.query_params.get('y', random.randint(0, GRID_HEIGHT - 1))) } st.session_state.character_stats = { 'STR': int(st.query_params.get('STR', 10)), 'DEX': int(st.query_params.get('DEX', 10)), 'CON': int(st.query_params.get('CON', 10)), 'INT': int(st.query_params.get('INT', 10)), 'WIS': int(st.query_params.get('WIS', 10)), 'CHA': int(st.query_params.get('CHA', 10)), 'HP': int(st.query_params.get('HP', 20)), 'MAX_HP': int(st.query_params.get('MAX_HP', 40)), 'score': int(st.query_params.get('score', 0)), 'created_at': float(st.query_params.get('created_at', time.time())) } def save_game_state(): if st.session_state.player_name and st.session_state.character_stats: params = { 'player_name': st.session_state.player_name, 'x': str(st.session_state.position['x']), 'y': str(st.session_state.position['y']), **{k: str(v) for k, v in st.session_state.character_stats.items()} } st.query_params.from_dict(params) 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 = [] load_game_state() def save_player_state(): 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) save_game_state() def calculate_distance(pos1, pos2): 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): 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() def update_nearby_players(): current_time = time.time() all_players = load_all_players(current_time)['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() st.session_state.nearby_players = nearby def create_game_board(): current_time = time.time() all_players = load_all_players(current_time)['players'] images = get_game_images() for y in range(GRID_HEIGHT): cols = st.columns(GRID_WIDTH) for x in range(GRID_WIDTH): 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 if x == st.session_state.position['x'] and y == st.session_state.position['y']: cols[x].image(images['player'], use_column_width=True) cols[x].markdown(f"**You** ({st.session_state.character_stats['score']} pts)") elif player_here: cols[x].image(images['other_player'], use_column_width=True) cols[x].markdown(f"**{player_here}** ({all_players[player_here]['stats']['score']} pts)") else: cols[x].image(images['tile'], use_column_width=True) def create_auto_refresh_timer(): refresh_js = """ """ st.components.v1.html(refresh_js, height=0) def main(): create_auto_refresh_timer() update_nearby_players() st.sidebar.title("Player Info") 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 = { 'STR': sum(sorted([random.randint(1, 6) for _ in range(4)])[1:]), 'DEX': sum(sorted([random.randint(1, 6) for _ in range(4)])[1:]), 'CON': sum(sorted([random.randint(1, 6) for _ in range(4)])[1:]), 'INT': sum(sorted([random.randint(1, 6) for _ in range(4)])[1:]), 'WIS': sum(sorted([random.randint(1, 6) for _ in range(4)])[1:]), 'CHA': sum(sorted([random.randint(1, 6) for _ in range(4)])[1:]), 'HP': random.randint(1, 20) * 2 + random.randint(1, 20), 'MAX_HP': 40, 'score': 0, 'created_at': time.time() } save_player_state() st.rerun() else: 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") 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() if st.sidebar.button("Clear Game State"): st.query_params.clear() clear_caches() st.rerun() st.title("Multiplayer Tile Game") create_game_board() 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()