Skip to content

Commit

Permalink
Some sugar for test tapes
Browse files Browse the repository at this point in the history
  • Loading branch information
captainurist committed Oct 12, 2023
1 parent e486d7d commit ebd8810
Show file tree
Hide file tree
Showing 6 changed files with 165 additions and 112 deletions.
53 changes: 19 additions & 34 deletions test/Bin/GameTest/GameTests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -177,8 +177,8 @@ GAME_TEST(Issues, Issue223) {
auto airTape = ctapes.resistances(CHARACTER_ATTRIBUTE_RESIST_AIR);
test.playTraceFromTestData("issue_223.mm7", "issue_223.json");
// expect normal resistances after restart 55-00-00-00.
EXPECT_EQ(fireTape.firstLast(), tape({280, 262, 390, 241}, {5, 0, 0, 0}));
EXPECT_EQ(airTape.firstLast(), tape({389, 385, 385, 381}, {5, 0, 0, 0}));
EXPECT_EQ(fireTape.frontBack(), tape({280, 262, 390, 241}, {5, 0, 0, 0}));
EXPECT_EQ(airTape.frontBack(), tape({389, 385, 385, 381}, {5, 0, 0, 0}));
}

GAME_TEST(Issues, Issue238) {
Expand Down Expand Up @@ -300,7 +300,7 @@ GAME_TEST(Issues, Issue293a) {
});

