# 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](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** dialog (forced) | | 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. Open trade to the whole table and counter-offers (an engine limitation too). 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.