'use client'; import { useEffect, useRef, useState, useSyncExternalStore } from 'react'; import { BattleWheel } from './battle-wheel'; import { CardInspector, CardRules } from './card-inspector'; import { PHASES } from '@/game/catalog'; import { MovementLesson, TraitorLesson } from './introduction-practice'; import { BiddingLesson } from './introduction-bidding'; import { AllianceLesson } from './introduction-alliance'; import { CharityLesson, RevivalLesson } from './introduction-resources'; import { StormLesson, SpiceBlowLesson } from './introduction-opening'; import { INTRODUCTION_CARDS, INTRODUCTION_LEADERS, INTRODUCTION_STEPS, INTRODUCTION_STORAGE_KEY, introductionBattle, introductionCollection, introductionShipment, newIntroduction, restoreIntroduction, type IntroductionState, } from '@/game/introduction'; import styles from './introduction.module.css'; const subscribeHydration = () => () => {}; const clientSnapshot = () => true; const serverSnapshot = () => false; export function Introduction() { const hydrated = useSyncExternalStore(subscribeHydration, clientSnapshot, serverSnapshot); return
← Return to Arrakis

LEARN TO PLAY · BASIC GAME

Your first steps on Arrakis

Practice the Basic phases, from the storm and spice blow to alliances, resources, bidding, movement, battle and collection, then take a seat at a real table. These are separate examples; they do not change any saved game.

{/* Remount on schema changes so a development hot update also migrates old state. */} {hydrated ? : Restoring your lesson…}
; } function IntroductionLessons() { const [{ state, storageFailed }, setProgress] = useState(() => { try { return { state: restoreIntroduction(localStorage.getItem(INTRODUCTION_STORAGE_KEY)) ?? newIntroduction(), storageFailed: false }; } catch { return { state: newIntroduction(), storageFailed: true }; } }); const heading = useRef(null); const focusNext = useRef(false); useEffect(() => { if (focusNext.current) { heading.current?.focus(); focusNext.current = false; } }, [state.step]); const update = (patch: Partial) => { const next = { ...state, ...patch }; let failed = false; try { localStorage.setItem(INTRODUCTION_STORAGE_KEY, JSON.stringify(next)); } catch { failed = true; } setProgress({ state: next, storageFailed: failed }); }; const go = (step: number) => { focusNext.current = true; update({ step }); }; const shipment = introductionShipment(state); const battle = state.revealed ? introductionBattle(state) : null; const collection = introductionCollection(state); const lesson = INTRODUCTION_STEPS[state.step]; const won = battle?.winner === 'you'; const survivingForces = battle ? (won ? 6 - state.dial : 0) : 0; return <>

{state.step + 1} / {INTRODUCTION_STEPS.length}

{lesson.title}

{state.step === 0 && <>

Each faction has its own powers. Practice Basic bidding as Fremen, then shipping and battles as Atreides with ordinary forces. Advanced combat and expansion powers come later.

On the table

Everyone can see the board, forces, storm and revealed cards. Your spice, Treachery hand and Traitors stay private unless a rule reveals them.

Your objective

Normally, hold three strongholds alone or four together with your ally at the Mentat Pause. Player-count and faction victory rules can change that target; the live table shows your current victory progress.

A turn follows these phases

    {PHASES.map(phase =>
  1. {phase}
  2. )}

The table tells you who must act. Your decisions can wait while another player seals a plan or answers a power. Automatic effects show in the chronicle.

} {state.step === 1 && } {state.step === 2 && } {state.step === 3 && } {state.step === 4 && } {state.step === 5 && } {state.step === 6 && } {state.step === 7 && <>

You have 8 spice and six available reserve forces. Ship from reserves before moving on the board. This example has no discount, storm, occupancy restriction or reacting faction power.

update({ shipment: Number(e.target.value), shipped: false })} />

{state.shipped ? 'Shipment complete' : 'Review the cost'}

{state.shipment} forces cost {shipment.cost} spice.

{shipment.affordable ?

{state.shipped ? 'You have' : 'You would have'} {shipment.remaining} spice left.

:

You need {shipment.cost - 8} more spice. Reduce the force count or choose the stronghold.

} {state.shipped &&

{state.shipment} forces arrived; {6 - state.shipment} of your six available reserves remain. You could now move one group already on the board, including these newly shipped forces.

}

Ordinary movement reaches an adjacent territory. Ornithopters and faction powers can extend its range. Inspect the live destination guide before committing.

} {state.step === 8 && } {state.step === 9 && <>

A separate battle: you are the aggressor in Wind Pass with six ordinary forces and {INTRODUCTION_LEADERS.you.name}, strength {INTRODUCTION_LEADERS.you.strength}. Your opponent also has six forces. Neither side calls a Traitor or uses a faction power in this example.

update({ dial })} />
{!battle ? <>

Opponent’s plan is sealed

Choose how much to risk. In a normal victory you lose the forces you dial; in defeat you lose all your forces in this battle. In Basic play, ordinary forces need no spice support.

Your selected leader contributes strength only if they survive the weapon exchange. Plans reveal together.

: <>

{won ? 'You won the battle' : 'Your opponent won'}

The opponent dialed 3 with {INTRODUCTION_LEADERS.opponent.name} (strength {INTRODUCTION_LEADERS.opponent.strength}), a {INTRODUCTION_CARDS.weapon.name} and no defense.

{battle.leaderDeaths.attacker ? 'Your Snooper stops poison, so it cannot stop this projectile weapon. Your leader dies and adds no strength.' : 'Your Shield stops the projectile weapon. Your leader survives and adds five strength.'}

Your total: {battle.scores!.attacker}. Opponent: {battle.scores!.defender}. {battle.scores!.attacker === battle.scores!.defender && 'The aggressor wins this ordinary tie.'}

{6 - survivingForces} of your forces go to the Tanks; {survivingForces} remain in Wind Pass. {won ? 'The opponent loses all six forces.' : 'The opponent loses the three dialed forces and keeps three.'}

{won ? 'A normal winner may keep or discard the cards they used.' : 'The defeated player discards the cards used in the plan.'} {battle.bounty?.amount ? `The winner also collects ${battle.bounty.amount} spice for the killed leader.` : 'Neither leader died, so there is no leader bounty.'}

}
} {state.step === 10 && } {state.step === 11 && <>

A fresh position has 8 spice in Wind Pass, sector 14. You have surviving forces in that same sector, outside the storm. Collection uses the forces still on the board after battles.

update({ collectors: Number(e.target.value), collected: false })} />

