Files
ProLibertateGames/docs/UI-catan.md
Claude f9b793d56e
CI / Build and check (pull_request) Successful in 8m3s
CI / Deploy to Cloudflare Pages (pull_request) Skipped
Merge origin/dev into feature/cities-and-knights
Brings in open trade offers and counter-offers. Cities & Knights gets the same
flow for goods: OfferGoods may be open to the table, CounterGoods answers an
offer, and AcceptCounter / DismissCounters are shared with the base game.

Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_016eTor2tpxKb4dXbd8RqnvZ
2026-10-17 14:45:39 +00:00

10 KiB

Katan UI — how it is built, and how to change it

The Katan screen (app/src/main/java/org/prolibertate/games/ui/game/catan/) is built in layers so that look, wording, layout and interaction can each be changed on their own. If you are an agent asked to restyle, retheme, re-copy or re-lay-out Katan, start here and touch only the layer you were asked about.

The rules live elsewhere and are not part of the UI: game/catan/ is a pure state machine (see RULES-catan.md). Nothing in ui/game/catan/ decides what is legal. The engine's legal-move list is the single source of truth, so the UI can only ever offer a move the rules would accept.

The layers

Layer File Owns Change it to…
Style CatanStyle.kt Every colour, proportion and the light/dark palettes Re-skin: new terrain colours, seat colours, piece sizes, token look
Copy CatanText.kt Every sentence, button label and name shown Reword, localise, rename resources
Geometry CatanGeometry.kt Engine lattice → pixels, and pixel → nearest vertex/edge/hex Change board scale, margin, harbour placement, tap reach (rarely)
Interaction CatanInteraction.kt What is tappable, what a tap means, which buttons are live Change how something is selected (never whether it is legal)
Board drawing CatanBoardView.kt The Canvas: tiles, tokens, ports, robber, roads, pieces, highlights Change how a piece is drawn (art, shapes, animation)
Panels CatanPanels.kt Resource tiles, hand, player cards, dice, action bar, log Restyle or rearrange widgets
Dialogs CatanDialogs.kt Discard, robber victim, play card, trade, trade reply, game over Change a dialog's flow or look
Screen CatanScreen.kt Wiring to MatchController, portrait/landscape layout, which dialog shows Change the layout or add a new element
Previews CatanPreviews.kt Android Studio previews of real positions Iterate on the look without a match

Everything outside this folder that mentions Katan is one-line wiring: PlayScreen.kt (builds the MatchController), GameSetupScreen.kt (players and points-to-win options), Catalog.kt (the catalogue entry).

Recipes

Change colours or sizes. Edit CatanStyle.kt. LightStyle is the reference palette; DarkStyle is derived from it. Metrics (roadWidth, settlementSize, tokenRadius, …) are fractions of the hex radius, so they scale with the screen. To try a whole new theme, provide it at the root: CompositionLocalProvider(LocalCatanStyle provides myStyle) { … }.

Change wording or translate. Edit CatanText.kt. Composables never build a sentence themselves. prompt(...) is the one-line "what is happening / what do I do" text; it is a pure function of the state, with tests in CatanTextTest.

Redraw a piece (settlement, road, robber, port, token). Edit the matching draw… function in CatanBoardView.kt. Positions come from BoardLayout, so you only decide the shape. Keep the hit target generous (see Accessibility).

Rearrange the screen. Edit CatanScreen.kt. It builds four independent pieces (board, players, promptRow, handAndActions) and arranges them differently in portrait and landscape. Moving or dropping a piece does not affect the others.

Add a widget. Make it a stateless composable in CatanPanels.kt (values in, lambdas out). Do not give it the MatchController.

Change how a selection works (for example a one-tap build instead of a build-mode toggle). Edit CatanInteraction.kt and its tests, then adjust the screen's UI state. The legal-move list stays the gate.

Rules of the road

  1. Never put a rule in a composable. If a button's enabled state or a tap's result depends on game logic, it goes in CatanInteraction (pure, tested) and is derived from the engine's legal list or CatanRules.
  2. Never hard-code a colour or a string outside CatanStyle / CatanText.
  3. Stateless by default. Panels and dialogs take values and report events. The only state they may own is the transient selection they are helping the player make (cards picked in a trade, for example).
  4. Hidden information. The local view comes from CatanRules.viewFor: other players' development cards and the deck are empty on a client. Do not show the size of the deck; it is not known to a client. Card counts (resourceCount, devHandCounts) are public.
  5. The state is immutable. Never mutate it; submit a move.
  6. The layout must not move. Everything outside the board has a fixed height, and the board takes whatever is left, so the island's scale is the same all game. Concretely: a control is enabled or disabled, never present or absent (the action bar always shows every button); text that can vary in length is pinned to a fixed number of lines (minLines = maxLines); a line that is sometimes empty is still drawn (a non-breaking space), as the award line on a player card is. A change that makes any non-board element taller or shorter at runtime will rescale the board under the player's thumb.

