Skip to content

Grey out menu options the game would refuse, with the reason - #168

Merged
dmccoystephenson merged 1 commit into
mainfrom
feature/greyed-out-unavailable-options
Aug 4, 2026
Merged

Grey out menu options the game would refuse, with the reason#168
dmccoystephenson merged 1 commit into
mainfrom
feature/greyed-out-unavailable-options

Conversation

@dmccoystephenson

@dmccoystephenson dmccoystephenson commented Aug 3, 2026

Copy link
Copy Markdown
Member

Summary

An option the player had earned but couldn't use right now looked exactly like one they could. The only way to find out you were too tired to fish was to pick Fish and be told so afterwards — the same for buying gear you can't afford, withdrawing from an empty account, or guessing a dice face before staking anything.

Every front-end now shows those options as unpickable and says why on the row itself.

  • showOptions takes an optional {optionNumber: reason} mapping. Call sites key by 1-based option number because these menus are built by appending — len(optionList) right after an append is always the row just added, so the numbers can't drift as options appear and disappear with progression. BaseUserInterface.unavailableReasons() converts it once into a list parallel to the options.
  • Web (web/client.js, shared by the server-backed and Pyodide front-ends): a greyed-out, disabled button carrying the reason in its own span, plus a tooltip; its number key is inert too.
  • Console: the row is tagged (unavailable: ...), and picking it names the blocker instead of "Try again!".
  • pygame: the row is drawn in grey, the arrow keys step over it, and ENTER/number keys can't confirm it.

Marked so far: fishing without the energy for an hour; selling with an empty hold; bait/rod that's unaffordable or already maxed out; drinking, buying a boat, a property or a home rung you can't pay for; repairing or upgrading a hull whose bill you can't cover; depositing with an empty purse; withdrawing from an empty account; guessing a dice face before placing a bet.

Safety: the game's own post-choice guards are left untouched, so nothing depends on a front-end honouring this. canAfford/hasEnergy are used as the predicates, so operator mode never sees a greyed-out button. Marking every option is treated as a call-site bug and dropped rather than leaving a menu that accepts nothing, and an out-of-range option number raises with a message naming the fix.

Two magic numbers picked up names along the way, since the new gate and the existing charge had to agree: docks.FISHING_ENERGY_COST and tavern.DRINK_COST.

Test plan

  • python3 -m compileall -q src tests
  • SDL_VIDEODRIVER=dummy SDL_AUDIODRIVER=dummy python3 -m pytest --verbose -vv --cov=src --cov-report=term-missing --cov-report=xml:cov.xml770 passed, 97% coverage (up from 731)
  • black src tests + autoflake per format.sh
  • All three front-ends touched and covered: new tests for the shared contract (tests/ui/test_baseUserInterface.py), the console rows and refusal message (tests/ui/test_userInterface.py), pygame row text/navigation/keyboard (tests/ui/test_pygameUserInterface.py), and the web screen contract (tests/ui/test_webUserInterface.py). tests/web/test_clientParity.py asserts the shared browser client actually consumes the contract, so it can't be sent server-side and ignored client-side.
  • Verified in a real browser client: ran web/client.js against a DOM stub — an options screen with unavailable: ["needs 10 energy - sleep at home", null, null] renders [1] Fish — needs 10 energy - sleep at home as class="unavailable", disabled=true, no click handler, while options 2 and 3 stay live. Pressing 1 sends nothing; pressing 2 sends "2".
  • Ran examples/web_app.py and confirmed GET /state publishes the new unavailable list alongside options.
  • No Player/Stats/TimeService fields changed, so no schemas/*.json or *JsonReaderWriter updates are needed.
  • README.md updated where the staged reveal is described.

🤖 Generated with Claude Code


drafted by Claude on behalf of Daniel Stephenson

An option the player had earned but couldn't use right now looked exactly
like one they could: the only way to find out you were too tired to fish
was to pick "Fish" and be told so afterwards. Every front-end now shows
those options as unpickable and says why on the row itself.

showOptions takes an optional {optionNumber: reason} mapping, converted
once in BaseUserInterface into a list parallel to the options. The web
front-end publishes it to the browser, which renders a greyed-out,
disabled button carrying the reason (and ignores its number key); the
console and pygame front-ends tag the same rows, skip them, and name the
blocker instead of "Try again!".

Marked so far: fishing without the energy for an hour, selling with an
empty hold, gear that's unaffordable or already maxed out, drinking or
buying a boat/property/home you can't pay for, repairing or upgrading a
hull whose bill you can't cover, depositing with an empty purse,
withdrawing from an empty account, and guessing a dice face before
staking anything.

The game's own post-choice guards are left untouched, so nothing depends
on a front-end honouring this. Marking every option is treated as a bug
and dropped rather than leaving a menu that accepts nothing.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@dmccoystephenson
dmccoystephenson merged commit c715b38 into main Aug 4, 2026
1 check passed
@dmccoystephenson
dmccoystephenson deleted the feature/greyed-out-unavailable-options branch August 4, 2026 03:49
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant