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
135 lines
7.6 KiB
Markdown
135 lines
7.6 KiB
Markdown
# 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.
|
|
|
|
## 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:
|
|
|
|
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.
|