EXPECT_EQ(totalItemsTape.delta(), +1);
EXPECT_EQ(conditionsTape.firstLast(), tape({CONDITION_GOOD, CONDITION_GOOD, CONDITION_GOOD, CONDITION_GOOD},
EXPECT_EQ(conditionsTape.frontBack(), tape({CONDITION_GOOD, CONDITION_GOOD, CONDITION_GOOD, CONDITION_GOOD},
{CONDITION_DISEASE_MEDIUM, CONDITION_DISEASE_WEAK, CONDITION_DISEASE_WEAK, CONDITION_DISEASE_WEAK}));
EXPECT_EQ(pParty->pCharacters[0].uMight, 30);
EXPECT_EQ(pParty->pCharacters[0].uIntelligence, 7); // +2
Expand Down Expand Up @@ -387,7 +387,7 @@ GAME_TEST(Issues, Issue331_679) {

// #679: Loading autosave after travelling by stables / boat results in gold loss.
EXPECT_EQ(goldTape.delta(), 0);
EXPECT_LT(goldTape.min(), goldTape.values().front()); // We did spend money.
EXPECT_LT(goldTape.min(), goldTape.front()); // We did spend money.
}

GAME_TEST(Prs, Pr347) {
Expand All @@ -404,24 +404,9 @@ GAME_TEST(Issues, Issue355) {
// GOG: 6-2. OpenEnroth: 9-5.
auto healthTape = ctapes.hps();
test.playTraceFromTestData("issue_355.mm7", "issue_355.json");

std::vector<int> damageRolls;
const auto &values = healthTape.values();
for (size_t i = 0; i < values.size() - 1; i++) {
const auto &prev = values[i];
const auto &next = values[i + 1];

for (size_t j = 0; j < prev.size(); j++) {
int damage = prev[j] - next[j];
if (damage != 0)
damageRolls.push_back(damage);
}
}
std::sort(damageRolls.begin(), damageRolls.end());

auto damageRange = healthTape.reversed().adjacentDeltas().flattened().filtered([] (int damage) { return damage > 0; }).minMax();
// 2d3+0 with a non-random engine can't roll 2 or 6, so all values should be in [3, 5].
EXPECT_EQ(damageRolls.front(), 3);
EXPECT_EQ(damageRolls.back(), 5);
EXPECT_EQ(damageRange, tape(3, 5));
}

GAME_TEST(Issues, Issue388) {
Expand All @@ -445,7 +430,7 @@ GAME_TEST(Issues, Issue395) {
auto expTape = ctapes.experiences();
auto learningTape = ctapes.skillLevels(CHARACTER_SKILL_LEARNING);
test.playTraceFromTestData("issue_395.mm7", "issue_395.json");
EXPECT_EQ(expTape.firstLast(), tape({100, 100, 100, 100}, {214, 228, 237, 258}));
EXPECT_EQ(expTape.frontBack(), tape({100, 100, 100, 100}, {214, 228, 237, 258}));
EXPECT_EQ(learningTape, tape({0, 4, 6, 10}));
}

Expand Down Expand Up @@ -672,7 +657,7 @@ GAME_TEST(Issues, Issue492) {
// Check that spells that target all visible actors work.
auto experienceTape = ctapes.experiences();
test.playTraceFromTestData("issue_492.mm7", "issue_492.json");
EXPECT_EQ(experienceTape.firstLast(), tape({279, 311, 266, 260}, {287, 319, 274, 268}));
EXPECT_EQ(experienceTape.frontBack(), tape({279, 311, 266, 260}, {287, 319, 274, 268}));
}

// 500
Expand Down Expand Up @@ -805,9 +790,9 @@ GAME_TEST(Issues, Issue601) {
auto conditionsTape = ctapes.conditions();
auto hpTape = ctapes.hps();
test.playTraceFromTestData("issue_601.mm7", "issue_601.json");
EXPECT_EQ(conditionsTape.firstLast(), tape({CONDITION_SLEEP, CONDITION_CURSED, CONDITION_FEAR, CONDITION_DEAD},
EXPECT_EQ(conditionsTape.frontBack(), tape({CONDITION_SLEEP, CONDITION_CURSED, CONDITION_FEAR, CONDITION_DEAD},
{CONDITION_GOOD, CONDITION_GOOD, CONDITION_GOOD, CONDITION_GOOD}));
EXPECT_EQ(hpTape.firstLast(), tape({66, 128, 86, 70}, {126, 190, 96, 80}));
EXPECT_EQ(hpTape.frontBack(), tape({66, 128, 86, 70}, {126, 190, 96, 80}));
}

GAME_TEST(Issues, Issue608) {
Expand Down Expand Up @@ -930,7 +915,7 @@ GAME_TEST(Issues, Issue645) {
// Characters does not enter unconscious state
auto conditionsTape = ctapes.conditions();
test.playTraceFromTestData("issue_645.mm7", "issue_645.json");
EXPECT_EQ(conditionsTape.firstLast(), tape({CONDITION_GOOD, CONDITION_GOOD, CONDITION_GOOD, CONDITION_GOOD},
EXPECT_EQ(conditionsTape.frontBack(), tape({CONDITION_GOOD, CONDITION_GOOD, CONDITION_GOOD, CONDITION_GOOD},
{CONDITION_UNCONSCIOUS, CONDITION_GOOD, CONDITION_UNCONSCIOUS, CONDITION_UNCONSCIOUS}));
}

Expand Down Expand Up @@ -1196,7 +1181,7 @@ GAME_TEST(Issues, Issue755) {
}

GAME_TEST(Issues, Issue760) {
// Check that mixing potions when character inventory is full does not discards empty bottle
// Check that mixing potions when character inventory is full does not discard empty bottle
auto itemsTape = tapes.totalItemCount();
test.playTraceFromTestData("issue_760.mm7", "issue_760.json");
EXPECT_EQ(itemsTape.delta(), 0);
Expand Down Expand Up @@ -1386,7 +1371,7 @@ GAME_TEST(Issues, Issue832) {
// Death Blossom + ice blast crash
auto deathsTape = tapes.actorCountByState(AIState::Dead);
test.playTraceFromTestData("issue_832.mm7", "issue_832.json");
EXPECT_EQ(deathsTape.firstLast(), tape(0, 3));
EXPECT_EQ(deathsTape.frontBack(), tape(0, 3));
}

GAME_TEST(Issues, Issue833) {
Expand Down Expand Up @@ -1474,7 +1459,7 @@ GAME_TEST(Prs, Pr1005) {
// Testing collisions - stairs should work. In this test case the party is walking onto a wooden paving in Tatalia.
auto zTape = tapes.custom([] { return pParty->pos.z; });
test.playTraceFromTestData("pr_1005.mm7", "pr_1005.json");
EXPECT_EQ(zTape.firstLast(), tape(154, 193)); // Paving is at z=192, party z should be this value +1.
EXPECT_EQ(zTape.frontBack(), tape(154, 193)); // Paving is at z=192, party z should be this value +1.
}

GAME_TEST(Issues, Issue1020) {
Expand All @@ -1500,7 +1485,7 @@ GAME_TEST(Issues, Issue1038) {
// Crash while fighting Eyes in Nighon Tunnels
auto conditionsTape = ctapes.conditions();
test.playTraceFromTestData("issue_1038.mm7", "issue_1038.json");
EXPECT_EQ(conditionsTape.firstLast(), tape({CONDITION_GOOD, CONDITION_INSANE, CONDITION_GOOD, CONDITION_INSANE},
EXPECT_EQ(conditionsTape.frontBack(), tape({CONDITION_GOOD, CONDITION_INSANE, CONDITION_GOOD, CONDITION_INSANE},
{CONDITION_INSANE, CONDITION_INSANE, CONDITION_SLEEP, CONDITION_UNCONSCIOUS}));
}

Expand All @@ -1523,7 +1508,7 @@ GAME_TEST(Issues, Issue1068) {
// Kills assert if characters don't have learning skill, but party has an npc that gives learning boost.
auto expTape = ctapes.experiences();
test.playTraceFromTestData("issue_1068.mm7", "issue_1068.json");
EXPECT_EQ(expTape.firstLast(), tape({158039, 156727, 157646, 157417}, {158518, 157206, 158125, 157896}));
EXPECT_EQ(expTape.frontBack(), tape({158039, 156727, 157646, 157417}, {158518, 157206, 158125, 157896}));
}

GAME_TEST(Issues, Issue1093) {
Expand Down Expand Up @@ -1573,9 +1558,9 @@ GAME_TEST(Issues, Issue1164) {
EXPECT_EQ(frameTimeTape, tape(15)); // Don't redo at other frame rates.

auto isNo = [] (const auto &pair) { return pair.first == CHARACTER_EXPRESSION_NO; };
auto begin = std::find_if(expressionTape.values().begin(), expressionTape.values().end(), isNo);
auto end = std::find_if_not(begin, expressionTape.values().end(), isNo);
ASSERT_NE(end, expressionTape.values().end());
auto begin = std::find_if(expressionTape.begin(), expressionTape.end(), isNo);
auto end = std::find_if_not(begin, expressionTape.end(), isNo);
ASSERT_NE(end, expressionTape.end());

// CHARACTER_EXPRESSION_NO should take 144 ticks, minus one frame. This one frame is an implementation artifact,
// shouldn't really be there, but for now we test it the way it actually works.
Expand Down
122 changes: 122 additions & 0 deletions test/Testing/Game/AccessibleVector.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
#pragma once

#include <cassert>
#include <vector>
#include <algorithm>
#include <numeric>
#include <iterator>

template<class Base>
class Accessible;

template<class T>
using AccessibleVector = Accessible<std::vector<T>>;

template<class T>
T calculateDelta(const T &l, const T &r) {
return r - l;
}

template<class Base>
class Accessible : public Base {
public:
using Base::Base;
using Base::begin;
using Base::end;
using value_type = std::iter_value_t<decltype(std::declval<const Base *>()->begin())>;

friend AccessibleVector<value_type> calculateDelta(const Accessible &l, const Accessible &r) {
AccessibleVector<value_type> result;
auto lpos = l.begin();
auto rpos = r.begin();
while (lpos != l.end())
result.push_back(calculateDelta(*lpos++, *rpos++));
assert(rpos == r.end());
return result;
}

size_t size() const {
return end() - begin();
}

const value_type &front() const {
assert(begin() != end());
return *begin();
}

const value_type &back() const {
assert(begin() != end());
return *end();
}

AccessibleVector<value_type> frontBack() const {
assert(begin() != end());
return {*begin(), *std::prev(end())};
}

value_type delta() const {
assert(begin() != end());
return calculateDelta(*begin(), *std::prev(end()));
}

AccessibleVector<value_type> adjacentDeltas() const {
AccessibleVector<value_type> result;

auto l0 = begin();
auto r = end();
if (l0 == r)
return result;

auto l1 = std::next(l0);
while (l1 != r)
result.push_back(calculateDelta(*l0++, *l1++));
return result;
}

value_type min() const {
assert(begin() != end());
return *std::min_element(begin(), end());
}

value_type max() const {
assert(begin() != end());
return *std::max_element(begin(), end());
}

AccessibleVector<value_type> minMax() const {
assert(begin() != end());
auto pair = std::minmax_element(begin(), end());
return {*pair.first, *pair.second};
}

auto flattened() const {
using element_type = std::iter_value_t<decltype(std::declval<const value_type *>()->begin())>;
AccessibleVector<element_type> result;
for (const auto &chunk : *this)
for (const auto &element : chunk)
result.push_back(element);
return result;
}

template<class Filter>
AccessibleVector<value_type> filtered(Filter filter) const {
AccessibleVector<value_type> result;
std::copy_if(begin(), end(), std::back_inserter(result), std::move(filter));
return result;
}

AccessibleVector<value_type> reversed() const {
AccessibleVector<value_type> result;
std::reverse_copy(begin(), end(), std::back_inserter(result));
return result;
}

bool contains(const value_type &value) const {
return std::find(begin(), end(), value) != end();
}
};

template<class L, class R>
bool operator==(const Accessible<L> &l, const Accessible<R> &r) {
return std::equal(l.begin(), l.end(), r.begin(), r.end());
}
2 changes: 1 addition & 1 deletion test/Testing/Game/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ if(OE_BUILD_TESTS)
GameTest.h
TestTape.h
TestController.h
TestVector.h)
AccessibleVector.h)

add_library(testing_game ${TESTING_GAME_SOURCES} ${TESTING_GAME_HEADERS})
target_link_libraries(testing_game PUBLIC application testing_extensions GTest::gtest)
Expand Down
4 changes: 2 additions & 2 deletions test/Testing/Game/CharacterTapeRecorder.h
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,9 @@ class CharacterTapeRecorder {
explicit CharacterTapeRecorder(TestController *controller);

template<class Callback, class T = std::invoke_result_t<Callback, const Character &>>
TestTape<TestVector<T>> custom(Callback callback) {
TestMultiTape<T> custom(Callback callback) {
return _controller->recordTape([callback = std::move(callback)] {
TestVector<T> result;
AccessibleVector<T> result;
for (const Character &character : characters())
result.push_back(callback(character));
return result;
Expand Down
Loading

0 comments on commit ebd8810

Please sign in to comment.