Adds the Katan screen on top of the WALA-119 engine, built in separable layers so the look, wording, layout and interaction can each be changed alone: - CatanStyle: every colour and proportion (light/dark), via LocalCatanStyle - CatanText: every word on screen (pure functions of state) - CatanGeometry: lattice -> pixels and tap hit-testing (pure, unit-tested) - CatanInteraction: what is tappable and what a tap means, derived only from the engine's legal-move list (pure, unit-tested) - CatanBoardView / CatanPanels / CatanDialogs: stateless composables - CatanScreen: wiring to MatchController, portrait and landscape layouts - CatanPreviews: Android Studio previews of real positions - docs/UI-catan.md: how to adapt each layer, rules of the road, known gaps Wiring: PlayScreen branch, GameSetupScreen options (3-4 players, game to 5/8/10/12), and a GameDescriptor.devPreview flag so dev builds can open Katan while production still hides it until two-device play is verified. Tests: CatanGeometryTest, CatanInteractionTest, CatanTextTest, CatalogTest. Co-Authored-By: Claude Sonnet 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_01JdMNbY5hLR7FjtfEE712xG
7.6 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
- 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'slegallist orCatanRules. - Never hard-code a colour or a string outside
CatanStyle/CatanText. - 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).
- 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. - The state is immutable. Never mutate it; submit a move.
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.
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:
- Two-device verification (host + guest over the LAN) and a full game on a phone and a tablet.
- 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.
- Art: terrain textures, resource and harbour icons, a real robber pawn.
- Open trade to the whole table and counter-offers (an engine limitation too).
- A board that shows the last move (a faint highlight of what just changed).
- Landscape polish on large tablets; the right-hand panel scrolls but is plain.