import React from 'react'; import type { BoardState, Player } from '../types'; import { PixelAvatar, getPlayerPieceType, isPlayerDead } from '../utils/pixelAvatars'; interface PlayerListProps { boardState: BoardState; selectedPlayer: Player | null; onSelectPlayer: (player: Player) => void; onRemovePlayer: (id: string) => void; onOpenPartyModal?: () => void; onDefeatParty?: (partyId: string) => void; onFightBattle?: (challengerId: string, defenderId: string) => void; onChallengeWizard?: (playerId: string) => void; } export const PlayerList: React.FC = ({ boardState, selectedPlayer, onSelectPlayer, onRemovePlayer, onOpenPartyModal, onDefeatParty, onChallengeWizard, }) => { const { players, parties = [], turn } = boardState; const currentTurnId = turn.current_player_id; const partyMap = new Map(); parties.forEach((p) => { p.member_ids.forEach((mid) => partyMap.set(mid, p.name)); }); const isStarted = Boolean(turn.game_started); const liveSelected = selectedPlayer ? players.find((p) => p.id === selectedPlayer.id) || selectedPlayer : null; const activePlayer = players.find((p) => p.id === currentTurnId) || null; const candidate = liveSelected || activePlayer; const candidateAdjacent = Boolean( candidate && boardState.wizard && Math.max( Math.abs(candidate.x - boardState.wizard.x), Math.abs(candidate.y - boardState.wizard.y) ) <= 1 ); const candidateIsTurn = Boolean(candidate && isStarted && candidate.id === currentTurnId); const candidateCanChallenge = Boolean( candidateAdjacent && candidateIsTurn && (!candidate?.party_id || candidate?.is_party_leader) ); return (
{/* Header */}

Active Bots & Parties

{players.length} Bots
{/* Turn & Round Banner */} {players.length > 0 && (
Round {turn.round_number} Turn #{turn.turn_number}
)} {/* Parties Section */} {parties.length > 0 && (
🛡️ Active Parties ({parties.length})
{parties.map((party) => (
👑 {party.name}
Leader: {party.leader_name} •{' '} {party.member_ids.length} bots • ⚡{party.total_strength}
{onDefeatParty && ( )}
))}
)} {/* Wizard NPC Section */} {boardState.wizard && (
🧙 Wandering Wizard (NPC) CHALLENGE NPC
🧙‍♂️ {boardState.wizard.name}
pos: ({boardState.wizard.x}, {boardState.wizard.y}) • Str: ⚡{boardState.wizard.strength}
{candidateAdjacent && onChallengeWizard ? ( ) : (
D20 Duel
)}
)} {/* Action to form party */} {players.length >= 2 && onOpenPartyModal && (
)} {/* Players List */}
{players.length === 0 ? (
🤖
No players registered yet.
Register a player or spawn a random bot to start taking turns!
) : ( players.map((player) => { const isDead = isPlayerDead(player); const isSelected = selectedPlayer?.id === player.id; const isCurrentTurn = !isDead && currentTurnId === player.id; const isLeader = !isDead && player.is_party_leader; const partyName = !isDead ? partyMap.get(player.id) : null; return (
onSelectPlayer(player)} className={`group flex items-center justify-between p-2.5 rounded-xl border transition-all cursor-pointer ${ isDead ? isSelected ? 'bg-slate-900/80 border-slate-600 shadow-md shadow-slate-900/50' : 'bg-slate-950/40 border-slate-800/80 hover:border-slate-700 opacity-75' : isCurrentTurn ? 'bg-amber-950/25 border-amber-500/70 shadow-md shadow-amber-500/10' : isSelected ? 'bg-sky-950/40 border-sky-500 shadow-md shadow-sky-500/10' : 'bg-slate-950/60 border-slate-800 hover:border-slate-700 hover:bg-slate-800/40' }`} >
{isLeader && ( 👑 )}
{player.name} {isDead ? ( 💀 DEAD ) : ( 0 ? 'bg-emerald-950 text-emerald-300 border border-emerald-800' : 'bg-slate-800 text-slate-400' }`} > {player.score >= 0 ? `+${player.score}` : player.score} pts )}
pos: ({player.x}, {player.y}) • Str: ⚡{player.strength || 1} • HP:{' '} {isDead ? '💀 0' : `❤️${player.health ?? 10}`} {isDead && ( • Score: {player.score} )} {partyName && !isDead && ( • {isLeader ? 'Leader' : 'Squad'} )}
{!isDead && boardState.wizard && Math.max( Math.abs(player.x - boardState.wizard.x), Math.abs(player.y - boardState.wizard.y) ) <= 1 && onChallengeWizard && ( )}
); }) )}
{/* Footer Info */}
Coordinate range: [0,0] → [64,64]
); };