Interaction model (what the player actually does)

Phase The player…
Setup taps a highlighted crossing (settlement), then a highlighted edge beside it (road). Cancel re-picks the settlement
Roll presses Roll dice
Seven Discard dialog (forced); then taps a hex to move the robber; a victim dialog appears only if more than one player can be robbed
Main turn arms Road / Settlement / City and taps a highlighted spot; Buy card; Play card; Trade; End turn
Road Building taps highlighted edges, or Done with roads
Offered a trade Accept / Decline / Counter… dialog (forced); Counter… opens a picker pre-filled with the mirror of the offer
Counter-offers received Counter-offers dialog: Accept beside the one to take, or No deal
Game over result dialog

Trade offers are free-form (two card multisets), so they are validated with CatanRules.isLegalMove, not looked up in the legal list.

Testing

Gradle does not run in the agent sandbox; CI is the compile and test check (dispatch ci.yml on your branch). What is covered:

  • CatanGeometryTest — the island fits any box, edges and corners are one radius, hit-testing finds the right vertex/edge/hex.
  • CatanInteractionTest — targets and tap outcomes for setup, building, free roads, the robber (including the victim choice), discards, bank trades, offers, development cards and trade replies, all against real engine states.
  • CatanTextTest — the prompts and tallies.

Drawing and dialogs are not unit-tested; use the previews in CatanPreviews.kt and play a game on a dev build.

Accessibility notes

  • Tap targets: vertices and edges accept a tap within tapReach (0.7) hex radii, about 23 dp on a phone. If you shrink the board, keep that physical size.
  • Seat colours are blue, orange, white and purple, each outlined in near-black so they read on any terrain. Colour is the only piece identity today; adding a shape or letter per seat would help colour-blind players (a good first task).
  • The board has a content description but no per-hex semantics; a screen-reader mode would need one.

Cities & Knights on the screen

The same layers carry the expansion; the screen only adds pieces, and none of them decides what is legal — each reads the engine's legal-move list.

Layer What it adds
CatanStyle.kt commodity colours, a colour per improvement track, the wall and barbarian colours, the awake-knight halo
CatanText.kt goods, track, knight and progress-card names and help text; the prompts for every chore
CatanInteraction.kt BuildKind.WALL / KNIGHT; chore taps; the moving-knight and armed-card modes; knightActions, progressHand, goodsBankTrades
CatanBoardView.kt shields for knights (strength dots; faded when asleep, white halo when awake), dashed walls, a pennant for each metropolis, the merchant coin, a faint robber until the first attack
CatanPanels.kt commodity tiles in the hand, the barbarian strip, the yellow / red / event dice, the extra player-card lines, the Improve and Progress buttons
CatanCkDialogs.kt goods picker (discard, wedding, harbour), improvements, progress cards, the knight menu, deck / aqueduct / spy / drop choices, goods trade
CatanScreen.kt the visual state: knight menu, moving knight, armed card, and which chore dialog shows

Interaction model. Tap one of your own knights (it gets a yellow ring when it can do something) for its menu: wake, promote, move or chase the robber. Move arms the board with the crossings the knight can reach. A progress card that needs a place on the board (Engineer, Medicine, Merchant, Diplomat, Intrigue, Inventor) closes the dialog and arms the board; the next tap plays it. Chores that are answered with a place — a city to lose, a metropolis site, a driven knight's refuge, a free knight — light the crossings on the board and the prompt line says what to tap; the others open a dialog that cannot be dismissed, because the game is waiting on it.

Testing. CatanCkInteractionTest plays whole games and checks that for every legal move at every position some control (a tap, a button or a dialog choice) produces it, so a legal move with no way to make it — a stuck phone — fails the build.

Status and known gaps

The screen is playable and feature-complete for the engine's rules, but is unverified on two real devices, so the catalogue entry is devPreview (openable on a dev build, hidden from production). To release it: verify two-device play, then in Catalog.kt set available = true and remove devPreview = true, and update CatalogTest.

Not done yet, in rough priority order:

  1. Two-device verification (host + guest over the LAN) and a full game on a phone and a tablet.
  2. Feedback on events: dice-roll animation, a highlight on hexes that produced, resource-gain flourishes, haptics. The state carries the dice and a text log; it does not say which hexes paid out, so that needs either a diff of two states or an engine addition.
  3. Art: terrain textures, resource and harbour icons, a real robber pawn.
  4. A timer or cancel button on offers (the engine has neither).
  5. A board that shows the last move (a faint highlight of what just changed).
  6. Landscape polish on large tablets; the right-hand panel scrolls but is plain.