import { useEffect, useRef, useState, useCallback } from 'react'; import type { BoardState, Player } from '../types'; const INITIAL_BOARD: BoardState = { config: { min_x: 0, max_x: 64, min_y: 0, max_y: 64, grid_cells_x: 64, grid_cells_y: 64, }, player_count: 0, players: [], }; export function useGameSocket() { const [boardState, setBoardState] = useState(INITIAL_BOARD); const [isConnected, setIsConnected] = useState(false); const [selectedPlayer, setSelectedPlayer] = useState(null); const wsRef = useRef(null); const reconnectTimeoutRef = useRef(null); const fetchBoard = useCallback(async () => { try { const res = await fetch('/api/board'); if (res.ok) { const data: BoardState = await res.json(); setBoardState(data); } } catch (err) { console.error('Failed to fetch board state:', err); } }, []); const connectWebSocket = useCallback(() => { if (wsRef.current && (wsRef.current.readyState === WebSocket.OPEN || wsRef.current.readyState === WebSocket.CONNECTING)) { return; } const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:'; const host = window.location.host; const wsUrl = `${protocol}//${host}/ws`; const ws = new WebSocket(wsUrl); wsRef.current = ws; ws.onopen = () => { setIsConnected(true); console.log('Connected to botWebWars WebSocket'); }; ws.onmessage = (event) => { try { const data = JSON.parse(event.data); if (data.event === 'init') { setBoardState(data.state); } else if (data.event === 'player_joined') { setBoardState((prev) => { const exists = prev.players.some((p) => p.id === data.player.id); const nextPlayers = exists ? prev.players.map((p) => (p.id === data.player.id ? data.player : p)) : [...prev.players, data.player]; return { ...prev, player_count: data.player_count ?? nextPlayers.length, players: nextPlayers, }; }); } else if (data.event === 'player_left') { setBoardState((prev) => { const nextPlayers = prev.players.filter((p) => p.id !== data.player_id); return { ...prev, player_count: data.player_count ?? nextPlayers.length, players: nextPlayers, }; }); setSelectedPlayer((prev) => (prev?.id === data.player_id ? null : prev)); } else if (data.event === 'board_reset') { setBoardState((prev) => ({ ...prev, player_count: 0, players: [], })); setSelectedPlayer(null); } } catch (err) { console.error('Error parsing WebSocket message:', err); } }; ws.onclose = () => { setIsConnected(false); reconnectTimeoutRef.current = window.setTimeout(() => { connectWebSocket(); }, 2500); }; ws.onerror = (err) => { console.error('WebSocket encountered an error:', err); ws.close(); }; }, []); useEffect(() => { fetchBoard(); connectWebSocket(); return () => { if (reconnectTimeoutRef.current) { clearTimeout(reconnectTimeoutRef.current); } if (wsRef.current) { wsRef.current.close(); } }; }, [fetchBoard, connectWebSocket]); // Keep player selection in sync if player updates useEffect(() => { if (selectedPlayer) { const updated = boardState.players.find((p) => p.id === selectedPlayer.id); if (updated) { setSelectedPlayer(updated); } else { setSelectedPlayer(null); } } }, [boardState.players, selectedPlayer]); const registerPlayer = async (name: string, color: string): Promise => { const res = await fetch('/api/players', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ name, color }), }); if (!res.ok) { const errorData = await res.json().catch(() => ({})); throw new Error(errorData.detail || 'Failed to register player'); } const newPlayer: Player = await res.json(); setSelectedPlayer(newPlayer); return newPlayer; }; const removePlayer = async (id: string) => { const res = await fetch(`/api/players/${id}`, { method: 'DELETE', }); if (!res.ok) { throw new Error('Failed to remove player'); } }; const resetBoard = async () => { const res = await fetch('/api/board/reset', { method: 'POST', }); if (!res.ok) { throw new Error('Failed to reset board'); } }; return { boardState, isConnected, selectedPlayer, setSelectedPlayer, registerPlayer, removePlayer, resetBoard, refresh: fetchBoard, }; }