From 714678115d03a7a366742f9e54cb7aa1db68cdaa Mon Sep 17 00:00:00 2001 From: Ematerasu Date: Thu, 2 Oct 2025 10:59:47 +0200 Subject: [PATCH] Enable patron configuration --- Engine/src/AI/ScriptsOfTribute.cs | 91 +++++++++++++++++++++++++++---- GameRunner/Program.cs | 31 ++++++++--- 2 files changed, 104 insertions(+), 18 deletions(-) diff --git a/Engine/src/AI/ScriptsOfTribute.cs b/Engine/src/AI/ScriptsOfTribute.cs index 600e98b..08ac689 100644 --- a/Engine/src/AI/ScriptsOfTribute.cs +++ b/Engine/src/AI/ScriptsOfTribute.cs @@ -1,13 +1,19 @@ using ScriptsOfTribute.Board; -using ScriptsOfTribute.utils; -using ScriptsOfTribute.Serializers; namespace ScriptsOfTribute.AI; public class ScriptsOfTribute { - private AI[] _players = new AI[2]; + private static readonly PatronId[] DefaultPatrons = + Enum.GetValues(typeof(PatronId)) + .Cast() + .Where(id => id != PatronId.TREASURY) + .ToArray(); + + public static PatronId[] GetDefaultPatrons() => (PatronId[])DefaultPatrons.Clone(); + private AI[] _players = new AI[2]; + private PatronId[] _patrons; private ScriptsOfTributeGame? _game; private ulong _seed; @@ -25,7 +31,7 @@ public ulong Seed public bool P2LoggerEnabled { get; set; } = false; public TimeSpan Timeout { get; set; } - public ScriptsOfTribute(AI player1, AI player2, TimeSpan timeout) + public ScriptsOfTribute(AI player1, AI player2, TimeSpan timeout, PatronId[] patrons) { _players[0] = player1; _players[1] = player2; @@ -33,9 +39,14 @@ public ScriptsOfTribute(AI player1, AI player2, TimeSpan timeout) player2.Id = PlayerEnum.PLAYER2; Seed = (ulong)Environment.TickCount; Timeout = timeout; + _patrons = patrons; } - public ScriptsOfTribute(AI player1, AI player2) : this(player1, player2, TimeSpan.FromSeconds(30)) + public ScriptsOfTribute(AI player1, AI player2) : this(player1, player2, TimeSpan.FromSeconds(30), DefaultPatrons) + { } + + public ScriptsOfTribute(AI player1, AI player2, TimeSpan timeout, IEnumerable? patronTokens) + : this(player1, player2, timeout, ResolvePatrons(patronTokens)) { } private Task SelectPatronTask(AI currentPlayer, List availablePatrons, int round) @@ -61,11 +72,9 @@ private Task SelectPatronTask(AI currentPlayer, List availab return (new EndGameState(playerToWin, GameEndReason.PATRON_SELECTION_TIMEOUT), null); } - private (EndGameState? ,PatronId[]?) PatronSelection() + private (EndGameState?, PatronId[]?) PatronSelection() { - List patrons = Enum.GetValues(typeof(PatronId)).Cast() - // TODO: Hardcoded banned patrons, improve this. - .Where(patronId => patronId != PatronId.TREASURY && patronId != PatronId.PSIJIC).ToList(); + List patrons = _patrons.ToList(); List patronsSelected = new List(); var (endGameState, patron) = SelectPatronWithTimeout(PlayerEnum.PLAYER2, _players[0], patrons, 1); @@ -121,7 +130,7 @@ private Task SelectPatronTask(AI currentPlayer, List availab PatronId[]? patrons; endGameState = PrepareBots(_players[0], _players[1], 5); - + if (endGameState is not null) { _players[0].GameEnd(endGameState, null); @@ -180,4 +189,66 @@ private Task SelectPatronTask(AI currentPlayer, List availab return null; } + + public static PatronId[] ResolvePatrons(IEnumerable? rawTokens) + { + bool userSpecified = false; + + if (rawTokens == null) + return GetDefaultPatrons(); + + var tokens = rawTokens + .SelectMany(s => s.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries)) + .Select(t => t.Trim()) + .Where(t => t.Length > 0) + .ToArray(); + + userSpecified = tokens.Length > 0; + + if (!userSpecified) + return GetDefaultPatrons(); + + if (tokens.Length == 1 && (tokens[0].Equals("default", StringComparison.OrdinalIgnoreCase) + || tokens[0].Equals("all", StringComparison.OrdinalIgnoreCase))) + return GetDefaultPatrons(); + + var parsed = new List(tokens.Length); + var invalid = new List(); + + foreach (var t in tokens) + { + if (TryParsePatronToken(t, out var id)) + parsed.Add(id); + else + invalid.Add(t); + } + + if (invalid.Count > 0) + { + var valid = string.Join(", ", Enum.GetNames(typeof(PatronId))); + throw new ArgumentException($"Unknown patron(s): {string.Join(", ", invalid)}\nValid: {valid}"); + } + + var result = parsed.Where(p => p != PatronId.TREASURY).Distinct().ToArray(); + + if (userSpecified && result.Length < 4) + throw new ArgumentException("Provide at least 4 selectable patrons."); + + return result.Length > 0 ? result : GetDefaultPatrons(); + } + + private static bool TryParsePatronToken(string token, out PatronId id) + { + if (Enum.TryParse(token, ignoreCase: true, out id)) + return true; + + if (int.TryParse(token, out var numeric) && Enum.IsDefined(typeof(PatronId), numeric)) + { + id = (PatronId)numeric; + return true; + } + + id = default; + return false; + } } diff --git a/GameRunner/Program.cs b/GameRunner/Program.cs index 9f6329f..e376947 100644 --- a/GameRunner/Program.cs +++ b/GameRunner/Program.cs @@ -35,6 +35,7 @@ var timeoutOption = CreateOption("--timeout", "Game timeout in seconds.", 30, "-to"); var clientPortOption = CreateOption("--client-port", "Base client port for gRPC bots.", 50000, "-cp"); var serverPortOption = CreateOption("--server-port", "Base server port for gRPC bots.", 49000, "-sp"); +var patronsOption = CreateOption("--patrons", "Allowed patrons for this run. Use 'default' (alias: 'all') for standard set (all selectable, no TREASURY).", Array.Empty(), "-p"); var bot1NameArgument = CreateBotArgument("bot1", "Name of the first bot or command."); var bot2NameArgument = CreateBotArgument("bot2", "Name of the second bot or command."); @@ -49,6 +50,7 @@ timeoutOption, clientPortOption, serverPortOption, + patronsOption, bot1NameArgument, bot2NameArgument, }; @@ -154,9 +156,13 @@ #region Prepare game -ScriptsOfTribute.AI.ScriptsOfTribute PrepareGame(AI bot1, AI bot2, LogsEnabled enableLogs, ulong seed, LogFileNameProvider? logProvider, int timeout) +ScriptsOfTribute.AI.ScriptsOfTribute PrepareGame( + AI bot1, + AI bot2, + LogsEnabled enableLogs, ulong seed, LogFileNameProvider? logProvider, int timeout, IEnumerable? patronTokens +) { - var game = new ScriptsOfTribute.AI.ScriptsOfTribute(bot1, bot2, TimeSpan.FromSeconds(timeout)) + var game = new ScriptsOfTribute.AI.ScriptsOfTribute(bot1, bot2, TimeSpan.FromSeconds(timeout), patronTokens) { Seed = seed, }; @@ -203,6 +209,7 @@ ScriptsOfTribute.AI.ScriptsOfTribute PrepareGame(AI bot1, AI bot2, LogsEnabled e int timeout = context.ParseResult.GetValueForOption(timeoutOption); int baseClientPort = context.ParseResult.GetValueForOption(clientPortOption); int baseServerPort = context.ParseResult.GetValueForOption(serverPortOption); + IEnumerable? patrons = context.ParseResult.GetValueForOption(patronsOption); BotInfo? bot1Info = context.ParseResult.GetValueForArgument(bot1NameArgument); BotInfo? bot2Info = context.ParseResult.GetValueForArgument(bot2NameArgument); @@ -219,9 +226,9 @@ ScriptsOfTribute.AI.ScriptsOfTribute PrepareGame(AI bot1, AI bot2, LogsEnabled e ulong actualSeed = seed ?? (ulong)new Random().NextInt64(); if (threads == 1) - RunSingleThreaded(runs, bot1Info, bot2Info, logs, logProvider, actualSeed, timeout, baseClientPort, baseServerPort, baseHost); + RunSingleThreaded(runs, bot1Info, bot2Info, logs, logProvider, actualSeed, timeout, baseClientPort, baseServerPort, patrons, baseHost); else - RunMultiThreaded(runs, threads, bot1Info, bot2Info, logs, logProvider, actualSeed, timeout, baseClientPort, baseServerPort, baseHost); + RunMultiThreaded(runs, threads, bot1Info, bot2Info, logs, logProvider, actualSeed, timeout, baseClientPort, baseServerPort, patrons, baseHost); }); void RunSingleThreaded( @@ -234,6 +241,7 @@ void RunSingleThreaded( int timeout, int baseClientPort, int baseServerPort, + IEnumerable? patrons, string baseHost = "localhost" ) { @@ -262,7 +270,7 @@ void RunSingleThreaded( for (var i = 0; i < runs; i++) { - var game = PrepareGame(bot1, bot2, enableLogs, currentSeed, logFileNameProvider, timeout); + var game = PrepareGame(bot1, bot2, enableLogs, currentSeed, logFileNameProvider, timeout, patrons); currentSeed += 1; granularWatch.Reset(); @@ -301,6 +309,7 @@ void RunMultiThreaded( int timeout, int baseClientPort, int baseServerPort, + IEnumerable? patrons, string baseHost = "localhost" ) { @@ -310,7 +319,13 @@ void RunMultiThreaded( var gamesPerThreadRemainder = runs % noOfThreads; var threads = new Task>[noOfThreads]; - List PlayGames(int amount, BotInfo bot1Info, BotInfo bot2Info, int threadNo, ulong seed) + List PlayGames( + int amount, + BotInfo bot1Info, + BotInfo bot2Info, + int threadNo, + ulong seed, + IEnumerable? patronsLocal) { var results = new ScriptsOfTribute.Board.EndGameState[amount]; var timeMeasurements = new long[amount]; @@ -321,7 +336,7 @@ void RunMultiThreaded( for (var i = 0; i < amount; i++) { - var game = PrepareGame(bot1, bot2, enableLogs, seed, logFileNameProvider, timeout); + var game = PrepareGame(bot1, bot2, enableLogs, seed, logFileNameProvider, timeout, patronsLocal); seed += 1; watch.Reset(); @@ -374,7 +389,7 @@ void RunMultiThreaded( ServerPort = baseServerPort + noOfThreads + i, } : bot2Info; - threads[i] = Task.Factory.StartNew(() => PlayGames(gamesToPlay, bot1ThreadInfo, bot2ThreadInfo, threadNo, currentSeedCopy)); + threads[i] = Task.Factory.StartNew(() => PlayGames(gamesToPlay, bot1ThreadInfo, bot2ThreadInfo, threadNo, currentSeedCopy, patrons)); currentSeed += (ulong)gamesToPlay; }