'use client'; import { useState } from 'react'; import { Button } from '@/components/ui/button'; import { HelpTip } from './help-tip'; import { GRAPH, TERRITORIES, MOBILE_LOCATION, mobileRouteDistance, splitLocation, territory, } from '@/game/board'; import type { Action, GameView } from '@/game/engine'; import { quoteSandmasterStronghold } from '@/game/sandmaster-movement'; 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 [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]]; }) : []; let preview = 0; const remaining: Record = {}; for (const key of route) { const before = remaining[key] ?? Math.max(0, (g.spice[key] ?? 0) - Number(skillPiles.includes(key))); const amount = Math.min(before, (me.forces[MOBILE_LOCATION] ?? 0) * 2); remaining[key] = before - amount; preview += amount; } const valid = placement || (!blocked && route.length > 1 && 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 && ( <>
    {route.map((key) => (
  1. {label(key)} {g.spice[key] ? ` · ${g.spice[key]} spice` : ''}
  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 && ( )}
); }