import React, { useState } from 'react'; import type { BoardState, Player } from '../types'; interface PartyModalProps { isOpen: boolean; boardState: BoardState; onClose: () => void; onFormParty: (memberIds: string[], leaderId: string, name?: string) => Promise; } export const PartyModal: React.FC = ({ isOpen, boardState, onClose, onFormParty, }) => { const [selectedIds, setSelectedIds] = useState([]); const [leaderId, setLeaderId] = useState(''); const [partyName, setPartyName] = useState(''); const [error, setError] = useState(null); const [loading, setLoading] = useState(false); if (!isOpen) return null; const { players } = boardState; // Check if two players are adjacent (Chebyshev distance <= 1) const areAdjacent = (p1: Player, p2: Player) => { return Math.max(Math.abs(p1.x - p2.x), Math.abs(p1.y - p2.y)) <= 1; }; // Find any adjacent pairs in the game for quick selection const findAdjacentPairs = () => { for (let i = 0; i < players.length; i++) { for (let j = i + 1; j < players.length; j++) { if (areAdjacent(players[i], players[j])) { return [players[i], players[j]]; } } } return null; }; const handleQuickPair = () => { const pair = findAdjacentPairs(); if (pair) { setSelectedIds([pair[0].id, pair[1].id]); setLeaderId(pair[0].id); setPartyName(`Squad ${pair[0].name}`); setError(null); } else { setError('No adjacent bots found! Move bots within 1 distance of each other to link them.'); } }; const toggleSelect = (id: string) => { setError(null); setSelectedIds((prev) => { const next = prev.includes(id) ? prev.filter((item) => item !== id) : [...prev, id]; if (!next.includes(leaderId)) { setLeaderId(next[0] || ''); } return next; }); }; const handleSubmit = async (e: React.FormEvent) => { e.preventDefault(); if (selectedIds.length < 2) { setError('Select at least 2 bots to form a party'); return; } if (!leaderId) { setError('You must agree on a party leader'); return; } setLoading(true); setError(null); try { await onFormParty(selectedIds, leaderId, partyName.trim() || undefined); onClose(); } catch (err: unknown) { if (err instanceof Error) { setError(err.message); } else { setError('Failed to form party'); } } finally { setLoading(false); } }; return (
🤝

Form a Bot Party

Bots can unite into a linked squad if they are within 1 distance of each other. The agreed{' '} Party Leader controls group movement. If defeated, the leader is killed (-1 score) & respawns, and the remainder elects a new leader.
{/* Party Name */}
setPartyName(e.target.value)} placeholder="e.g. NeonVipers" className="w-full bg-slate-950 border border-slate-700 rounded-lg px-3 py-2 text-xs font-mono text-slate-100 focus:outline-none focus:border-sky-500" />
{/* Member Selection */}
{players.map((p) => { const isChecked = selectedIds.includes(p.id); return (
toggleSelect(p.id)} className={`flex items-center justify-between p-2 rounded-lg cursor-pointer border text-xs transition-colors ${ isChecked ? 'bg-sky-950/60 border-sky-500/70 text-sky-200' : 'bg-slate-900/60 border-slate-800 text-slate-400 hover:border-slate-700' }`} >
{p.name} ({p.x}, {p.y})
{}} className="rounded border-slate-700 text-sky-500" />
); })}
{/* Agreed Leader Selection */} {selectedIds.length > 0 && (
{selectedIds.map((id) => { const bot = players.find((p) => p.id === id); if (!bot) return null; const isLeader = leaderId === id; return ( ); })}
)} {error && (
{error}
)}
); };