mirror of
https://github.com/nolen777/eagle0.git
synced 2026-07-28 20:55:44 +00:00
Reuse effective destination connectivity checks (#8789)
This commit is contained in:
@@ -6,6 +6,7 @@
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <optional>
|
||||
|
||||
#include "AIMinimumDistanceAndTarget.hpp"
|
||||
#include "src/main/cpp/net/eagle0/shardok/library/fb_helpers/HexMapHelpers.hpp"
|
||||
@@ -51,34 +52,50 @@ auto CanReachAllWithDistances(
|
||||
});
|
||||
}
|
||||
|
||||
auto DefinitelyReachesAllWithoutCreatingCrossing(
|
||||
const EffectiveLandRegions &effectiveLandRegions,
|
||||
const Coords &origin,
|
||||
const CoordsSet &destinations) -> bool {
|
||||
return std::ranges::all_of(destinations, [&](const Coords &destination) {
|
||||
return effectiveLandRegions.DefinitelyConnectedWithoutCreatingCrossing(origin, destination);
|
||||
});
|
||||
}
|
||||
class EffectiveDestinations {
|
||||
private:
|
||||
const EffectiveLandRegions &effectiveLandRegions;
|
||||
std::optional<Coords> representative;
|
||||
bool shareRegion;
|
||||
|
||||
public:
|
||||
EffectiveDestinations(
|
||||
const EffectiveLandRegions &effectiveLandRegions,
|
||||
const CoordsSet &destinations)
|
||||
: effectiveLandRegions(effectiveLandRegions),
|
||||
shareRegion(true) {
|
||||
for (const Coords &destination : destinations) {
|
||||
if (!representative.has_value()) {
|
||||
representative = destination;
|
||||
} else if (!effectiveLandRegions.DefinitelyConnectedWithoutCreatingCrossing(
|
||||
*representative,
|
||||
destination)) {
|
||||
shareRegion = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[[nodiscard]] auto DefinitelyReachableFrom(const Coords &origin) const -> bool {
|
||||
return !representative.has_value() ||
|
||||
(shareRegion && effectiveLandRegions.DefinitelyConnectedWithoutCreatingCrossing(
|
||||
origin,
|
||||
*representative));
|
||||
}
|
||||
};
|
||||
|
||||
auto DefinitelyReachesAllWithoutCreatingCrossing(
|
||||
const HexMap *map,
|
||||
const net::eagle0::shardok::storage::fb::Unit *unit,
|
||||
const CoordsSet &destinations,
|
||||
const EffectiveLandRegions &effectiveLandRegions) -> bool {
|
||||
const EffectiveDestinations &effectiveDestinations) -> bool {
|
||||
if (unit->status() == net::eagle0::shardok::storage::fb::UnitStatus_NORMAL_UNIT) {
|
||||
return DefinitelyReachesAllWithoutCreatingCrossing(
|
||||
effectiveLandRegions,
|
||||
unit->location(),
|
||||
destinations);
|
||||
return effectiveDestinations.DefinitelyReachableFrom(unit->location());
|
||||
}
|
||||
|
||||
const auto *startingPositions =
|
||||
map->attacker_starting_positions()->Get(unit->starting_position_index())->positions();
|
||||
return std::ranges::all_of(*startingPositions, [&](const Coords *position) {
|
||||
return DefinitelyReachesAllWithoutCreatingCrossing(
|
||||
effectiveLandRegions,
|
||||
*position,
|
||||
destinations);
|
||||
return effectiveDestinations.DefinitelyReachableFrom(*position);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -140,20 +157,21 @@ auto UnitIdsRequiringWaterCrossing(
|
||||
const auto *units = state->units();
|
||||
const HexMap *map = state->hex_map();
|
||||
const EffectiveLandRegions effectiveLandRegions(landRegions, map);
|
||||
const EffectiveDestinations effectiveDestinations(effectiveLandRegions, destinations);
|
||||
|
||||
std::vector<const net::eagle0::shardok::storage::fb::Unit *> unitsToCheck{};
|
||||
unitsToCheck.reserve(units->size());
|
||||
const auto queueForExactCheck = [&](const auto *unit) {
|
||||
if (unitsToCheck.empty()) { unitsToCheck.reserve(units->size()); }
|
||||
unitsToCheck.push_back(unit);
|
||||
};
|
||||
std::array<int8_t, 9> startingPositionIndices{};
|
||||
startingPositionIndices.fill(-1);
|
||||
for (const auto *unit : *units) {
|
||||
if (unit->player_id() != pid) continue;
|
||||
|
||||
if (unit->status() == net::eagle0::shardok::storage::fb::UnitStatus_NORMAL_UNIT) {
|
||||
if (!DefinitelyReachesAllWithoutCreatingCrossing(
|
||||
effectiveLandRegions,
|
||||
unit->location(),
|
||||
destinations)) {
|
||||
unitsToCheck.push_back(unit);
|
||||
if (!effectiveDestinations.DefinitelyReachableFrom(unit->location())) {
|
||||
queueForExactCheck(unit);
|
||||
}
|
||||
} else if (unit->status() == net::eagle0::shardok::storage::fb::UnitStatus_RESERVE_UNIT) {
|
||||
if (startingPositionIndices[unit->starting_position_index()] == -1) {
|
||||
@@ -162,10 +180,7 @@ auto UnitIdsRequiringWaterCrossing(
|
||||
->Get(unit->starting_position_index())
|
||||
->positions();
|
||||
for (const Coords *position : *startingPositions) {
|
||||
if (!DefinitelyReachesAllWithoutCreatingCrossing(
|
||||
effectiveLandRegions,
|
||||
*position,
|
||||
destinations)) {
|
||||
if (!effectiveDestinations.DefinitelyReachableFrom(*position)) {
|
||||
startingPositionIndices[unit->starting_position_index()] = 1;
|
||||
break;
|
||||
}
|
||||
@@ -173,7 +188,7 @@ auto UnitIdsRequiringWaterCrossing(
|
||||
}
|
||||
|
||||
if (startingPositionIndices[unit->starting_position_index()] == 1) {
|
||||
unitsToCheck.push_back(unit);
|
||||
queueForExactCheck(unit);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -279,6 +294,7 @@ auto HasEnoughNonUndeadAttackersWithoutWaterCrossing(
|
||||
const auto *units = state->units();
|
||||
const HexMap *baseMap = state->hex_map();
|
||||
const EffectiveLandRegions effectiveLandRegions(landRegions, baseMap);
|
||||
const EffectiveDestinations effectiveDestinations(effectiveLandRegions, destinations);
|
||||
|
||||
std::size_t eligibleUnitCount = 0;
|
||||
std::size_t reachableUnitCount = 0;
|
||||
@@ -286,11 +302,7 @@ auto HasEnoughNonUndeadAttackersWithoutWaterCrossing(
|
||||
if (!IsActiveNonUndeadAttacker(state, unit)) continue;
|
||||
|
||||
++eligibleUnitCount;
|
||||
if (DefinitelyReachesAllWithoutCreatingCrossing(
|
||||
baseMap,
|
||||
unit,
|
||||
destinations,
|
||||
effectiveLandRegions) &&
|
||||
if (DefinitelyReachesAllWithoutCreatingCrossing(baseMap, unit, effectiveDestinations) &&
|
||||
++reachableUnitCount >= requiredUnitCount) {
|
||||
return true;
|
||||
}
|
||||
@@ -334,11 +346,7 @@ auto HasEnoughNonUndeadAttackersWithoutWaterCrossing(
|
||||
|
||||
for (const auto *unit : *units) {
|
||||
if (!IsActiveNonUndeadAttacker(state, unit)) continue;
|
||||
if (DefinitelyReachesAllWithoutCreatingCrossing(
|
||||
baseMap,
|
||||
unit,
|
||||
destinations,
|
||||
effectiveLandRegions)) {
|
||||
if (DefinitelyReachesAllWithoutCreatingCrossing(baseMap, unit, effectiveDestinations)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
@@ -118,6 +118,43 @@ TEST_F(AIWaterCrossingCalculatorTest, UnitsRequiringWaterCrossing_allSameSide_re
|
||||
EXPECT_EQ(0, ActionPointDistancesCache::GetThreadLocalCacheSize());
|
||||
}
|
||||
|
||||
TEST_F(AIWaterCrossingCalculatorTest, UnitsRequiringWaterCrossing_emptyDestinations_returnsZero) {
|
||||
const CoordsSet destinations(hexMap);
|
||||
|
||||
EXPECT_TRUE(UnitIdsRequiringWaterCrossing(
|
||||
gameState,
|
||||
0,
|
||||
destinations,
|
||||
alCache->GetLandRegions(),
|
||||
apdCache,
|
||||
[](BattalionTypeId typeId) {
|
||||
return GetGameSettings()->GetGetter().GetBattalionType(typeId);
|
||||
})
|
||||
.empty());
|
||||
EXPECT_EQ(0, ActionPointDistancesCache::GetThreadLocalCacheSize());
|
||||
}
|
||||
|
||||
TEST_F(AIWaterCrossingCalculatorTest,
|
||||
UnitsRequiringWaterCrossing_destinationsInDifferentRegions_returnsTwo) {
|
||||
CoordsSet destinations(hexMap);
|
||||
destinations.Add(sameSideDestination);
|
||||
destinations.Add(acrossDestination);
|
||||
|
||||
const auto expected = std::vector<UnitId>{0, 1};
|
||||
EXPECT_EQ(
|
||||
expected,
|
||||
UnitIdsRequiringWaterCrossing(
|
||||
gameState,
|
||||
0,
|
||||
destinations,
|
||||
alCache->GetLandRegions(),
|
||||
apdCache,
|
||||
[](BattalionTypeId typeId) {
|
||||
return GetGameSettings()->GetGetter().GetBattalionType(typeId);
|
||||
}));
|
||||
EXPECT_EQ(0, ActionPointDistancesCache::GetThreadLocalCacheSize());
|
||||
}
|
||||
|
||||
TEST_F(AIWaterCrossingCalculatorTest, UnitsRequiringWaterCrossing_allOtherSide_returnsTwo) {
|
||||
unit0->mutable_location() = Coords(5, 4);
|
||||
unit1->mutable_location() = Coords(5, 0);
|
||||
@@ -270,6 +307,29 @@ TEST_F(AIWaterCrossingCalculatorTest,
|
||||
EXPECT_EQ(0, ActionPointDistancesCache::GetThreadLocalCacheSize());
|
||||
}
|
||||
|
||||
TEST_F(AIWaterCrossingCalculatorTest,
|
||||
UnitsRequiringWaterCrossing_bridgeConnectsAllDestinationRegions_skipsDistances) {
|
||||
auto& bridgeModifier = MutableTerrain(hexMap, Coords(1, 3))->mutable_modifier();
|
||||
bridgeModifier.mutable_bridge().mutate_present(true);
|
||||
bridgeModifier.mutable_bridge().mutate_integrity(100.0);
|
||||
|
||||
CoordsSet destinations(hexMap);
|
||||
destinations.Add(sameSideDestination);
|
||||
destinations.Add(acrossDestination);
|
||||
|
||||
EXPECT_TRUE(UnitIdsRequiringWaterCrossing(
|
||||
gameState,
|
||||
0,
|
||||
destinations,
|
||||
alCache->GetLandRegions(),
|
||||
apdCache,
|
||||
[](BattalionTypeId typeId) {
|
||||
return GetGameSettings()->GetGetter().GetBattalionType(typeId);
|
||||
})
|
||||
.empty());
|
||||
EXPECT_EQ(0, ActionPointDistancesCache::GetThreadLocalCacheSize());
|
||||
}
|
||||
|
||||
TEST_F(AIWaterCrossingCalculatorTest,
|
||||
UnitsRequiringWaterCrossing_irrelevantBridgeStillSkipsDistances) {
|
||||
auto& bridgeModifier = MutableTerrain(hexMap, Coords(3, 5))->mutable_modifier();
|
||||
|
||||
Reference in New Issue
Block a user