Spaces:
Sleeping
Sleeping
import streamlit as st | |
import streamlit.components.v1 as components | |
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 | |
# Initialize session state variables | |
if 'game_state' not in st.session_state: | |
st.session_state.game_state = { | |
'players': {}, | |
'chat_messages': [], | |
'tile_map': [], | |
'current_player': None | |
} | |
if 'player_name' not in st.session_state: | |
st.session_state.player_name = None | |
if 'last_refresh' not in st.session_state: | |
st.session_state.last_refresh = time.time() | |
if 'start_time' not in st.session_state: | |
st.session_state.start_time = None | |
if 'play_duration' not in st.session_state: | |
st.session_state.play_duration = 0 | |
# Utility functions | |
def load_tiles_and_text(): | |
"""Load tile images and their corresponding markdown text from the root directory""" | |
tiles = {} | |
for file in os.listdir('.'): | |
if file.endswith(('.png', '.jpg', '.jpeg')): | |
tile_name = os.path.splitext(file)[0] | |
# Load image | |
img = Image.open(file) | |
# Convert image to base64 for HTML embedding | |
buffered = BytesIO() | |
img.save(buffered, format="PNG") | |
img_str = base64.b64encode(buffered.getvalue()).decode() | |
# Check for corresponding markdown file | |
md_file = f"{tile_name}.md" | |
overlay_text = "" | |
if os.path.exists(md_file): | |
with open(md_file, 'r') as f: | |
overlay_text = f.read().strip() | |
tiles[tile_name] = { | |
'image': img_str, | |
'text': overlay_text | |
} | |
return tiles | |
def generate_map(width=20, height=15): | |
"""Generate a random tile map""" | |
tile_types = ['grass', 'water', 'rock'] | |
return [[random.choice(tile_types) for _ in range(width)] for _ in range(height)] | |
def create_tile_html(tile_data, size=60): | |
"""Create HTML for a single tile with text overlay""" | |
return f""" | |
<div style="position: relative; width: {size}px; height: {size}px; display: inline-block;"> | |
<img src="data:image/png;base64,{tile_data['image']}" | |
style="width: {size}px; height: {size}px; object-fit: cover;"> | |
<div style="position: absolute; top: 0; left: 0; right: 0; bottom: 0; | |
background-color: rgba(255, 255, 255, 0.7); | |
display: flex; align-items: center; justify-content: center; | |
text-align: center; font-size: 10px; padding: 2px; | |
opacity: 0.8; pointer-events: none;"> | |
{tile_data['text']} | |
</div> | |
</div> | |
""" | |
def create_game_board_html(tile_map, tiles, players): | |
"""Create HTML for the entire game board""" | |
board_html = """ | |
<style> | |
.game-board { line-height: 0; } | |
.player-marker { | |
position: absolute; | |
width: 10px; | |
height: 10px; | |
background-color: red; | |
border-radius: 50%; | |
z-index: 2; | |
} | |
</style> | |
<div class="game-board"> | |
""" | |
for y, row in enumerate(tile_map): | |
for x, tile_type in enumerate(row): | |
if tile_type in tiles: | |
board_html += create_tile_html(tiles[tile_type]) | |
board_html += "<br>" | |
# Add player markers | |
for player_name, player_data in players.items(): | |
pos_x = player_data['position']['x'] * 60 + 25 | |
pos_y = player_data['position']['y'] * 60 + 25 | |
board_html += f""" | |
<div class="player-marker" style="left: {pos_x}px; top: {pos_y}px;" | |
title="{player_name}"></div> | |
""" | |
board_html += "</div>" | |
return board_html | |
def save_game_state(): | |
"""Save the current game state to a file""" | |
state_file = Path("game_state.json") | |
state_to_save = { | |
'players': st.session_state.game_state['players'], | |
'chat_messages': st.session_state.game_state['chat_messages'], | |
'tile_map': st.session_state.game_state['tile_map'] | |
} | |
with open(state_file, 'w') as f: | |
json.dump(state_to_save, f) | |
def load_game_state(): | |
"""Load the game state from file""" | |
state_file = Path("game_state.json") | |
if state_file.exists(): | |
with open(state_file, 'r') as f: | |
loaded_state = json.load(f) | |
st.session_state.game_state.update(loaded_state) | |
def add_chat_message(player_name, message): | |
"""Add a message to the chat history""" | |
timestamp = datetime.now().strftime("%H:%M:%S") | |
st.session_state.game_state['chat_messages'].append({ | |
'player': player_name, | |
'message': message, | |
'timestamp': timestamp | |
}) | |
save_game_state() | |
def format_duration(seconds): | |
"""Format duration in seconds to HH:MM:SS""" | |
hours = seconds // 3600 | |
minutes = (seconds % 3600) // 60 | |
seconds = seconds % 60 | |
return f"{int(hours):02d}:{int(minutes):02d}:{int(seconds):02d}" | |
def check_auto_refresh(): | |
"""Check if it's time to refresh the page""" | |
current_time = time.time() | |
if current_time - st.session_state.last_refresh >= 5: | |
st.session_state.last_refresh = current_time | |
st.rerun() | |
def update_play_duration(): | |
"""Update the player's total play duration""" | |
if st.session_state.start_time is not None: | |
current_time = time.time() | |
st.session_state.play_duration = int(current_time - st.session_state.start_time) | |
# Main game UI | |
def main(): | |
st.title("Multiplayer Tile Game") | |
if st.session_state.player_name is None: | |
with st.form("join_game"): | |
player_name = st.text_input("Enter your name:") | |
submitted = st.form_submit_button("Join Game") | |
if submitted and player_name: | |
st.session_state.player_name = player_name | |
st.session_state.start_time = time.time() | |
st.session_state.play_duration = 0 | |
st.session_state.game_state['players'][player_name] = { | |
'position': {'x': 0, 'y': 0}, | |
'last_active': time.time(), | |
'score': 0 | |
} | |
st.rerun() | |
else: | |
update_play_duration() | |
st.sidebar.metric("Play Time", format_duration(st.session_state.play_duration)) | |
col1, col2 = st.columns([2, 1]) | |
with col1: | |
st.subheader("Game Map") | |
if not st.session_state.game_state['tile_map']: | |
st.session_state.game_state['tile_map'] = generate_map() | |
tiles = load_tiles_and_text() | |
if tiles: | |
# Generate and display the game board | |
board_html = create_game_board_html( | |
st.session_state.game_state['tile_map'], | |
tiles, | |
st.session_state.game_state['players'] | |
) | |
components.html(board_html, height=600) | |
# Movement controls | |
if st.session_state.player_name: | |
player = st.session_state.game_state['players'][st.session_state.player_name] | |
cols = st.columns(4) | |
if cols[0].button("β"): | |
player['position']['x'] = max(0, player['position']['x'] - 1) | |
if cols[1].button("β"): | |
player['position']['y'] = max(0, player['position']['y'] - 1) | |
if cols[2].button("β"): | |
player['position']['y'] = min(14, player['position']['y'] + 1) | |
if cols[3].button("β"): | |
player['position']['x'] = min(19, player['position']['x'] + 1) | |
st.write(f"Position: ({player['position']['x']}, {player['position']['y']})") | |
else: | |
st.warning("No image files found. Please add .png files and optional .md files with matching names.") | |
with col2: | |
st.subheader("Chat Room") | |
chat_container = st.container() | |
with chat_container: | |
for message in st.session_state.game_state['chat_messages'][-50:]: | |
st.text(f"[{message['timestamp']}] {message['player']}: {message['message']}") | |
with st.form("chat_form", clear_on_submit=True): | |
message = st.text_input("Message:") | |
if st.form_submit_button("Send") and message: | |
add_chat_message(st.session_state.player_name, message) | |
st.rerun() | |
if st.button("Leave Game"): | |
if st.session_state.player_name in st.session_state.game_state['players']: | |
del st.session_state.game_state['players'][st.session_state.player_name] | |
st.session_state.player_name = None | |
st.session_state.start_time = None | |
st.session_state.play_duration = 0 | |
save_game_state() | |
st.rerun() | |
check_auto_refresh() | |
if __name__ == "__main__": | |
load_game_state() | |
main() |