Katan: playable screen, setup options, dev-preview catalogue gate
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
This commit is contained in:
@@ -42,6 +42,8 @@ Naming: the catalogue id, lowercase, e.g. `crazy8s` → `RULES-crazy8s.md`.
|
||||
|
||||
- Build, CI, releases and OTA are described in the CI section of the root
|
||||
[README](../README.md).
|
||||
- [UI-catan.md](UI-catan.md) — how the Katan screen is layered, and how to restyle,
|
||||
re-copy or re-lay-out it without touching the rules.
|
||||
- `licenses/` — third-party licences that must travel with the app
|
||||
(Uncial Antiqua under the SIL OFL; the app itself is GPL-3.0, see `/LICENSE`).
|
||||
- Repo-level agent notes live in [`CLAUDE.md`](../CLAUDE.md) at the root.
|
||||
|
||||
+4
-6
@@ -19,8 +19,7 @@ behaviour is pinned by `CatanBoardTest`, `CatanRulesTest` and `CatanAiTest`.
|
||||
- **Points to win — *toggle*.** Defaults to 10. `CatanOptions.pointsToWin`, at
|
||||
least 2. A lower target makes a test game short.
|
||||
|
||||
The setup screen that exposes these lands with the screen task; the engine reads
|
||||
them from the table configuration today.
|
||||
The setup screen exposes both (players: 3 or 4; game to 5, 8, 10 or 12 points).
|
||||
|
||||
## Board
|
||||
|
||||
@@ -291,10 +290,9 @@ timing rules; Longest Road and Largest Army worth five; victory at ten.
|
||||
- **No player-to-player trade over the wire beyond accept/decline.** The engine
|
||||
supports it; whether the UI surfaces it is the screen task's concern.
|
||||
- **No trading away a development card.** Not part of the base game.
|
||||
- **The setup screen is not part of this task.** `CatanOptions` (players, target
|
||||
points) is read from the table configuration; the screen that sets it, and the
|
||||
catalogue descriptor's `available` flag, land with the screen and verification
|
||||
tasks.
|
||||
- **The screen is built, not yet verified on two devices.** The Katan screen and
|
||||
setup options exist (see [UI-catan.md](UI-catan.md)); the catalogue entry is a
|
||||
dev-channel preview until two-device play has been verified.
|
||||
- **The AI does not look ahead.** It scores one move deep with a fixed resource
|
||||
valuation. It plays legally and plausibly; it is not a strong opponent, and no
|
||||
strength claim is made.
|
||||
|
||||
@@ -0,0 +1,134 @@
|
||||
# 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.
|
||||
Reference in New Issue
Block a user