From 7f20e8d764a94ca96e0358375826c88ab89f1646 Mon Sep 17 00:00:00 2001 From: Paul Wichern Date: Sat, 25 Jan 2025 16:27:23 +0100 Subject: [PATCH 1/7] Use s25 random class in AIJH --- libs/s25main/ai/aijh/AIConstruction.cpp | 12 +++++++----- libs/s25main/ai/aijh/AIPlayerJH.cpp | 20 +++++++++++--------- 2 files changed, 18 insertions(+), 14 deletions(-) diff --git a/libs/s25main/ai/aijh/AIConstruction.cpp b/libs/s25main/ai/aijh/AIConstruction.cpp index dfa39ec189..5b851b5575 100644 --- a/libs/s25main/ai/aijh/AIConstruction.cpp +++ b/libs/s25main/ai/aijh/AIConstruction.cpp @@ -16,6 +16,7 @@ #include "buildings/nobMilitary.h" #include "buildings/nobUsual.h" #include "helpers/containerUtils.h" +#include "random/Random.h" #include "nodeObjs/noFlag.h" #include "nodeObjs/noRoadNode.h" #include "gameTypes/BuildingQuality.h" @@ -465,10 +466,12 @@ helpers::OptionalEnum AIConstruction::ChooseMilitaryBuilding(const const BuildingType biggestBld = GetBiggestAllowedMilBuilding().value(); const Inventory& inventory = aii.GetInventory(); - if(((rand() % 3) == 0 || inventory.people[Job::Private] < 15) + uint8_t playerId = aii.GetPlayerId(); + if((RANDOM.Rand(RANDOM_CONTEXT2(playerId), 3) == 0 || inventory.people[Job::Private] < 15) && (inventory.goods[GoodType::Stones] > 6 || bldPlanner.GetNumBuildings(BuildingType::Quarry) > 0)) bld = BuildingType::Guardhouse; - if(aijh.getAIInterface().isHarborPosClose(pt, 19) && rand() % 10 != 0 && aijh.ggs.isEnabled(AddonId::SEA_ATTACK)) + if(aijh.getAIInterface().isHarborPosClose(pt, 19) && RANDOM.Rand(RANDOM_CONTEXT2(playerId), 10) != 0 + && aijh.ggs.isEnabled(AddonId::SEA_ATTACK)) { if(aii.CanBuildBuildingtype(BuildingType::Watchtower)) return BuildingType::Watchtower; @@ -478,13 +481,12 @@ helpers::OptionalEnum AIConstruction::ChooseMilitaryBuilding(const { if(aijh.UpdateUpgradeBuilding() < 0 && bldPlanner.GetNumBuildingSites(biggestBld) < 1 && (inventory.goods[GoodType::Stones] > 20 || bldPlanner.GetNumBuildings(BuildingType::Quarry) > 0) - && rand() % 10 != 0) + && RANDOM.Rand(RANDOM_CONTEXT2(playerId), 10) != 0) { return biggestBld; } } - uint8_t playerId = aii.GetPlayerId(); sortedMilitaryBlds military = aii.gwb.LookForMilitaryBuildings(pt, 3); for(const nobBaseMilitary* milBld : military) { @@ -493,7 +495,7 @@ helpers::OptionalEnum AIConstruction::ChooseMilitaryBuilding(const // Prüfen ob Feind in der Nähe if(milBld->GetPlayer() != playerId && distance < 35) { - int randmil = rand(); + int randmil = RANDOM.Rand(RANDOM_CONTEXT2(playerId), std::numeric_limits::max()); bool buildCatapult = randmil % 8 == 0 && aii.CanBuildCatapult() && bldPlanner.GetNumAdditionalBuildingsWanted(BuildingType::Catapult) > 0; // another catapult within "min" radius? ->dont build here! diff --git a/libs/s25main/ai/aijh/AIPlayerJH.cpp b/libs/s25main/ai/aijh/AIPlayerJH.cpp index 9bd836dfb2..da23cdfcb4 100644 --- a/libs/s25main/ai/aijh/AIPlayerJH.cpp +++ b/libs/s25main/ai/aijh/AIPlayerJH.cpp @@ -26,6 +26,7 @@ #include "notifications/RoadNote.h" #include "notifications/ShipNote.h" #include "pathfinding/PathConditionRoad.h" +#include "random/Random.h" #include "nodeObjs/noAnimal.h" #include "nodeObjs/noFlag.h" #include "nodeObjs/noShip.h" @@ -344,7 +345,7 @@ void AIPlayerJH::PlanNewBuildings(const unsigned gf) DistributeGoodsByBlocking(GoodType::Boards, 30); DistributeGoodsByBlocking(GoodType::Stones, 50); // go to the picked random warehouse and try to build around it - int randomStore = rand() % (storehouses.size()); + int randomStore = RANDOM.Rand(RANDOM_CONTEXT2(playerId), storehouses.size()); auto it = storehouses.begin(); std::advance(it, randomStore); const MapPoint whPos = (*it)->GetPos(); @@ -365,7 +366,7 @@ void AIPlayerJH::PlanNewBuildings(const unsigned gf) const std::list& militaryBuildings = aii.GetMilitaryBuildings(); if(militaryBuildings.empty()) return; - int randomMiliBld = rand() % militaryBuildings.size(); + int randomMiliBld = RANDOM.Rand(RANDOM_CONTEXT2(playerId), militaryBuildings.size()); auto it2 = militaryBuildings.begin(); std::advance(it2, randomMiliBld); MapPoint bldPos = (*it2)->GetPos(); @@ -1209,7 +1210,7 @@ void AIPlayerJH::HandleExpedition(const noShip* ship) aii.FoundColony(ship); else { - const unsigned offset = rand() % helpers::MaxEnumValue_v; + const unsigned offset = RANDOM.Rand(RANDOM_CONTEXT2(playerId), helpers::MaxEnumValue_v); for(auto dir : helpers::EnumRange{}) { dir = ShipDirection((rttr::enum_cast(dir) + offset) % helpers::MaxEnumValue_v); @@ -1254,9 +1255,7 @@ void AIPlayerJH::HandleTreeChopped(const MapPoint pt) UpdateNodesAround(pt, 3); - int random = rand(); - - if(random % 2 == 0) + if(RANDOM.Rand(RANDOM_CONTEXT2(playerId), 2) == 0) AddMilitaryBuildJob(pt); else // if (random % 12 == 0) AddBuildJob(BuildingType::Woodcutter, pt); @@ -1538,7 +1537,7 @@ void AIPlayerJH::TryToAttack() // We skip the current building with a probability of limit/numMilBlds // -> For twice the number of blds as the limit we will most likely skip every 2nd building // This way we check roughly (at most) limit buildings but avoid any preference for one building over an other - if(rand() % numMilBlds > limit) + if(static_cast(RANDOM.Rand(RANDOM_CONTEXT2(playerId), static_cast(numMilBlds))) > limit) continue; if(milBld->GetFrontierDistance() == FrontierDistance::Far) // inland building? -> skip it @@ -1571,7 +1570,7 @@ void AIPlayerJH::TryToAttack() // shuffle everything but headquarters and harbors without any troops in them std::shuffle(potentialTargets.begin() + hq_or_harbor_without_soldiers, potentialTargets.end(), - std::mt19937(std::random_device()())); + std::mt19937(RANDOM.Rand(RANDOM_CONTEXT2(playerId), 2048))); // check for each potential attacking target the number of available attacking soldiers for(const nobBaseMilitary* target : potentialTargets) @@ -1704,7 +1703,10 @@ void AIPlayerJH::TrySeaAttack() unsigned limit = 15; unsigned skip = 0; if(searcharoundharborspots.size() > 15) - skip = std::max(rand() % (searcharoundharborspots.size() / 15 + 1) * 15, 1) - 1; + skip = + std::max( + RANDOM.Rand(RANDOM_CONTEXT2(playerId), static_cast(searcharoundharborspots.size() / 15 + 1) * 15), 1) + - 1; for(unsigned i = skip; i < searcharoundharborspots.size() && limit > 0; i++) { limit--; From 244e43620cb8e044d85efb7aeccadd146cb230f8 Mon Sep 17 00:00:00 2001 From: Paul Wichern Date: Sat, 8 Feb 2025 20:25:01 +0100 Subject: [PATCH 2/7] use custom std::minstd_rand based RNG for AI --- libs/s25main/ai/aijh/AIConstruction.cpp | 10 +++++----- libs/s25main/ai/aijh/AIPlayerJH.cpp | 19 ++++++++----------- libs/s25main/ai/random.cpp | 16 ++++++++++++++++ libs/s25main/ai/random.h | 20 ++++++++++++++++++++ 4 files changed, 49 insertions(+), 16 deletions(-) create mode 100644 libs/s25main/ai/random.cpp create mode 100644 libs/s25main/ai/random.h diff --git a/libs/s25main/ai/aijh/AIConstruction.cpp b/libs/s25main/ai/aijh/AIConstruction.cpp index 5b851b5575..e1237f057f 100644 --- a/libs/s25main/ai/aijh/AIConstruction.cpp +++ b/libs/s25main/ai/aijh/AIConstruction.cpp @@ -10,13 +10,13 @@ #include "addons/const_addons.h" #include "ai/AIInterface.h" #include "ai/aijh/AIPlayerJH.h" +#include "ai/random.h" #include "buildings/noBuildingSite.h" #include "buildings/nobBaseMilitary.h" #include "buildings/nobBaseWarehouse.h" #include "buildings/nobMilitary.h" #include "buildings/nobUsual.h" #include "helpers/containerUtils.h" -#include "random/Random.h" #include "nodeObjs/noFlag.h" #include "nodeObjs/noRoadNode.h" #include "gameTypes/BuildingQuality.h" @@ -467,10 +467,10 @@ helpers::OptionalEnum AIConstruction::ChooseMilitaryBuilding(const const Inventory& inventory = aii.GetInventory(); uint8_t playerId = aii.GetPlayerId(); - if((RANDOM.Rand(RANDOM_CONTEXT2(playerId), 3) == 0 || inventory.people[Job::Private] < 15) + if((AI::randomValue(0, 3) == 0 || inventory.people[Job::Private] < 15) && (inventory.goods[GoodType::Stones] > 6 || bldPlanner.GetNumBuildings(BuildingType::Quarry) > 0)) bld = BuildingType::Guardhouse; - if(aijh.getAIInterface().isHarborPosClose(pt, 19) && RANDOM.Rand(RANDOM_CONTEXT2(playerId), 10) != 0 + if(aijh.getAIInterface().isHarborPosClose(pt, 19) && AI::randomValue(0, 10) != 0 && aijh.ggs.isEnabled(AddonId::SEA_ATTACK)) { if(aii.CanBuildBuildingtype(BuildingType::Watchtower)) @@ -481,7 +481,7 @@ helpers::OptionalEnum AIConstruction::ChooseMilitaryBuilding(const { if(aijh.UpdateUpgradeBuilding() < 0 && bldPlanner.GetNumBuildingSites(biggestBld) < 1 && (inventory.goods[GoodType::Stones] > 20 || bldPlanner.GetNumBuildings(BuildingType::Quarry) > 0) - && RANDOM.Rand(RANDOM_CONTEXT2(playerId), 10) != 0) + && AI::randomValue(0, 10) != 0) { return biggestBld; } @@ -495,7 +495,7 @@ helpers::OptionalEnum AIConstruction::ChooseMilitaryBuilding(const // Prüfen ob Feind in der Nähe if(milBld->GetPlayer() != playerId && distance < 35) { - int randmil = RANDOM.Rand(RANDOM_CONTEXT2(playerId), std::numeric_limits::max()); + int randmil = AI::randomValue(0, std::numeric_limits::max()); bool buildCatapult = randmil % 8 == 0 && aii.CanBuildCatapult() && bldPlanner.GetNumAdditionalBuildingsWanted(BuildingType::Catapult) > 0; // another catapult within "min" radius? ->dont build here! diff --git a/libs/s25main/ai/aijh/AIPlayerJH.cpp b/libs/s25main/ai/aijh/AIPlayerJH.cpp index da23cdfcb4..cd5a1016f2 100644 --- a/libs/s25main/ai/aijh/AIPlayerJH.cpp +++ b/libs/s25main/ai/aijh/AIPlayerJH.cpp @@ -11,6 +11,7 @@ #include "RttrForeachPt.h" #include "addons/const_addons.h" #include "ai/AIEvents.h" +#include "ai/random.h" #include "boost/filesystem/fstream.hpp" #include "buildings/noBuildingSite.h" #include "buildings/nobHarborBuilding.h" @@ -26,7 +27,6 @@ #include "notifications/RoadNote.h" #include "notifications/ShipNote.h" #include "pathfinding/PathConditionRoad.h" -#include "random/Random.h" #include "nodeObjs/noAnimal.h" #include "nodeObjs/noFlag.h" #include "nodeObjs/noShip.h" @@ -40,7 +40,6 @@ #include #include #include -#include #include #include @@ -345,7 +344,7 @@ void AIPlayerJH::PlanNewBuildings(const unsigned gf) DistributeGoodsByBlocking(GoodType::Boards, 30); DistributeGoodsByBlocking(GoodType::Stones, 50); // go to the picked random warehouse and try to build around it - int randomStore = RANDOM.Rand(RANDOM_CONTEXT2(playerId), storehouses.size()); + int randomStore = AI::randomValue(0, storehouses.size()); auto it = storehouses.begin(); std::advance(it, randomStore); const MapPoint whPos = (*it)->GetPos(); @@ -366,7 +365,7 @@ void AIPlayerJH::PlanNewBuildings(const unsigned gf) const std::list& militaryBuildings = aii.GetMilitaryBuildings(); if(militaryBuildings.empty()) return; - int randomMiliBld = RANDOM.Rand(RANDOM_CONTEXT2(playerId), militaryBuildings.size()); + int randomMiliBld = AI::randomValue(0, militaryBuildings.size()); auto it2 = militaryBuildings.begin(); std::advance(it2, randomMiliBld); MapPoint bldPos = (*it2)->GetPos(); @@ -1210,7 +1209,7 @@ void AIPlayerJH::HandleExpedition(const noShip* ship) aii.FoundColony(ship); else { - const unsigned offset = RANDOM.Rand(RANDOM_CONTEXT2(playerId), helpers::MaxEnumValue_v); + const unsigned offset = AI::randomValue(0, helpers::MaxEnumValue_v); for(auto dir : helpers::EnumRange{}) { dir = ShipDirection((rttr::enum_cast(dir) + offset) % helpers::MaxEnumValue_v); @@ -1255,7 +1254,7 @@ void AIPlayerJH::HandleTreeChopped(const MapPoint pt) UpdateNodesAround(pt, 3); - if(RANDOM.Rand(RANDOM_CONTEXT2(playerId), 2) == 0) + if(AI::randomValue(0, 2) == 0) AddMilitaryBuildJob(pt); else // if (random % 12 == 0) AddBuildJob(BuildingType::Woodcutter, pt); @@ -1537,7 +1536,7 @@ void AIPlayerJH::TryToAttack() // We skip the current building with a probability of limit/numMilBlds // -> For twice the number of blds as the limit we will most likely skip every 2nd building // This way we check roughly (at most) limit buildings but avoid any preference for one building over an other - if(static_cast(RANDOM.Rand(RANDOM_CONTEXT2(playerId), static_cast(numMilBlds))) > limit) + if(AI::randomValue(0, numMilBlds) > limit) continue; if(milBld->GetFrontierDistance() == FrontierDistance::Far) // inland building? -> skip it @@ -1570,7 +1569,7 @@ void AIPlayerJH::TryToAttack() // shuffle everything but headquarters and harbors without any troops in them std::shuffle(potentialTargets.begin() + hq_or_harbor_without_soldiers, potentialTargets.end(), - std::mt19937(RANDOM.Rand(RANDOM_CONTEXT2(playerId), 2048))); + std::mt19937(AI::randomValue(0, 2048))); // check for each potential attacking target the number of available attacking soldiers for(const nobBaseMilitary* target : potentialTargets) @@ -1704,9 +1703,7 @@ void AIPlayerJH::TrySeaAttack() unsigned skip = 0; if(searcharoundharborspots.size() > 15) skip = - std::max( - RANDOM.Rand(RANDOM_CONTEXT2(playerId), static_cast(searcharoundharborspots.size() / 15 + 1) * 15), 1) - - 1; + std::max(AI::randomValue(0, static_cast(searcharoundharborspots.size() / 15 + 1) * 15), 1) - 1; for(unsigned i = skip; i < searcharoundharborspots.size() && limit > 0; i++) { limit--; diff --git a/libs/s25main/ai/random.cpp b/libs/s25main/ai/random.cpp new file mode 100644 index 0000000000..38a210de34 --- /dev/null +++ b/libs/s25main/ai/random.cpp @@ -0,0 +1,16 @@ +// Copyright (C) 2005 - 2025 Settlers Freaks (sf-team at siedler25.org) +// +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "ai/random.h" + +namespace AI { + +std::minstd_rand& getRandomGenerator() +{ + std::random_device rnd_dev; + static std::minstd_rand rng(rnd_dev()); + return rng; +} + +} // namespace AI diff --git a/libs/s25main/ai/random.h b/libs/s25main/ai/random.h new file mode 100644 index 0000000000..dd461cf498 --- /dev/null +++ b/libs/s25main/ai/random.h @@ -0,0 +1,20 @@ +// Copyright (C) 2005 - 2025 Settlers Freaks (sf-team at siedler25.org) +// +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include "helpers/random.h" +#include + +namespace AI { + +std::minstd_rand& getRandomGenerator(); + +template +T randomValue(T min = std::numeric_limits::min(), T max = std::numeric_limits::max()) +{ + return helpers::randomValue(getRandomGenerator(), min, max); +} + +} // namespace AI From b5f62445406cdc546599ba8a912c791825f29f58 Mon Sep 17 00:00:00 2001 From: Paul Wichern Date: Sat, 8 Feb 2025 20:29:44 +0100 Subject: [PATCH 3/7] Allow setting AI RNG init value in ai-battle --- extras/ai-battle/main.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/extras/ai-battle/main.cpp b/extras/ai-battle/main.cpp index 72d8e43630..cab8fd28de 100644 --- a/extras/ai-battle/main.cpp +++ b/extras/ai-battle/main.cpp @@ -7,6 +7,7 @@ #include "QuickStartGame.h" #include "RTTR_Version.h" #include "RttrConfig.h" +#include "ai/random.h" #include "files.h" #include "random/Random.h" #include "s25util/System.h" @@ -30,6 +31,7 @@ int main(int argc, char** argv) boost::optional replay_path; boost::optional savegame_path; unsigned random_init = static_cast(std::chrono::high_resolution_clock::now().time_since_epoch().count()); + unsigned random_ai_init = random_init; po::options_description desc("Allowed options"); // clang-format off @@ -41,6 +43,7 @@ int main(int argc, char** argv) ("replay", po::value(&replay_path),"Filename to write replay to (optional)") ("save", po::value(&savegame_path),"Filename to write savegame to (optional)") ("random_init", po::value(&random_init),"Seed value for the random number generator (optional)") + ("random_ai_init", po::value(&random_ai_init),"Seed value for the AI random number generator (optional)") ("maxGF", po::value()->default_value(std::numeric_limits::max()),"Maximum number of game frames to run (optional)") ("version", "Show version information and exit") ; @@ -85,10 +88,12 @@ int main(int argc, char** argv) bnw::cout << argv[i] << " "; bnw::cout << std::endl; bnw::cout << "random_init: " << random_init << std::endl; + bnw::cout << "random_ai_init: " << random_ai_init << std::endl; bnw::cout << std::endl; RTTRCONFIG.Init(); RANDOM.Init(random_init); + AI::getRandomGenerator().seed(random_ai_init); const bfs::path mapPath = RTTRCONFIG.ExpandPath(options["map"].as()); const std::vector ais = ParseAIOptions(options["ai"].as>()); From cdf19fce249aa86c011c4cbe2cd3334fe0d233c8 Mon Sep 17 00:00:00 2001 From: Paul Wichern Date: Sat, 8 Feb 2025 23:36:49 +0100 Subject: [PATCH 4/7] Fix off by one error --- libs/s25main/ai/aijh/AIConstruction.cpp | 6 +++--- libs/s25main/ai/aijh/AIPlayerJH.cpp | 14 +++++++------- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/libs/s25main/ai/aijh/AIConstruction.cpp b/libs/s25main/ai/aijh/AIConstruction.cpp index e1237f057f..fb1e90b573 100644 --- a/libs/s25main/ai/aijh/AIConstruction.cpp +++ b/libs/s25main/ai/aijh/AIConstruction.cpp @@ -467,10 +467,10 @@ helpers::OptionalEnum AIConstruction::ChooseMilitaryBuilding(const const Inventory& inventory = aii.GetInventory(); uint8_t playerId = aii.GetPlayerId(); - if((AI::randomValue(0, 3) == 0 || inventory.people[Job::Private] < 15) + if((AI::randomValue(0, 2) == 0 || inventory.people[Job::Private] < 15) && (inventory.goods[GoodType::Stones] > 6 || bldPlanner.GetNumBuildings(BuildingType::Quarry) > 0)) bld = BuildingType::Guardhouse; - if(aijh.getAIInterface().isHarborPosClose(pt, 19) && AI::randomValue(0, 10) != 0 + if(aijh.getAIInterface().isHarborPosClose(pt, 19) && AI::randomValue(0, 9) != 0 && aijh.ggs.isEnabled(AddonId::SEA_ATTACK)) { if(aii.CanBuildBuildingtype(BuildingType::Watchtower)) @@ -481,7 +481,7 @@ helpers::OptionalEnum AIConstruction::ChooseMilitaryBuilding(const { if(aijh.UpdateUpgradeBuilding() < 0 && bldPlanner.GetNumBuildingSites(biggestBld) < 1 && (inventory.goods[GoodType::Stones] > 20 || bldPlanner.GetNumBuildings(BuildingType::Quarry) > 0) - && AI::randomValue(0, 10) != 0) + && AI::randomValue(0, 9) != 0) { return biggestBld; } diff --git a/libs/s25main/ai/aijh/AIPlayerJH.cpp b/libs/s25main/ai/aijh/AIPlayerJH.cpp index cd5a1016f2..09d845937c 100644 --- a/libs/s25main/ai/aijh/AIPlayerJH.cpp +++ b/libs/s25main/ai/aijh/AIPlayerJH.cpp @@ -344,7 +344,7 @@ void AIPlayerJH::PlanNewBuildings(const unsigned gf) DistributeGoodsByBlocking(GoodType::Boards, 30); DistributeGoodsByBlocking(GoodType::Stones, 50); // go to the picked random warehouse and try to build around it - int randomStore = AI::randomValue(0, storehouses.size()); + int randomStore = AI::randomValue(0, storehouses.size() - 1); auto it = storehouses.begin(); std::advance(it, randomStore); const MapPoint whPos = (*it)->GetPos(); @@ -365,7 +365,7 @@ void AIPlayerJH::PlanNewBuildings(const unsigned gf) const std::list& militaryBuildings = aii.GetMilitaryBuildings(); if(militaryBuildings.empty()) return; - int randomMiliBld = AI::randomValue(0, militaryBuildings.size()); + int randomMiliBld = AI::randomValue(0, militaryBuildings.size() - 1); auto it2 = militaryBuildings.begin(); std::advance(it2, randomMiliBld); MapPoint bldPos = (*it2)->GetPos(); @@ -1209,7 +1209,7 @@ void AIPlayerJH::HandleExpedition(const noShip* ship) aii.FoundColony(ship); else { - const unsigned offset = AI::randomValue(0, helpers::MaxEnumValue_v); + const unsigned offset = AI::randomValue(0, helpers::MaxEnumValue_v - 1); for(auto dir : helpers::EnumRange{}) { dir = ShipDirection((rttr::enum_cast(dir) + offset) % helpers::MaxEnumValue_v); @@ -1254,7 +1254,7 @@ void AIPlayerJH::HandleTreeChopped(const MapPoint pt) UpdateNodesAround(pt, 3); - if(AI::randomValue(0, 2) == 0) + if(AI::randomValue(0, 1) == 0) AddMilitaryBuildJob(pt); else // if (random % 12 == 0) AddBuildJob(BuildingType::Woodcutter, pt); @@ -1536,7 +1536,7 @@ void AIPlayerJH::TryToAttack() // We skip the current building with a probability of limit/numMilBlds // -> For twice the number of blds as the limit we will most likely skip every 2nd building // This way we check roughly (at most) limit buildings but avoid any preference for one building over an other - if(AI::randomValue(0, numMilBlds) > limit) + if(AI::randomValue(0, numMilBlds - 1) > limit) continue; if(milBld->GetFrontierDistance() == FrontierDistance::Far) // inland building? -> skip it @@ -1569,7 +1569,7 @@ void AIPlayerJH::TryToAttack() // shuffle everything but headquarters and harbors without any troops in them std::shuffle(potentialTargets.begin() + hq_or_harbor_without_soldiers, potentialTargets.end(), - std::mt19937(AI::randomValue(0, 2048))); + std::mt19937(AI::randomValue(0, 2047))); // check for each potential attacking target the number of available attacking soldiers for(const nobBaseMilitary* target : potentialTargets) @@ -1703,7 +1703,7 @@ void AIPlayerJH::TrySeaAttack() unsigned skip = 0; if(searcharoundharborspots.size() > 15) skip = - std::max(AI::randomValue(0, static_cast(searcharoundharborspots.size() / 15 + 1) * 15), 1) - 1; + std::max(AI::randomValue(0, static_cast(searcharoundharborspots.size() / 15) * 15), 1) - 1; for(unsigned i = skip; i < searcharoundharborspots.size() && limit > 0; i++) { limit--; From 9c698e370ccf72d0cc784aac348ec76dee86bf1a Mon Sep 17 00:00:00 2001 From: Paul Wichern Date: Sun, 9 Feb 2025 13:34:28 +0100 Subject: [PATCH 5/7] Apply review suggestions --- libs/s25main/ai/aijh/AIConstruction.cpp | 11 +++++------ libs/s25main/ai/aijh/AIPlayerJH.cpp | 16 +++++++--------- libs/s25main/ai/random.cpp | 3 +-- libs/s25main/ai/random.h | 21 +++++++++++++++++++++ 4 files changed, 34 insertions(+), 17 deletions(-) diff --git a/libs/s25main/ai/aijh/AIConstruction.cpp b/libs/s25main/ai/aijh/AIConstruction.cpp index fb1e90b573..3310667fe8 100644 --- a/libs/s25main/ai/aijh/AIConstruction.cpp +++ b/libs/s25main/ai/aijh/AIConstruction.cpp @@ -466,12 +466,10 @@ helpers::OptionalEnum AIConstruction::ChooseMilitaryBuilding(const const BuildingType biggestBld = GetBiggestAllowedMilBuilding().value(); const Inventory& inventory = aii.GetInventory(); - uint8_t playerId = aii.GetPlayerId(); - if((AI::randomValue(0, 2) == 0 || inventory.people[Job::Private] < 15) + if((inventory.people[Job::Private] < 15 || AI::random()) && (inventory.goods[GoodType::Stones] > 6 || bldPlanner.GetNumBuildings(BuildingType::Quarry) > 0)) bld = BuildingType::Guardhouse; - if(aijh.getAIInterface().isHarborPosClose(pt, 19) && AI::randomValue(0, 9) != 0 - && aijh.ggs.isEnabled(AddonId::SEA_ATTACK)) + if(aijh.getAIInterface().isHarborPosClose(pt, 19) && !AI::random(9) && aijh.ggs.isEnabled(AddonId::SEA_ATTACK)) { if(aii.CanBuildBuildingtype(BuildingType::Watchtower)) return BuildingType::Watchtower; @@ -481,12 +479,13 @@ helpers::OptionalEnum AIConstruction::ChooseMilitaryBuilding(const { if(aijh.UpdateUpgradeBuilding() < 0 && bldPlanner.GetNumBuildingSites(biggestBld) < 1 && (inventory.goods[GoodType::Stones] > 20 || bldPlanner.GetNumBuildings(BuildingType::Quarry) > 0) - && AI::randomValue(0, 9) != 0) + && !AI::random(9u)) { return biggestBld; } } + uint8_t playerId = aii.GetPlayerId(); sortedMilitaryBlds military = aii.gwb.LookForMilitaryBuildings(pt, 3); for(const nobBaseMilitary* milBld : military) { @@ -495,7 +494,7 @@ helpers::OptionalEnum AIConstruction::ChooseMilitaryBuilding(const // Prüfen ob Feind in der Nähe if(milBld->GetPlayer() != playerId && distance < 35) { - int randmil = AI::randomValue(0, std::numeric_limits::max()); + const auto randmil = AI::randomValue(0, std::numeric_limits::max()); bool buildCatapult = randmil % 8 == 0 && aii.CanBuildCatapult() && bldPlanner.GetNumAdditionalBuildingsWanted(BuildingType::Catapult) > 0; // another catapult within "min" radius? ->dont build here! diff --git a/libs/s25main/ai/aijh/AIPlayerJH.cpp b/libs/s25main/ai/aijh/AIPlayerJH.cpp index 09d845937c..96a9c5a4e8 100644 --- a/libs/s25main/ai/aijh/AIPlayerJH.cpp +++ b/libs/s25main/ai/aijh/AIPlayerJH.cpp @@ -344,9 +344,8 @@ void AIPlayerJH::PlanNewBuildings(const unsigned gf) DistributeGoodsByBlocking(GoodType::Boards, 30); DistributeGoodsByBlocking(GoodType::Stones, 50); // go to the picked random warehouse and try to build around it - int randomStore = AI::randomValue(0, storehouses.size() - 1); auto it = storehouses.begin(); - std::advance(it, randomStore); + std::advance(it, AI::randomIndex(storehouses)); const MapPoint whPos = (*it)->GetPos(); UpdateNodesAround(whPos, 15); // update the area we want to build in first for(const BuildingType i : bldToTest) @@ -365,7 +364,7 @@ void AIPlayerJH::PlanNewBuildings(const unsigned gf) const std::list& militaryBuildings = aii.GetMilitaryBuildings(); if(militaryBuildings.empty()) return; - int randomMiliBld = AI::randomValue(0, militaryBuildings.size() - 1); + const int randomMiliBld = static_cast(AI::randomIndex(militaryBuildings)); auto it2 = militaryBuildings.begin(); std::advance(it2, randomMiliBld); MapPoint bldPos = (*it2)->GetPos(); @@ -1209,7 +1208,7 @@ void AIPlayerJH::HandleExpedition(const noShip* ship) aii.FoundColony(ship); else { - const unsigned offset = AI::randomValue(0, helpers::MaxEnumValue_v - 1); + const unsigned offset = AI::randomValue(0u, helpers::MaxEnumValue_v - 1u); for(auto dir : helpers::EnumRange{}) { dir = ShipDirection((rttr::enum_cast(dir) + offset) % helpers::MaxEnumValue_v); @@ -1254,7 +1253,7 @@ void AIPlayerJH::HandleTreeChopped(const MapPoint pt) UpdateNodesAround(pt, 3); - if(AI::randomValue(0, 1) == 0) + if(AI::random()) AddMilitaryBuildJob(pt); else // if (random % 12 == 0) AddBuildJob(BuildingType::Woodcutter, pt); @@ -1536,7 +1535,7 @@ void AIPlayerJH::TryToAttack() // We skip the current building with a probability of limit/numMilBlds // -> For twice the number of blds as the limit we will most likely skip every 2nd building // This way we check roughly (at most) limit buildings but avoid any preference for one building over an other - if(AI::randomValue(0, numMilBlds - 1) > limit) + if(AI::random(numMilBlds - 1u, limit)) continue; if(milBld->GetFrontierDistance() == FrontierDistance::Far) // inland building? -> skip it @@ -1569,7 +1568,7 @@ void AIPlayerJH::TryToAttack() // shuffle everything but headquarters and harbors without any troops in them std::shuffle(potentialTargets.begin() + hq_or_harbor_without_soldiers, potentialTargets.end(), - std::mt19937(AI::randomValue(0, 2047))); + AI::getRandomGenerator()); // check for each potential attacking target the number of available attacking soldiers for(const nobBaseMilitary* target : potentialTargets) @@ -1702,8 +1701,7 @@ void AIPlayerJH::TrySeaAttack() unsigned limit = 15; unsigned skip = 0; if(searcharoundharborspots.size() > 15) - skip = - std::max(AI::randomValue(0, static_cast(searcharoundharborspots.size() / 15) * 15), 1) - 1; + skip = AI::randomValue(0u, static_cast(searcharoundharborspots.size() / 15u)) * 15u; for(unsigned i = skip; i < searcharoundharborspots.size() && limit > 0; i++) { limit--; diff --git a/libs/s25main/ai/random.cpp b/libs/s25main/ai/random.cpp index 38a210de34..dfddfa2cdc 100644 --- a/libs/s25main/ai/random.cpp +++ b/libs/s25main/ai/random.cpp @@ -8,8 +8,7 @@ namespace AI { std::minstd_rand& getRandomGenerator() { - std::random_device rnd_dev; - static std::minstd_rand rng(rnd_dev()); + static std::minstd_rand rng(std::random_device{}()); return rng; } diff --git a/libs/s25main/ai/random.h b/libs/s25main/ai/random.h index dd461cf498..f1627a9292 100644 --- a/libs/s25main/ai/random.h +++ b/libs/s25main/ai/random.h @@ -17,4 +17,25 @@ T randomValue(T min = std::numeric_limits::min(), T max = std::numeric_limits return helpers::randomValue(getRandomGenerator(), min, max); } +// Return a random bool: +// random() ... will return true|false with 50% chance each +// random(15) ... will return true in 1/15 of the cases +inline bool random(unsigned total = 1u) +{ + return helpers::randomValue(getRandomGenerator(), 0u, total) == 0u; +} + +// random(20, 5) ... will return true in ~3/4 of the cases (random(20) > 5) +inline bool random(unsigned total, unsigned chance) +{ + return helpers::randomValue(getRandomGenerator(), 0u, total) > chance; +} + +template +inline unsigned randomIndex(const ContainerT& container) +{ + RTTR_Assert(!container.empty()); + return helpers::randomValue(getRandomGenerator(), 0u, static_cast(container.size()) - 1u); +} + } // namespace AI From 0ceff0d67a5979733ed5668acaf59af877ac1ce0 Mon Sep 17 00:00:00 2001 From: Paul Wichern Date: Thu, 13 Feb 2025 21:58:14 +0100 Subject: [PATCH 6/7] Fix review findings --- libs/s25main/ai/random.h | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/libs/s25main/ai/random.h b/libs/s25main/ai/random.h index f1627a9292..4f4a43f8af 100644 --- a/libs/s25main/ai/random.h +++ b/libs/s25main/ai/random.h @@ -11,6 +11,7 @@ namespace AI { std::minstd_rand& getRandomGenerator(); +// Return a random value (min and max are included) template T randomValue(T min = std::numeric_limits::min(), T max = std::numeric_limits::max()) { @@ -20,22 +21,17 @@ T randomValue(T min = std::numeric_limits::min(), T max = std::numeric_limits // Return a random bool: // random() ... will return true|false with 50% chance each // random(15) ... will return true in 1/15 of the cases -inline bool random(unsigned total = 1u) +// random(20, 5) ... will return true in 1/4 of the cases (random(1,20) <= 5) +inline bool random(unsigned total = 2u, unsigned chance = 1u) { - return helpers::randomValue(getRandomGenerator(), 0u, total) == 0u; -} - -// random(20, 5) ... will return true in ~3/4 of the cases (random(20) > 5) -inline bool random(unsigned total, unsigned chance) -{ - return helpers::randomValue(getRandomGenerator(), 0u, total) > chance; + return randomValue(1u, total) <= chance; } template inline unsigned randomIndex(const ContainerT& container) { RTTR_Assert(!container.empty()); - return helpers::randomValue(getRandomGenerator(), 0u, static_cast(container.size()) - 1u); + return randomValue(0u, static_cast(container.size()) - 1u); } } // namespace AI From 41864ab6adc608e5c76fd873608103b686a8b024 Mon Sep 17 00:00:00 2001 From: Paul Wichern Date: Fri, 14 Feb 2025 16:33:04 +0100 Subject: [PATCH 7/7] Fix review findings --- libs/s25main/ai/aijh/AIConstruction.cpp | 2 +- libs/s25main/ai/aijh/AIPlayerJH.cpp | 9 ++++----- libs/s25main/ai/random.h | 15 +++++++++++++-- 3 files changed, 18 insertions(+), 8 deletions(-) diff --git a/libs/s25main/ai/aijh/AIConstruction.cpp b/libs/s25main/ai/aijh/AIConstruction.cpp index 3310667fe8..103f779510 100644 --- a/libs/s25main/ai/aijh/AIConstruction.cpp +++ b/libs/s25main/ai/aijh/AIConstruction.cpp @@ -466,7 +466,7 @@ helpers::OptionalEnum AIConstruction::ChooseMilitaryBuilding(const const BuildingType biggestBld = GetBiggestAllowedMilBuilding().value(); const Inventory& inventory = aii.GetInventory(); - if((inventory.people[Job::Private] < 15 || AI::random()) + if((inventory.people[Job::Private] < 15 || AI::random(3)) && (inventory.goods[GoodType::Stones] > 6 || bldPlanner.GetNumBuildings(BuildingType::Quarry) > 0)) bld = BuildingType::Guardhouse; if(aijh.getAIInterface().isHarborPosClose(pt, 19) && !AI::random(9) && aijh.ggs.isEnabled(AddonId::SEA_ATTACK)) diff --git a/libs/s25main/ai/aijh/AIPlayerJH.cpp b/libs/s25main/ai/aijh/AIPlayerJH.cpp index 96a9c5a4e8..618714b3de 100644 --- a/libs/s25main/ai/aijh/AIPlayerJH.cpp +++ b/libs/s25main/ai/aijh/AIPlayerJH.cpp @@ -344,9 +344,8 @@ void AIPlayerJH::PlanNewBuildings(const unsigned gf) DistributeGoodsByBlocking(GoodType::Boards, 30); DistributeGoodsByBlocking(GoodType::Stones, 50); // go to the picked random warehouse and try to build around it - auto it = storehouses.begin(); - std::advance(it, AI::randomIndex(storehouses)); - const MapPoint whPos = (*it)->GetPos(); + const auto storehouse = AI::randomElement(storehouses); + const MapPoint whPos = storehouse->GetPos(); UpdateNodesAround(whPos, 15); // update the area we want to build in first for(const BuildingType i : bldToTest) { @@ -1532,10 +1531,10 @@ void AIPlayerJH::TryToAttack() constexpr unsigned limit = 40; for(const nobMilitary* milBld : militaryBuildings) { - // We skip the current building with a probability of limit/numMilBlds + // We handle the current building with a probability of limit/numMilBlds // -> For twice the number of blds as the limit we will most likely skip every 2nd building // This way we check roughly (at most) limit buildings but avoid any preference for one building over an other - if(AI::random(numMilBlds - 1u, limit)) + if(!AI::random(numMilBlds, limit)) continue; if(milBld->GetFrontierDistance() == FrontierDistance::Far) // inland building? -> skip it diff --git a/libs/s25main/ai/random.h b/libs/s25main/ai/random.h index 4f4a43f8af..9934b0c448 100644 --- a/libs/s25main/ai/random.h +++ b/libs/s25main/ai/random.h @@ -21,10 +21,11 @@ T randomValue(T min = std::numeric_limits::min(), T max = std::numeric_limits // Return a random bool: // random() ... will return true|false with 50% chance each // random(15) ... will return true in 1/15 of the cases -// random(20, 5) ... will return true in 1/4 of the cases (random(1,20) <= 5) +// random(20, 5) ... will return true in 5 out of 20 cases, i.e. a probability of 25%. Sames as random(4, 1) inline bool random(unsigned total = 2u, unsigned chance = 1u) { - return randomValue(1u, total) <= chance; + RTTR_Assert(total > 0u); + return (chance >= total) || randomValue(1u, total) <= chance; } template @@ -34,4 +35,14 @@ inline unsigned randomIndex(const ContainerT& container) return randomValue(0u, static_cast(container.size()) - 1u); } +template +inline auto randomElement(const ContainerT& container) +{ + RTTR_Assert(!container.empty()); + auto it = container.begin(); + if(container.size() > 1u) + std::advance(it, randomIndex(container)); + return *it; +} + } // namespace AI