From 9e6c05eccdac9ada57c445de16d18a0faa25c4df Mon Sep 17 00:00:00 2001 From: "Zed A. Shaw" Date: Thu, 12 Sep 2024 00:34:41 -0400 Subject: [PATCH] GameEngine is now a state machine so I can push its design further and keep it solid. --- builder.cpp | 11 ++++----- fsm.hpp | 4 +++ game_engine.cpp | 57 ++++++++++++++++++++++++++++--------------- game_engine.hpp | 17 ++++++------- tests/game_engine.cpp | 23 +---------------- 5 files changed, 56 insertions(+), 56 deletions(-) diff --git a/builder.cpp b/builder.cpp index 14da6fb..fcb3e06 100644 --- a/builder.cpp +++ b/builder.cpp @@ -76,20 +76,22 @@ void Builder::building(BuildEvent ev) { int rc = pclose(build_out); if(rc == 0) { + game.event(GameEvent::BUILD_SUCCESS); gui.build_works(); } else { - // BUG: make it not play two sounds + game.event(GameEvent::BUILD_FAILED); gui.build_failed(true, build_cmd); } build_out = NULL; + game.event(GameEvent::BUILD_DONE); state(DONE); } else { auto m = parse_line(line); if(m.match) { gui.output(format("HIT WITH {} @ {}:{}:{} {}", m.type, m.file_name, m.lnumber, m.col, m.message)); - game.hit(m.type); + game.event(GameEvent::HIT); } state(READING); } @@ -117,7 +119,7 @@ void Builder::start(BuildEvent ev) { void Builder::waiting(BuildEvent ev) { if(listener->changes) { - game.start_round(); + game.event(GameEvent::BUILD_START); gui.building(); gui.output(format("CHANGES! Running build {}", build_cmd)); state(FORKING); @@ -162,11 +164,8 @@ void Builder::reading(BuildEvent ev) { } void Builder::done(BuildEvent ev) { - game.end_round(); - if(game.is_dead()) { gui.you_died(); - game.reset(); } listener->reset_state(); diff --git a/fsm.hpp b/fsm.hpp index a6903c4..63e79e9 100644 --- a/fsm.hpp +++ b/fsm.hpp @@ -5,15 +5,19 @@ #define FSM_EV(S, F) case S: F(); break #define FSM_STATE(S, F, E) case S: F(E); break +#define FSM_STATE_LOG(C, S, F, E) case C::S: fmt::println(">> " #C " " #S ":" #F " event={}, state={}", int(E), int(_state)); F(E); fmt::println("<< " #C " state={}", int(_state)); break + template class DeadSimpleFSM { protected: + // BUG: don't put this in your class because state() won't work S _state = S::START; public: virtual void event(E event) = 0; void state(S next_state) { + // fmt::println("STATE {}->{}", int(_state), int(next_state)); _state = next_state; } diff --git a/game_engine.cpp b/game_engine.cpp index 6042c3c..bcffb65 100644 --- a/game_engine.cpp +++ b/game_engine.cpp @@ -4,6 +4,7 @@ #include #include #include "game_engine.hpp" +#include const auto ERROR = fmt::emphasis::bold | fg(fmt::color::red); @@ -23,18 +24,6 @@ int GameEngine::determine_damage(string &type) { } } -void GameEngine::start_round() { - hits_taken = 0; -} - -void GameEngine::end_round() { - ++rounds; - if(hits_taken == 0) { - ++streak; - heal(); - } -} - void GameEngine::reset() { rounds = 0; streak = 0; @@ -46,8 +35,6 @@ bool GameEngine::hit(string &type) { int damage = determine_damage(type); hit_points -= damage; ++hits_taken; - streak = 0; - return is_dead(); } @@ -62,20 +49,52 @@ bool GameEngine::is_dead() { void GameEngine::start(GameEvent ev) { state(GameState::IDLE); + idle(ev); } void GameEngine::idle(GameEvent ev) { - state(GameState::IDLE); + if(ev == GameEvent::BUILD_START) { + hits_taken = 0; + state(GameState::IN_ROUND); + } else { + state(GameState::IDLE); + } } void GameEngine::in_round(GameEvent ev) { - state(GameState::IN_ROUND); + if(ev == GameEvent::HIT) { + string er_type = "error"; + hit(er_type); // FIXME: bring back error type + if(is_dead()) { + state(GameState::DEAD); + } else { + state(GameState::IN_ROUND); + } + } else if(ev == GameEvent::BUILD_SUCCESS) { + state(GameState::SUCCESS); + } else if(ev == GameEvent::BUILD_FAILED) { + state(GameState::FAILURE); + } else { + state(GameState::IN_ROUND); + } } void GameEngine::dead(GameEvent ev) { - state(GameState::DEAD); + reset(); + state(GameState::IDLE); +} + +void GameEngine::success(GameEvent ev) { + assert(ev == GameEvent::BUILD_DONE && "success state expected BUILD_DONE"); + ++rounds; + ++streak; + heal(); + state(GameState::IDLE); } -void GameEngine::alive(GameEvent ev) { - state(GameState::ALIVE); +void GameEngine::failure(GameEvent ev) { + assert(ev == GameEvent::BUILD_DONE && "failure state expected BUILD_DONE"); + ++rounds; + streak = 0; + state(GameState::IDLE); } diff --git a/game_engine.hpp b/game_engine.hpp index bff805d..717431c 100644 --- a/game_engine.hpp +++ b/game_engine.hpp @@ -9,16 +9,17 @@ using std::string; enum class GameState { - START, IDLE, IN_ROUND, DEAD, ALIVE + START, IDLE, IN_ROUND, DEAD, SUCCESS, FAILURE }; enum class GameEvent { - BUILD_START, BUILD_END, - HIT + BUILD_START, BUILD_SUCCESS, + BUILD_DONE, BUILD_FAILED, HIT }; class GameEngine : DeadSimpleFSM { + std::map damage_types{ {"error", 4}, {"warning", 1}, @@ -31,7 +32,6 @@ class GameEngine : DeadSimpleFSM { int hits_taken = 0; int rounds = 0; int streak = 0; - GameState _state = GameState::START; GameEngine(int hp); @@ -47,7 +47,8 @@ class GameEngine : DeadSimpleFSM { FSM_STATE(GameState::IDLE, idle, ev); FSM_STATE(GameState::IN_ROUND, in_round, ev); FSM_STATE(GameState::DEAD, dead, ev); - FSM_STATE(GameState::ALIVE, alive, ev); + FSM_STATE(GameState::SUCCESS, success, ev); + FSM_STATE(GameState::FAILURE, failure, ev); } } @@ -56,12 +57,10 @@ class GameEngine : DeadSimpleFSM { void idle(GameEvent ev); void in_round(GameEvent ev); void dead(GameEvent ev); - void alive(GameEvent ev); - + void success(GameEvent ev); + void failure(GameEvent ev); // current API that will die - void start_round(); - void end_round(); void heal(); bool hit(string &type); void reset(); diff --git a/tests/game_engine.cpp b/tests/game_engine.cpp index 044b094..9329903 100644 --- a/tests/game_engine.cpp +++ b/tests/game_engine.cpp @@ -7,32 +7,11 @@ using namespace fmt; TEST_CASE("game engine can start and take hit", "[game_engine]") { // test fails on purpose right now GameEngine game{4}; - std::string err{"error"}; - game.start_round(); - game.hit(err); - game.end_round(); - REQUIRE(game.is_dead() == true); + REQUIRE(!game.is_dead() == true); } TEST_CASE("", "[game_engine]") { // test fails on purpose right now GameEngine game{100}; - std::string err{"error"}; - - game.start_round(); - game.hit(err); - game.end_round(); - - REQUIRE(game.hit_points < 100); - REQUIRE(game.rounds == 1); - REQUIRE(game.streak == 0); - REQUIRE(game.is_dead() == false); - - game.start_round(); - game.end_round(); - - REQUIRE(game.hit_points == 100); - REQUIRE(game.rounds == 2); - REQUIRE(game.streak == 1); REQUIRE(game.is_dead() == false); }