LetsMakeGames2 / app.py
awacke1's picture
Update app.py
27d644c verified
raw
history blame
10.1 kB
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 asyncio
import threading
from typing import Optional
import queue
import atexit
# ๐ŸŽฎ Game Constants
GRID_WIDTH = 16
GRID_HEIGHT = 9
REFRESH_RATE = 5
INTERACTION_RADIUS = 2
POINTS_PER_INTERACTION = 1
# ๐Ÿ•’ Timer Management
class GameTimer:
def __init__(self):
self.running = True
self._loop: Optional[asyncio.AbstractEventLoop] = None
self.update_queue = queue.Queue()
self.thread = threading.Thread(target=self._run_timer, daemon=True)
self.thread.start()
def _run_timer(self):
self._loop = asyncio.new_event_loop()
asyncio.set_event_loop(self._loop)
self._loop.run_until_complete(self._timer_loop())
async def _timer_loop(self):
while self.running:
try:
current_time = time.time()
self.update_queue.put(('update', current_time))
await asyncio.sleep(REFRESH_RATE)
except Exception as e:
print(f"Timer error: {e}")
await asyncio.sleep(1)
def stop(self):
self.running = False
if self._loop:
self._loop.stop()
# ๐ŸŽจ Resource Management: Load and cache images, assign randomly but consistently for each (x, y)
@st.cache_resource(show_spinner="Loading game resources...")
def get_game_images():
image_folder = Path('game_images') # Directory containing the .png images
images = []
for file in image_folder.glob('*.png'):
images.append(Image.open(file))
return images
def initialize_map():
# Path to the cached map configuration
map_path = Path('map.json')
# Load or initialize the map with consistent image selections for each grid position
if map_path.exists():
with open(map_path, 'r') as f:
map_layout = json.load(f)
else:
# If the map doesn't exist, create a random mapping and save it
map_layout = {}
for y in range(GRID_HEIGHT):
for x in range(GRID_WIDTH):
map_layout[f"{x},{y}"] = random.randint(0, len(get_game_images()) - 1)
with open(map_path, 'w') as f:
json.dump(map_layout, f)
return map_layout
# Cache the map layout for consistent use across sessions
map_layout = initialize_map()
# First define the load_game_state function
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()))
}
# Then 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 = {
'STR': 10,
'DEX': 10,
'CON': 10,
'INT': 10,
'WIS': 10,
'CHA': 10,
'HP': 20,
'MAX_HP': 40,
'score': 0,
'created_at': time.time()
}
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 = []
# Now call load_game_state after it's been defined
load_game_state()
# ๐ŸŽฒ Game State Initialization
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()
# ๐ŸŽฎ Player State Management
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)
# ๐Ÿ“ Distance Calculation
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
# ๐Ÿƒโ€โ™‚๏ธ Movement System
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()
# Modify the create_game_board function to use the map_layout
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
# Display the player or other players using the preloaded images
if x == st.session_state.position['x'] and y == st.session_state.position['y']:
cols[x].image(images[map_layout[f"{x},{y}"]], use_column_width=True)
score = st.session_state.character_stats.get('score', 0)
cols[x].markdown(f"**You** ({score} pts)")
elif player_here:
cols[x].image(images[map_layout[f"{x},{y}"]], use_column_width=True)
player_score = all_players[player_here]['stats'].get('score', 0)
cols[x].markdown(f"**{player_here}** ({player_score} pts)")
else:
cols[x].image(images[map_layout[f"{x},{y}"]], use_column_width=True)
# ๐ŸŽฎ Main Game Loop
def main():
# Initialize timer in session state
if 'game_timer' not in st.session_state:
st.session_state.game_timer = GameTimer()
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\n"
f"Last seen: {player['last_seen']:.1f}s ago"
)
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()
st.title("Multiplayer Tile Game")
create_game_board()
# ๐Ÿงน Cleanup on exit
def cleanup():
if 'game_timer' in st.session_state:
st.session_state.game_timer.stop()
# Register cleanup
atexit.register(cleanup)
if __name__ == "__main__":
main()