'use client'; import { useState } from 'react'; import { Button } from '@/components/ui/button'; import { Input } from '@/components/ui/input'; import { HelpTip } from './help-tip'; import { GRAPH, TERRITORIES, mobileRouteDistance, splitLocation, territory, } from '@/game/board'; import type { Action, GameView } from '@/game/engine'; import { quoteSandmasterStronghold } from '@/game/sandmaster-movement'; import { mobileStrongholdCollectionCapacity } from '@/game/mobile-stronghold-collection'; import { isBoardNoFieldLocation } from '@/game/richese-no-field'; const label = (key: string) => { const loc = splitLocation(key); return `${territory(loc.territory).name}${loc.sector ? ` · sector ${loc.sector}` : ''}`; }; export function MobileStronghold({ game: g, act, busy, card, }: { game: GameView; act: (action: Action) => void; busy: boolean; card?: string; }) { const pointer = g.mobileStronghold?.location; const placement = !pointer; const blocked = !placement ? g.homeworldMobility?.mobileStrongholdBlocked : null; const [destination, setDestination] = useState('polar_sink:0'); const [route, setRoute] = useState(pointer ? [pointer] : []); const [collect, setCollect] = useState(true); const [collectionEdits, setCollectionEdits] = useState>({}); const [skillSelections, setSkillSelections] = useState>({}); const max = card ? 2 : 3; const current = route.at(-1) ?? ''; const choices = placement ? TERRITORIES.filter((t) => t.type !== 'stronghold').flatMap((t) => t.sectors.map((s) => `${t.id}:${s}`), ) : (GRAPH[current] ?? []).filter( (key) => mobileRouteDistance([...route, key]) <= max && splitLocation(key).sector !== g.storm && splitLocation(route[0]).sector !== g.storm, ); const me = g.players.find((p) => p.id === g.me)!; const sandmaster = placement ? null : quoteSandmasterStronghold(g, me.id, route); const skillPiles = collect && sandmaster ? Object.entries(sandmaster.piles).flatMap(([id, keys]) => { const chosen = skillSelections[id]; return chosen === false ? [] : [typeof chosen === 'string' && keys.includes(chosen) ? chosen : keys[0]]; }) : []; const remaining: Record = {}; const capacity = mobileStrongholdCollectionCapacity(g.players.map(player => { const marker = player.noField?.deployed; return { ...player, noField: marker && isBoardNoFieldLocation(marker.location) ? { deployed: { controller: marker.controller, location: marker.location } } : null }; })); const collections = route.map((key, index) => { const before = remaining[key] ?? Math.max(0, (g.spice[key] ?? 0) - Number(skillPiles.includes(key))); const maximum = Math.min(before, capacity); const editKey = `${index}:${key}`; const raw = collectionEdits[editKey]; const selected = raw === undefined ? maximum : Number(raw); const legal = !collect || (raw !== '' && Number.isSafeInteger(selected) && selected >= 0 && selected <= maximum); const amount = collect && legal ? selected : 0; remaining[key] = before - amount; return { location: key, editKey, amount, maximum, raw, legal }; }); const preview = collections.reduce((total, choice) => total + choice.amount, 0); const valid = placement || (!blocked && route.length > 1 && mobileRouteDistance(route) <= max && route.every((key) => splitLocation(key).sector !== g.storm) && territory(splitLocation(current).territory).type !== 'stronghold'); return (
{blocked &&

{blocked}

}

{placement ? 'Point your stronghold at a sector in any non-stronghold territory. Placement does not collect spice.' : `Move the stronghold up to ${max} territories. Choose each sector along the route; the forces stay inside.`}{' '}

{!placement && pointer && splitLocation(pointer).sector === g.storm && (

The pointing sector is in storm. Keep the current position until the storm clears.

)} {!placement && ( <>
    {collections.map((choice, index) => (
  1. {!choice.legal &&

    Choose a whole amount from 0 to {choice.maximum}.

    }
  2. ))}

{mobileRouteDistance(route)} / {max} territories ·{' '} {collect ? preview + skillPiles.length : 0} spice to collect

)} {!placement && ( <> {sandmaster && Object.keys(sandmaster.piles).length > 0 && (
Sandmaster · one spice per entered territory

Provisional owner allocation: choose one existing pile or decline each collection. Sandmaster resolves before the faction’s route collection; this is not a publisher allocation ruling.

{Object.entries(sandmaster.piles).map(([id, keys]) => ( ))}

{skillPiles.length} Sandmaster spice; {collect ? preview : 0} native faction spice.

)} {!valid && route.length > 1 && (

Finish in a non-stronghold territory.

)} )} {!placement && !card && ( )}
); }