GameEngine is now a state machine so I can push its design further and keep it solid.

master
Zed A. Shaw 4 weeks ago
parent 1c89afaee2
commit 9e6c05eccd
  1. 11
      builder.cpp
  2. 4
      fsm.hpp
  3. 57
      game_engine.cpp
  4. 17
      game_engine.hpp
  5. 23
      tests/game_engine.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();

@ -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<typename S, typename E>
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;
}

@ -4,6 +4,7 @@
#include <fmt/core.h>
#include <fmt/color.h>
#include "game_engine.hpp"
#include <cassert>
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);
}

@ -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<GameState, GameEvent> {
std::map<string, int> damage_types{
{"error", 4},
{"warning", 1},
@ -31,7 +32,6 @@ class GameEngine : DeadSimpleFSM<GameState, GameEvent> {
int hits_taken = 0;
int rounds = 0;
int streak = 0;
GameState _state = GameState::START;
GameEngine(int hp);
@ -47,7 +47,8 @@ class GameEngine : DeadSimpleFSM<GameState, GameEvent> {
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<GameState, GameEvent> {
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();

@ -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);
}

Loading…
Cancel
Save