{state.collected ? 'Collection complete' : 'Collection capacity'}

Each ordinary force can collect {state.city ? 3 : 2} spice{state.city ? ' because you occupy Arrakeen' : ''}.

{state.collected ? 'You collected' : 'You can collect'} {collection.amount} spice. {collection.remaining} {state.collected ? 'remains' : 'would remain'} on the board.

Spice beyond your capacity stays for a later turn. In a real table this calculation is automatic.

} {state.step === 12 && <>

You can revisit any example. This introduction covers ordinary alliances, bidding, core resources, movement and battle choices; it does not teach every faction, card, alliance power, storm exception or expansion.

  • Create a private room, choose a faction, and add friends or AI players. Share the room invitation.
  • Mark yourself ready after reviewing the selected rules. The host begins once all seats are ready.
  • Inspect your private Treachery hand and Traitors. Use the phase guide when a decision is unfamiliar.
  • Use Protect your saved seat and keep its recovery kit private. An invitation identifies the room; the kit restores your seat after cookie loss.

Refresh restores a live game through its saved seat. If a request has an uncertain result, use the offered retry rather than repeating the game action.

}
Read the related rules {state.step < INTRODUCTION_STEPS.length - 1 && }
{storageFailed ? 'Browser storage is unavailable. You can still practice, but a refresh may reset this lesson.' : 'Your lesson position and practice choices are saved in this browser.'} ; }