-
Notifications
You must be signed in to change notification settings - Fork 99
ai-battle: support team assignment (--teams) and establish team start-pacts #1957
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
FlexApex
wants to merge
10
commits into
Return-To-The-Roots:master
Choose a base branch
from
FlexApex:ai-battle-teams
base: master
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
10 commits
Select commit
Hold shift + click to select a range
06ab684
Fix and extend AI-Battle
MichalLabuda 0b91823
Fix LoadLuaScript dangling ref, restore original help text style with…
MichalLabuda 9c00cab
Move lua script loading to the HeadlessGame constructor
MichalLabuda 8e40140
Add suppressStdout flag to LuaInterfaceBase to redirect Lua log outpu…
MichalLabuda 490b70f
Merge branch 'master' into fix-and-extend-ai-battle
Flamefire 28b52fd
Apply clang-tidy: use auto for get_value<unsigned>() result
MichalLabuda 96c5682
Merge branch 'master' into fix-and-extend-ai-battle
MichalLabuda 02cc027
Use s25util::fromStringClassic to parse addon IDs locale-independently
MichalLabuda 94ae86c
ai-battle: add --teams option for team assignment
FlexApex 2841770
ai-battle: establish team start-pacts in HeadlessGame to match GameCl…
FlexApex File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,4 +1,4 @@ | ||
| // Copyright (C) 2005 - 2024 Settlers Freaks (sf-team at siedler25.org) | ||
| // Copyright (C) 2005 - 2026 Settlers Freaks (sf-team at siedler25.org) | ||
| // | ||
| // SPDX-License-Identifier: GPL-2.0-or-later | ||
|
|
||
|
|
@@ -7,16 +7,25 @@ | |
| #include "QuickStartGame.h" | ||
| #include "RTTR_Version.h" | ||
| #include "RttrConfig.h" | ||
| #include "addons/Addon.h" | ||
| #include "addons/AddonBool.h" | ||
| #include "addons/AddonList.h" | ||
| #include "addons/const_addons.h" | ||
| #include "ai/random.h" | ||
| #include "files.h" | ||
| #include "gameTypes/TeamTypes.h" | ||
| #include "random/Random.h" | ||
| #include "s25util/StringConversion.h" | ||
| #include "s25util/System.h" | ||
|
|
||
| #include <boost/filesystem.hpp> | ||
| #include <boost/nowide/args.hpp> | ||
| #include <boost/nowide/filesystem.hpp> | ||
| #include <boost/nowide/iostream.hpp> | ||
| #include <boost/program_options.hpp> | ||
| #include <boost/property_tree/ini_parser.hpp> | ||
| #include <iomanip> | ||
| #include <sstream> | ||
| #if BOOST_VERSION >= 109000 | ||
| # include <optional> | ||
| using std::optional; | ||
|
|
@@ -29,13 +38,47 @@ namespace bnw = boost::nowide; | |
| namespace bfs = boost::filesystem; | ||
| namespace po = boost::program_options; | ||
|
|
||
| static void loadAddonsFromIni(GlobalGameSettings& ggs, const bfs::path& iniPath) | ||
| { | ||
| if(!bfs::exists(iniPath)) | ||
| throw std::runtime_error("Settings file not found: " + iniPath.string()); | ||
|
|
||
| boost::property_tree::ptree tree; | ||
| boost::property_tree::read_ini(iniPath.string(), tree); | ||
|
|
||
| const auto addons = tree.get_child_optional("addons"); | ||
| if(!addons) | ||
| { | ||
| bnw::cout << "Note: no [addons] section in " << iniPath << ", using defaults.\n"; | ||
| return; | ||
| } | ||
|
|
||
| unsigned loaded = 0; | ||
| for(const auto& entry : *addons) | ||
| { | ||
| try | ||
| { | ||
| const auto id = static_cast<AddonId>(s25util::fromStringClassic<unsigned>(entry.first)); | ||
| const auto v = entry.second.get_value<unsigned>(); | ||
| ggs.setSelection(id, v); | ||
| ++loaded; | ||
| } catch(const std::exception&) | ||
| { | ||
| // Unknown or invalid entry - skip silently | ||
| } | ||
| } | ||
| bnw::cout << "Loaded " << loaded << " addon settings from " << iniPath << '\n'; | ||
| } | ||
|
|
||
| int main(int argc, char** argv) | ||
| { | ||
| bnw::nowide_filesystem(); | ||
| bnw::args _(argc, argv); | ||
|
|
||
| optional<std::string> replay_path; | ||
| optional<std::string> savegame_path; | ||
| optional<std::string> lua_path; | ||
| optional<std::string> settings_path; | ||
| unsigned random_init = static_cast<unsigned>(std::chrono::high_resolution_clock::now().time_since_epoch().count()); | ||
| unsigned random_ai_init = random_init; | ||
|
|
||
|
|
@@ -44,20 +87,31 @@ int main(int argc, char** argv) | |
| desc.add_options() | ||
| ("help,h", "Show help") | ||
| ("map,m", po::value<std::string>()->required(),"Map to load") | ||
| ("ai", po::value<std::vector<std::string>>()->required(),"AI player(s) to add") | ||
| ("objective", po::value<std::string>()->default_value("domination"),"domination(default)|conquer") | ||
| ("ai", po::value<std::vector<std::string>>()->required(),"AI player(s) to add (aijh | dummy)") | ||
| ("teams", po::value<std::string>(),"Team assignment, e.g. \"0,1;2,3\" for a 2v2 (groups separated by ';', player indices by ','). Allied players get start pacts.") | ||
| ("objective", po::value<std::string>()->default_value("domination"),"domination(default) | conquer") | ||
| ("wares", po::value<std::string>()->default_value("normal"),"Starting wares: vlow | low | normal (default) | alot") | ||
| ("settings", po::value(&settings_path),"INI file with an [addons] section to configure addon settings (optional)") | ||
| ("replay", po::value(&replay_path),"Filename to write replay to (optional)") | ||
| ("save", po::value(&savegame_path),"Filename to write savegame to (optional)") | ||
| ("lua", po::value(&lua_path),"Lua script to execute during the game (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<unsigned>()->default_value(std::numeric_limits<unsigned>::max()),"Maximum number of game frames to run (optional)") | ||
| ("version", "Show version information and exit") | ||
| ; | ||
| // clang-format on | ||
|
|
||
| const auto printHelp = [&](std::ostream& os) { | ||
| os << desc | ||
| << "\nNote: path arguments support the <RTTR_USERDATA> placeholder " | ||
| "(game data folder: SAVES, REPLAYS, MAPS, PRESETS)." | ||
| << std::endl; | ||
| }; | ||
|
|
||
| if(argc == 1) | ||
| { | ||
| bnw::cerr << desc << std::endl; | ||
| printHelp(bnw::cerr); | ||
| return 1; | ||
| } | ||
|
|
||
|
|
@@ -68,7 +122,7 @@ int main(int argc, char** argv) | |
|
|
||
| if(options.count("help")) | ||
| { | ||
| bnw::cout << desc << std::endl; | ||
| printHelp(bnw::cout); | ||
| return 0; | ||
| } | ||
| if(options.count("version")) | ||
|
|
@@ -83,7 +137,7 @@ int main(int argc, char** argv) | |
| } catch(const std::exception& e) | ||
| { | ||
| bnw::cerr << "Error: " << e.what() << std::endl; | ||
| bnw::cerr << desc << std::endl; | ||
| printHelp(bnw::cerr); | ||
| return 1; | ||
| } | ||
|
|
||
|
|
@@ -116,15 +170,79 @@ int main(int argc, char** argv) | |
| return 1; | ||
| } | ||
|
|
||
| ggs.objective = GameObjective::TotalDomination; | ||
| HeadlessGame game(ggs, mapPath, ais); | ||
| const auto wares = options["wares"].as<std::string>(); | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Any specific reason to use this construct here but have lua/settings_path "auto-filled"? |
||
| if(wares == "vlow") | ||
| ggs.startWares = StartWares::VLow; | ||
| else if(wares == "low") | ||
| ggs.startWares = StartWares::Low; | ||
| else if(wares == "normal") | ||
| ggs.startWares = StartWares::Normal; | ||
| else if(wares == "alot") | ||
| ggs.startWares = StartWares::ALot; | ||
| else | ||
| { | ||
| bnw::cerr << "Unknown wares value: " << wares << std::endl; | ||
| return 1; | ||
| } | ||
|
|
||
| if(settings_path) | ||
| { | ||
| loadAddonsFromIni(ggs, RTTRCONFIG.ExpandPath(*settings_path)); | ||
|
|
||
| bnw::cout << "settings: " << RTTRCONFIG.ExpandPath(*settings_path) << std::endl; | ||
| bnw::cout << "addon selections (non-default only):" << std::endl; | ||
| for(unsigned i = 0; i < ggs.getNumAddons(); ++i) | ||
| { | ||
| unsigned status = 0; | ||
| const Addon* addon = ggs.getAddon(i, status); | ||
| if(addon && status != addon->getDefaultStatus()) | ||
| { | ||
| bnw::cout << " [0x" << std::hex << std::setw(8) << std::setfill('0') | ||
| << static_cast<unsigned>(addon->getId()) << std::dec << "] " << addon->getName() << " = "; | ||
| if(const auto* listAddon = dynamic_cast<const AddonList*>(addon)) | ||
| bnw::cout << listAddon->getOptionName(status); | ||
| else if(dynamic_cast<const AddonBool*>(addon)) | ||
| bnw::cout << (status ? "True" : "False"); | ||
| else | ||
| bnw::cout << status; | ||
| bnw::cout << std::endl; | ||
| } | ||
| } | ||
| } | ||
|
|
||
| // Team assignment, e.g. "0,1;2,3". Player index -> Team (Team1, Team2, ...). | ||
| std::vector<Team> teams; | ||
| if(options.count("teams")) | ||
| { | ||
| std::stringstream groups(options["teams"].as<std::string>()); | ||
| std::string group; | ||
| unsigned teamIdx = 0; | ||
| while(std::getline(groups, group, ';')) | ||
| { | ||
| const Team team = static_cast<Team>(static_cast<uint8_t>(Team::Team1) + teamIdx); | ||
| std::stringstream members(group); | ||
| std::string idx; | ||
| while(std::getline(members, idx, ',')) | ||
| { | ||
| if(idx.empty()) | ||
| continue; | ||
| const unsigned p = static_cast<unsigned>(std::stoul(idx)); | ||
| if(p >= teams.size()) | ||
| teams.resize(p + 1, Team::None); | ||
| teams[p] = team; | ||
| } | ||
| ++teamIdx; | ||
| } | ||
| } | ||
|
|
||
| HeadlessGame game(ggs, mapPath, ais, lua_path ? RTTRCONFIG.ExpandPath(*lua_path) : bfs::path{}, teams); | ||
| if(replay_path) | ||
| game.RecordReplay(*replay_path, random_init); | ||
| game.RecordReplay(RTTRCONFIG.ExpandPath(*replay_path), random_init); | ||
|
|
||
| game.Run(options["maxGF"].as<unsigned>()); | ||
| game.Close(); | ||
| if(savegame_path) | ||
| game.SaveGame(*savegame_path); | ||
| game.SaveGame(RTTRCONFIG.ExpandPath(*savegame_path)); | ||
| } catch(const std::exception& e) | ||
| { | ||
| bnw::cerr << e.what() << std::endl; | ||
|
|
||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd say this should be a hard error: The user intended to do something which isn't done.
A missing addon section would also be an error, maybe unless the file is fully empty. Otherwise an accidental spelling mistake like "adon" goes unnoticed.