Files
EVEngine/test/tactics_action.cpp

167 lines
8.6 KiB
C++

#include "action/Action.h"
#include "common/ECS.h"
#include "tactics/Tactics.h"
#include "tactics/TacticsAction.h"
#include "zeroerr/assert.h"
#include "zeroerr/unittest.h"
namespace {
eve::SubjectRef subject(const char* text) {
const auto id = eve::PersistentId::parse(text);
REQUIRE(id.has_value());
return eve::SubjectRef::fromPersistentId(*id);
}
} // namespace
TEST_CASE("tactics.moveCommitsThroughSharedActionRuntime") {
ecs::Table world;
ecs::ScopedTable guard(world);
eve::tactics::Tactics tactics;
auto battleResult = tactics.newBattle(subject("00000000-0000-0000-0000-000000000040"), 9);
REQUIRE(battleResult.ok());
const auto battleHandle = std::move(battleResult).takeValue();
REQUIRE(tactics.addCell(battleHandle, {0, 0, 0}).ok());
REQUIRE(tactics.addCell(battleHandle, {1, 0, 0}).ok());
auto sideResult = tactics.newSide(battleHandle, subject("00000000-0000-0000-0000-000000000041"));
REQUIRE(sideResult.ok());
const auto sideHandle = std::move(sideResult).takeValue();
const auto unitSubject = subject("00000000-0000-0000-0000-000000000042");
auto unitResult = tactics.newUnit(battleHandle, sideHandle, unitSubject, {}, {0, 0, 0}, {1, 100, 0, 1});
REQUIRE(unitResult.ok());
const auto unitHandle = std::move(unitResult).takeValue();
REQUIRE(tactics.start(battleHandle, eve::tactics::TurnPolicyKind::Initiative).ok());
REQUIRE(tactics.advance(battleHandle, {eve::SimulationTick(1), eve::Duration::zero()}).ok());
REQUIRE(tactics.advance(battleHandle, {eve::SimulationTick(2), eve::Duration::zero()}).ok());
REQUIRE(tactics.advance(battleHandle, {eve::SimulationTick(3), eve::Duration::zero()}).ok());
auto* battle = dynamic_cast<eve::tactics::Battle*>(ecs::try_get(battleHandle));
REQUIRE(battle != nullptr);
eve::tactics::MoveActionExecutor executor(*battle);
eve::action::ActionServices services;
services.effects = &executor;
eve::action::ActionRuntime runtime(services);
auto request = eve::tactics::makeMoveRequest(unitHandle, {1, 0, 0}, eve::SimulationTick(3));
REQUIRE(request.ok());
auto submitted = runtime.submit(eve::tactics::moveActionDefinition(), std::move(request).takeValue());
REQUIRE(submitted.ok());
const auto execution = std::move(submitted).takeValue();
auto advanced = runtime.advance(execution, eve::SimulationTick(4), eve::Duration::zero());
REQUIRE(advanced.ok());
CHECK_EQ(static_cast<int>(std::move(advanced).takeValue().phase),
static_cast<int>(eve::action::ActionPhase::Completed));
const auto position = battle->board()->value.position(unitSubject);
REQUIRE(position.has_value());
CHECK((*position == eve::tactics::Cell{1, 0, 0}));
auto* unit = dynamic_cast<eve::tactics::TacticalUnit*>(ecs::try_get(unitHandle));
REQUIRE(unit != nullptr);
CHECK_EQ(unit->turn()->movePoints, 0);
auto faceRequest = eve::tactics::makeFaceRequest(unitHandle, 2, eve::SimulationTick(4));
REQUIRE(faceRequest.ok());
auto faceExecution = runtime.submit(eve::tactics::faceActionDefinition(), std::move(faceRequest).takeValue());
REQUIRE(faceExecution.ok());
auto faceAdvanced =
runtime.advance(std::move(faceExecution).takeValue(), eve::SimulationTick(5), eve::Duration::zero());
REQUIRE(faceAdvanced.ok());
CHECK_EQ(unit->position()->facing, 2);
auto waitRequest = eve::tactics::makeWaitRequest(unitHandle, eve::SimulationTick(5));
REQUIRE(waitRequest.ok());
auto waitExecution = runtime.submit(eve::tactics::waitActionDefinition(), std::move(waitRequest).takeValue());
REQUIRE(waitExecution.ok());
auto waitAdvanced =
runtime.advance(std::move(waitExecution).takeValue(), eve::SimulationTick(6), eve::Duration::zero());
REQUIRE(waitAdvanced.ok());
CHECK_EQ(static_cast<int>(battle->turn()->phase), static_cast<int>(eve::tactics::BattlePhase::TurnEnd));
}
TEST_CASE("tactics.moveActionPrepareFailureDoesNotMutateBoard") {
ecs::Table world;
ecs::ScopedTable guard(world);
eve::tactics::Tactics tactics;
auto battleResult = tactics.newBattle(subject("00000000-0000-0000-0000-000000000050"));
REQUIRE(battleResult.ok());
const auto battleHandle = std::move(battleResult).takeValue();
REQUIRE(tactics.addCell(battleHandle, {0, 0, 0}).ok());
REQUIRE(tactics.addCell(battleHandle, {1, 0, 0}).ok());
auto sideResult = tactics.newSide(battleHandle, subject("00000000-0000-0000-0000-000000000051"));
REQUIRE(sideResult.ok());
const auto sideHandle = std::move(sideResult).takeValue();
const auto actor = subject("00000000-0000-0000-0000-000000000052");
auto unitResult = tactics.newUnit(battleHandle, sideHandle, actor, {}, {0, 0, 0}, {1, 0, 0, 1});
REQUIRE(unitResult.ok());
const auto unitHandle = std::move(unitResult).takeValue();
REQUIRE(tactics.start(battleHandle, eve::tactics::TurnPolicyKind::Initiative).ok());
REQUIRE(tactics.advance(battleHandle, {eve::SimulationTick(1), eve::Duration::zero()}).ok());
REQUIRE(tactics.advance(battleHandle, {eve::SimulationTick(2), eve::Duration::zero()}).ok());
REQUIRE(tactics.advance(battleHandle, {eve::SimulationTick(3), eve::Duration::zero()}).ok());
auto* battle = dynamic_cast<eve::tactics::Battle*>(ecs::try_get(battleHandle));
REQUIRE(battle != nullptr);
eve::tactics::MoveActionExecutor executor(*battle);
eve::action::ActionServices services;
services.effects = &executor;
eve::action::ActionRuntime runtime(services);
const auto beforeRevision = battle->turn()->revision;
const auto beforeEvents = battle->events()->values.size();
const auto beforeCommands = battle->commands()->values.size();
const auto beforeStreams = battle->random()->streams.size();
auto request = eve::tactics::makeMoveRequest(unitHandle, {1, 0, 0}, eve::SimulationTick(3));
REQUIRE(request.ok());
auto submitted = runtime.submit(eve::tactics::moveActionDefinition(), std::move(request).takeValue());
REQUIRE(submitted.ok());
auto advanced = runtime.advance(std::move(submitted).takeValue(), eve::SimulationTick(4), eve::Duration::zero());
CHECK(!advanced.ok());
const auto position = battle->board()->value.position(actor);
REQUIRE(position.has_value());
CHECK((*position == eve::tactics::Cell{0, 0, 0}));
CHECK_EQ(battle->turn()->revision, beforeRevision);
CHECK_EQ(battle->events()->values.size(), beforeEvents);
CHECK_EQ(battle->commands()->values.size(), beforeCommands);
CHECK_EQ(battle->random()->streams.size(), beforeStreams);
}
TEST_CASE("tactics.moveActionRejectsStaleExpectedRevision") {
ecs::Table world;
ecs::ScopedTable guard(world);
eve::tactics::Tactics tactics;
auto battleResult = tactics.newBattle(subject("00000000-0000-0000-0000-000000000055"));
REQUIRE(battleResult.ok());
const auto battleHandle = std::move(battleResult).takeValue();
REQUIRE(tactics.addCell(battleHandle, {0, 0, 0}).ok());
REQUIRE(tactics.addCell(battleHandle, {1, 0, 0}).ok());
auto side = tactics.newSide(battleHandle, subject("00000000-0000-0000-0000-000000000056"));
REQUIRE(side.ok());
const auto actor = subject("00000000-0000-0000-0000-000000000057");
auto unit = tactics.newUnit(battleHandle, side.value(), actor, {}, {0, 0, 0}, {1, 100, 0, 1});
REQUIRE(unit.ok());
const auto unitHandle = std::move(unit).takeValue();
REQUIRE(tactics.start(battleHandle, eve::tactics::TurnPolicyKind::Initiative).ok());
REQUIRE(tactics.advance(battleHandle, {eve::SimulationTick(1), eve::Duration::zero()}).ok());
REQUIRE(tactics.advance(battleHandle, {eve::SimulationTick(2), eve::Duration::zero()}).ok());
REQUIRE(tactics.advance(battleHandle, {eve::SimulationTick(3), eve::Duration::zero()}).ok());
auto request = eve::tactics::makeMoveRequest(unitHandle, {1, 0, 0}, eve::SimulationTick(3));
REQUIRE(request.ok());
REQUIRE(tactics.faceUnit(battleHandle, actor, 1).ok());
auto* battle = dynamic_cast<eve::tactics::Battle*>(ecs::try_get(battleHandle));
REQUIRE(battle != nullptr);
eve::tactics::MoveActionExecutor executor(*battle);
eve::action::ActionServices services;
services.effects = &executor;
eve::action::ActionRuntime runtime(services);
auto submitted = runtime.submit(eve::tactics::moveActionDefinition(), std::move(request).takeValue());
REQUIRE(submitted.ok());
auto advanced = runtime.advance(std::move(submitted).takeValue(), eve::SimulationTick(4), eve::Duration::zero());
CHECK(!advanced.ok());
CHECK_EQ(advanced.error()->code(), eve::DiagnosticCode::Conflict);
CHECK_EQ(battle->board()->value.position(actor).value(), (eve::tactics::Cell{0, 0, 0}));
}