feat(core): integrate Countdown Engine game mode (#52) - #53
Conversation
There was a problem hiding this comment.
🟡 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) undercore/include/flip7/andcore/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.
| CommandResult NumbersRound::submitSolution(uint32_t boardId, uint32_t resultValue) { | ||
| submissions_[boardId] = resultValue; | ||
| return CommandResult{true, "", ""}; | ||
| } |
| 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"}; | ||
| } |
| 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 |
| 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; |
| /** | ||
| * Open-Source Countdown Engine - Core Header | ||
| * Platform-neutral C++ implementation of Countdown game rules, round plugins, | ||
| * command-event reducer pattern, and score management. | ||
| */ |
| Home = 0, | ||
| Puzzle = 1, | ||
| Mastermind = 2, | ||
| Countdown = 3, | ||
| }; |
17b7dd4 to
e83110e
Compare
- 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
a321ec2 to
2d5aee4
Compare
Summary
Integrates the platform-neutral Open-Source Countdown Engine specification into
flip7coreas 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):ActiveGameKind::Countdown = 3to support game mode selection across CYD ESP32 firmware and JNI Android companion wrappers.Unit Tests (
core/tests/countdown_test.cpp):ctest.Verification
Built and executed
flip7core_tests:Result:
26 tests from 4 test suites ran. [ PASSED ] 26 tests.Closes #52.