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>()); diff --git a/libs/s25main/ai/aijh/AIConstruction.cpp b/libs/s25main/ai/aijh/AIConstruction.cpp index dfa39ec189..103f779510 100644 --- a/libs/s25main/ai/aijh/AIConstruction.cpp +++ b/libs/s25main/ai/aijh/AIConstruction.cpp @@ -10,6 +10,7 @@ #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" @@ -465,10 +466,10 @@ helpers::OptionalEnum AIConstruction::ChooseMilitaryBuilding(const const BuildingType biggestBld = GetBiggestAllowedMilBuilding().value(); const Inventory& inventory = aii.GetInventory(); - if(((rand() % 3) == 0 || inventory.people[Job::Private] < 15) + 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) && rand() % 10 != 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; @@ -478,7 +479,7 @@ 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) + && !AI::random(9u)) { return biggestBld; } @@ -493,7 +494,7 @@ helpers::OptionalEnum AIConstruction::ChooseMilitaryBuilding(const // Prüfen ob Feind in der Nähe if(milBld->GetPlayer() != playerId && distance < 35) { - int randmil = rand(); + 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 9bd836dfb2..618714b3de 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" @@ -39,7 +40,6 @@ #include #include #include -#include #include #include @@ -344,10 +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 = rand() % (storehouses.size()); - auto it = storehouses.begin(); - std::advance(it, randomStore); - 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) { @@ -365,7 +363,7 @@ void AIPlayerJH::PlanNewBuildings(const unsigned gf) const std::list& militaryBuildings = aii.GetMilitaryBuildings(); if(militaryBuildings.empty()) return; - int randomMiliBld = rand() % militaryBuildings.size(); + const int randomMiliBld = static_cast(AI::randomIndex(militaryBuildings)); auto it2 = militaryBuildings.begin(); std::advance(it2, randomMiliBld); MapPoint bldPos = (*it2)->GetPos(); @@ -1209,7 +1207,7 @@ void AIPlayerJH::HandleExpedition(const noShip* ship) aii.FoundColony(ship); else { - const unsigned offset = rand() % helpers::MaxEnumValue_v; + 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,9 +1252,7 @@ void AIPlayerJH::HandleTreeChopped(const MapPoint pt) UpdateNodesAround(pt, 3); - int random = rand(); - - if(random % 2 == 0) + if(AI::random()) AddMilitaryBuildJob(pt); else // if (random % 12 == 0) AddBuildJob(BuildingType::Woodcutter, pt); @@ -1535,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(rand() % numMilBlds > limit) + if(!AI::random(numMilBlds, limit)) continue; if(milBld->GetFrontierDistance() == FrontierDistance::Far) // inland building? -> skip it @@ -1571,7 +1567,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()())); + AI::getRandomGenerator()); // check for each potential attacking target the number of available attacking soldiers for(const nobBaseMilitary* target : potentialTargets) @@ -1704,7 +1700,7 @@ 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 = 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 new file mode 100644 index 0000000000..dfddfa2cdc --- /dev/null +++ b/libs/s25main/ai/random.cpp @@ -0,0 +1,15 @@ +// 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() +{ + static std::minstd_rand rng(std::random_device{}()); + return rng; +} + +} // namespace AI diff --git a/libs/s25main/ai/random.h b/libs/s25main/ai/random.h new file mode 100644 index 0000000000..9934b0c448 --- /dev/null +++ b/libs/s25main/ai/random.h @@ -0,0 +1,48 @@ +// 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(); + +// Return a random value (min and max are included) +template +T randomValue(T min = std::numeric_limits::min(), T max = std::numeric_limits::max()) +{ + 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 +// 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) +{ + RTTR_Assert(total > 0u); + return (chance >= total) || randomValue(1u, total) <= chance; +} + +template +inline unsigned randomIndex(const ContainerT& container) +{ + RTTR_Assert(!container.empty()); + 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