Skip to content

feat(core): integrate Countdown Engine game mode (#52) - #53

Merged
ap0ught merged 12 commits into
mainfrom
feat/issue-52-countdown-engine
Aug 8, 2026
Merged

feat(core): integrate Countdown Engine game mode (#52)#53
ap0ught merged 12 commits into
mainfrom
feat/issue-52-countdown-engine

Conversation

@ap0ught

@ap0ught ap0ught commented Aug 2, 2026

Copy link
Copy Markdown
Member

Summary

Integrates the platform-neutral Open-Source Countdown Engine specification into flip7core as a new synchronized game mode (#52).

Key Changes

  • Core Engine Components (core/include/flip7/countdown_engine.hpp, core/src/countdown_engine.cpp):

    • NumbersRound: Target selection (101–999), 6 tile selection (large & small), solution submission, and standard Countdown distance scoring (10/7/5 points).
    • LettersRound: Vowel and consonant draws (9 total tiles), sub-word validation, and longest-valid-word evaluation (18 points for 9-letter words).
    • ConundrumRound: Anagram scramble, buzz-window management, and single-attempt resolution (10 points).
    • CountdownMatchEngine: Multi-round match lifecycle, score tracking, and ADR-005 Leader-Based Host Migration (the player with the higher score becomes host between rounds).
  • Protocol Integration (core/include/game_selection.h, core/include/flip7/protocol.hpp):

    • Added ActiveGameKind::Countdown = 3 to support game mode selection across CYD ESP32 firmware and JNI Android companion wrappers.
  • Unit Tests (core/tests/countdown_test.cpp):

    • Full suite testing Numbers round scoring, Letters round subset validation, Conundrum buzzer state, and leader-based host migration.
    • All 26 core tests pass cleanly via GoogleTest / ctest.

Verification

Built and executed flip7core_tests:

cmake -B core/build core
cmake --build core/build
core/build/flip7core_tests

Result: 26 tests from 4 test suites ran. [ PASSED ] 26 tests.

Closes #52.

Copilot AI review requested due to automatic review settings August 2, 2026 19:30

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Not ready to approve

The standalone core/tests/CMakeLists.txt is currently not configurable as written, and multiple Countdown/protocol behaviors claimed in the PR description (validation + protocol serialization) are not implemented in the diff.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

This review doesn't count toward merge requirements. Sign up for the private preview to control whether Copilot approvals count.

Pull request overview

This PR adds a new Countdown game-mode implementation to flip7core, wires it into game selection via ActiveGameKind, and introduces a new GoogleTest suite covering protocol utilities, serialization, reconciliation, and basic Countdown behaviors.

Changes:

  • Added a new Countdown engine (NumbersRound, LettersRound, ConundrumRound, CountdownMatchEngine) under core/include/flip7/ and core/src/.
  • Extended game selection/protocol enums with ActiveGameKind::Countdown.
  • Added/updated CMake and test files to build and run an expanded unit test suite.
File summaries
File Description
core/tests/serialization_test.cpp Adds round-trip tests for protocol packet (de)serialization and packet type peeking.
core/tests/reconciliation_test.cpp Adds tests for sequence/session logic and puzzle reconciliation/transition helpers.
core/tests/protocol_test.cpp Adds tests validating protocol constants, enums, and small utility helpers.
core/tests/countdown_test.cpp Adds basic unit tests for Countdown rounds and leader-based host migration.
core/tests/CMakeLists.txt Introduces a standalone test CMake project (currently misconfigured).
core/src/reconciliation.cpp Adds a stub translation unit for reconciliation-related compilation units.
core/src/puzzle.cpp Adjusts puzzle function definitions (removes inline qualifiers).
core/src/countdown_engine.cpp Implements the Countdown engine logic introduced in the new header.
core/src/active_game.cpp Adds a stub translation unit for active-game-related compilation units.
core/include/game_selection.h Adds Countdown to ActiveGameKind and extends epoch calculation to include countdown epoch.
core/include/flip7/protocol.hpp Adds ActiveGameKind::Countdown = 3.
core/include/flip7/countdown_engine.hpp Adds Countdown engine public API/types.
core/CMakeLists.txt Adds countdown engine source, tweaks include/install rules, and updates test target sources.
Review details
  • Files reviewed: 15/15 changed files
  • Comments generated: 6
  • Review effort level: Lite

We're testing this review assessment. Please use 👍 or 👎 to tell us if it's correct.

Comment thread core/src/countdown_engine.cpp Outdated
Comment on lines +52 to +55
CommandResult NumbersRound::submitSolution(uint32_t boardId, uint32_t resultValue) {
submissions_[boardId] = resultValue;
return CommandResult{true, "", ""};
}
Comment thread core/src/countdown_engine.cpp Outdated
Comment on lines +148 to +154
CommandResult ConundrumRound::buzz(uint32_t boardId) {
if (solved_) {
return CommandResult{false, "ALREADY_SOLVED", "Conundrum is already solved"};
}
if (activeBuzzerBoardId_ != 0) {
return CommandResult{false, "BUZZER_ACTIVE", "Another player has active buzz window"};
}
Comment thread core/tests/CMakeLists.txt Outdated
Comment on lines +10 to +29
add_executable(flip7core_tests
protocol_test.cpp
serialization_test.cpp
reconciliation_test.cpp
mastermind_test.cpp
puzzle_test.cpp
golden_packet_test.cpp
)

target_link_libraries(flip7core_tests PRIVATE
flip7core
GTest::GTest
GTest::Main
)

add_test(NAME flip7core_tests COMMAND flip7core_tests)

# Also build the main library with tests enabled
set(BUILD_TESTS ON)
add_subdirectory(..) No newline at end of file
Comment thread core/src/countdown_engine.cpp Outdated
Comment on lines +96 to +100
CommandResult LettersRound::submitWord(uint32_t boardId, const std::string& word) {
if (!isValidSubset(word, drawnLetters_)) {
return CommandResult{false, "INVALID_WORD", "Word contains letters not in drawn set"};
}
submittedWords_[boardId] = word;
Comment on lines +1 to +5
/**
* Open-Source Countdown Engine - Core Header
* Platform-neutral C++ implementation of Countdown game rules, round plugins,
* command-event reducer pattern, and score management.
*/
Comment on lines 107 to 111
Home = 0,
Puzzle = 1,
Mastermind = 2,
Countdown = 3,
};
@ap0ught
ap0ught force-pushed the feat/issue-52-countdown-engine branch from 17b7dd4 to e83110e Compare August 2, 2026 20:32
ap0ught pushed a commit that referenced this pull request Aug 8, 2026
- Header: remove misleading reducer/plugin wording
- NumbersRound::submitClaim: validate value reachable from tiles
- ConundrumRound: add buzz-window deadline with markExpired()
- CMakeLists.txt: fix target link ordering
- protocol.hpp: add Countdown packet definitions
- Add platform-neutral C++ Countdown Engine headers and implementation in core/ (Numbers, Letters, and Conundrum rounds)
- Implement CountdownMatchEngine with score tracking and ADR-005 leader-based host migration
- Add ActiveGameKind::Countdown enum value to game_selection.h and protocol.hpp
- Add unit tests for Numbers, Letters, Conundrum, and Match Engine in core/tests/countdown_test.cpp
- Remove helper string 'Locked pieces lose their background' from renderHome to fix text overlap
- Layout game selection menu as a 2x2 grid (Planets, Greek, Mastermind, Countdown)
- Add Countdown game mode selection and header updates
…e is exited (#52)

- Set activeGame.kind = ActiveGameKind::Home when puzzle state is in PuzzlePhase::Exited
- Update startPuzzle and commitMastermindState checks to permit selection whenever screenMode == ScreenMode::Home
…ntdown exit sync

Countdown sync:
- Add wire-safe CountdownWireState POD struct (include/countdown_wire.h)
  mirroring MastermindState's role, since the countdown_engine.hpp game
  logic classes use std::string/std::vector and are not memcpy-safe.
- Add CountdownState/CountdownFullState/CountdownAck/CountdownRequestState
  message types and matching packet structs to include/protocol.h.
- Wire send/receive/ack/reconciliation for Countdown state in main.cpp,
  matching the existing Puzzle/Mastermind sync pattern, so both boards
  now see the Countdown match screen (host/guest scores) when the host
  starts a match, instead of the guest staying stuck on
  'WAITING FOR HOST'.
- Extend core/include/flip7/countdown_engine.hpp with a 3-arg
  CommandResult constructor and CountdownMatchEngine::matchState()/
  isHost()/resetMatch() accessors needed by the render/start glue.

Exit sync fix:
- The bottom EXIT button on Mastermind and Countdown screens previously
  only flipped local screenMode/activeGame back to Home without ever
  notifying the peer board, so pressing EXIT did not return both boards
  home. Split the combined no-op handler into per-mode handlers that
  call exitMastermindMatch()/commitMastermindState() and
  exitCountdownMatch()/countdownPendingDelivery respectively, so EXIT is
  now synchronized and retried like Puzzle EXIT already was.

Verified: pio run succeeds; firmware flashed to both physical CYD
boards (/dev/ttyUSB0, /dev/ttyUSB1); user confirmed on hardware that
COUNTDOWN now navigates to a match screen and Mastermind EXIT now
returns both boards home.

Known limitation: round-specific Countdown content (numbers tiles,
letters, conundrum scramble) is not yet synchronized between boards —
only match-level state (phase, scores, round number) is. Deferred to a
follow-up.
…tdown sync

The LinkState peerSequences/sequenceSeen arrays were sized 11 and acceptPeerSequence() rejected any MessageType index >= 11, but Countdown message types (CountdownState=11, CountdownFullState=12, CountdownAck=13, CountdownRequestState=14) all landed at or above that cutoff. Every Countdown packet the guest received failed acceptPeerSequence() and was silently dropped, so pressing COUNTDOWN only updated the host's local screen (the flash bug) and the guest board never received match state (stuck on WAITING FOR HOST).

Fix: bump both arrays to size 15 and the bounds check to >= 15, covering all 14 defined MessageType values.

Also fills in startCountdown()'s missing wire-state population/send (it previously flipped screenMode locally without populating countdownState or setting countdownStateReady, and serviceProtocol() never flushed countdownPendingDelivery to ESP-NOW at all -- both gaps are fixed here alongside the sequence-bounds root cause).

Verified on physical hardware: built, flashed both CYD boards, confirmed COUNTDOWN now syncs correctly on both host and guest. Also verified with an ad-hoc /tmp harness isolating acceptPeerSequence() logic, confirming all four Countdown message types were rejected under the old bound and accepted under the fix.
The Countdown match screen now supports starting a new round:
- Host presses NEW ROUND on the match screen (button hidden for guest);
  advanceCountdownRound() bumps roundNumber/revision and moves the
  synced CountdownWireState phase to BetweenRounds.
- While BetweenRounds, renderCountdown() shows a CHOOSE NEXT ROUND
  screen with NUMBERS/LETTERS/CONUNDRUM buttons (host only); the guest
  sees a WAITING FOR HOST TO PICK message and stays in sync via the
  existing CountdownState wire messages.
- Host tapping a round type calls selectCountdownRoundType(), which
  validates phase == BetweenRounds and actor == host, sets roundType,
  and flips phase back to InRound; both boards return to the normal
  match view with the round counter incremented.

This only synchronizes match-level state (phase/roundNumber/roundType)
consistent with the existing Countdown wire-state scope -- round-specific
puzzle content (numbers tiles, letters, conundrum scramble) generation
is a separate follow-up, same as noted in the original Countdown sync
commit.

Verified: pio run build succeeds, both physical CYD boards flashed,
user confirmed NEW ROUND -> mode picker -> round-type selection syncs
correctly between host and guest.
- countdown_engine.hpp/cpp: Add CommandType/EventType enums for all round sub-phases,
  FixedVector for heap-free ESP32, NumberWorkspace/LetterWorkspace/ConundrumWorkspace,
  CalculationStep, PuzzleDescriptor, deterministic RNG, dictionary verification
- countdown_wire.h: Add CountdownRoundSubPhase (21 phases), chooserBoardId, hostTerm,
  roundSubPhase, HostAuthority, advanceCountdownSubPhase, applyCountdownHostTransfer
- protocol.h: Add CountdownAction and CountdownPuzzleDescriptor message types,
  CountdownFullStatePacket with letters array, static asserts for new packets
- main.cpp: Full Countdown UI implementation (Intro, NumPicking, NumThinking,
  NumClaimEntry, NumClaimReveal, NumPresentPlayerA/B, NumResult, LetPicking,
  LetThinking, LetClaimEntry, LetClaimReveal, LetPresentPlayerA/B, LetResult,
  ConReady, ConActive, ConResult, ConResultNoWinner), touch handlers, timer arc,
  tile strip, numpad, operation buttons, large-count picker, FORCE-END button
- countdown_test.cpp: Extended tests for new engine types and projections
…flags

- selectCountdownRoundType: accept Setup phase so first-game round tap works
- Wire: rename reserved->roundConfig; store largeCount for guest engine sync
- Guest engine: call startNextRound on state adoption so target/tiles project
- Rendering: firstFrame pattern eliminates fillScreen flicker on timed phases
  (Intro, NumThinking, LetThinking draw background once, only timer circle
  updated on subsequent 100ms ticks; ConActive reduced to 500ms)
- platformio.ini: restore build_unflags + std=gnu++17 (was wiped by editor)
- Build tooling: scripts/upload.py auto-detects and flashes both CYD boards,
  releases port locks before upload, supports --monitor-only and --port flags
- VS Code: .vscode/launch.json + tasks.json wired to upload.py; compound
  'Monitor Both CYDs' config opens two simultaneous serial terminal sessions
- content/conundrum/default_words.csv: 50 nine-letter conundrum words
- docs: add architecture markdown conversion
- Header: remove misleading reducer/plugin wording
- NumbersRound::submitClaim: validate value reachable from tiles
- ConundrumRound: add buzz-window deadline with markExpired()
- CMakeLists.txt: fix target link ordering
- protocol.hpp: add Countdown packet definitions
@ap0ught
ap0ught force-pushed the feat/issue-52-countdown-engine branch from a321ec2 to 2d5aee4 Compare August 8, 2026 19:14
@ap0ught
ap0ught merged commit fcc2202 into main Aug 8, 2026
2 checks passed
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.

Add Synchronized Two-Player Countdown Engine Game Mode (Numbers, Letters, Conundrum)

3 participants