import { INTRODUCTION_LEADERS, introductionTraitor, type IntroductionState } from '@/game/introduction'; import { INTRODUCTION_MOVE_DESTINATIONS, introductionMovement } from '@/game/introduction-movement'; import { splitLocation, territory } from '@/game/board'; import { LeaderInspector } from './leader-inspector'; import styles from './introduction.module.css'; type Props = { state: IntroductionState; update: (patch: Partial) => void }; export function MovementLesson({ state, update }: Props) { const move = introductionMovement(state); return <>

You have five ordinary Atreides forces in Red Chasm, sector 7. Move some or all as one group. This separate practice position has no allies or reacting powers.

update({ moveForces: Number(e.target.value), moved: false })} />

{state.moved ? 'Movement complete' : 'Check the route'}

Your range is {move.range} {move.range === 1 ? 'territory' : 'territories'}{state.moveCity ? ' because you already occupy Arrakeen.' : '.'}

Guild and Emperor each occupy Tuek’s Sietch in this position. A third faction cannot enter or pass through that stronghold.

{move.blocked ?

{move.blocked}

: <>

The shortest open route to {move.name}, sector {move.sector}, crosses {move.steps} {move.steps === 1 ? 'territory' : 'territories'}.

{state.moved ?

{move.arrived} forces arrived. {move.sourceRemaining} remain in Red Chasm. {state.moveCity && 'The separate Arrakeen force stays there. '}Movement costs no spice and uses your one ordinary move.

:

You can move this group. Forces left behind stay in Red Chasm.

} } {state.moved && }

Crossing a territory boundary uses range; crossing a sector line within one territory does not add range. Storm sectors and stronghold capacity still constrain the path. Ornithopters let any one group move up to three territories, even when it starts away from the city.

Change a control to start a fresh example. Live movement has further faction, alliance and card rules; this exercise uses ordinary Basic movement.

; } export function TraitorLesson({ state, update }: Props) { const resolved = state.traitorStage === 'resolved'; const revealed = state.traitorStage !== 'plans'; const outcome = resolved ? introductionTraitor(state) : null; const remaining = (player: string) => !outcome ? 6 : outcome.destroyedArmies.includes(player) ? 0 : 6 - (outcome.winner === player ? outcome.basicWinnerLosses ?? 0 : 0); return <>

A fresh battle has six forces on each side. You privately hold the Traitor for {INTRODUCTION_LEADERS.opponent.name}. A matching Traitor can overturn a losing plan, but you choose whether to reveal it after both plans appear.

This chooses the teaching scenario. At a real table, you cannot see the opponent’s private Traitors.

{!revealed ? <>

Your example plan uses {INTRODUCTION_LEADERS.you.name}, dial 2, no weapon and a Snooper. The opposing plan stays face down until you reveal both.

: <>

Both plans are revealed

You: {INTRODUCTION_LEADERS.you.name}, dial 2, Snooper, no weapon.

Opponent: {INTRODUCTION_LEADERS.opponent.name}, dial 3, projectile weapon, no defense.

{!resolved && <> } }
{!outcome ? <>

{revealed ? 'Your Traitor matches' : 'Wait for the reveal'}

{revealed ? 'The opposing leader matches the card you hold. You may call it now or keep it secret. Ordinary weapon and score resolution waits for the Traitor decisions.' : 'Having a Traitor is not enough: the opposing plan must use that leader. Your card remains private until you call it.'}

: <>

{outcome.result === 'mutualTraitors' ? 'Both leaders betrayed their factions' : outcome.winner === 'you' ? 'Your Traitor wins the battle' : 'Your opponent wins'}

{outcome.result === 'mutualTraitors' ? 'Both leaders die and both armies are destroyed. Neither side wins or receives a leader bounty.' : outcome.result === 'traitor' ? `${outcome.winner === 'you' ? 'Your' : 'The opponent’s'} successful call wins before weapons or battle scores resolve. The winner keeps every force and its leader; the betrayed leader dies and its army is destroyed.` : 'You declined and the opponent made no call. The projectile kills your leader because a Snooper only stops poison. Ordinary scores decide the battle.'}

{outcome.scores &&

Your strength: {outcome.scores.attacker}. Opponent: {outcome.scores.defender}.

}

Your forces remaining: {remaining('you')}. Opponent: {remaining('opponent')}.

{outcome.bounty?.amount ? `${outcome.bounty.player === 'you' ? 'You collect' : 'The opponent collects'} ${outcome.bounty.amount} spice for the killed leader.` : 'No leader bounty is paid.'}

{outcome.winner ? 'The defeated player discards the cards used in the plan. The winner may keep or discard its used card.' : 'Both sides discard the cards used in their plans.'} A called Traitor card stays face up with its owner.

}
; }