Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
91 changes: 81 additions & 10 deletions Engine/src/AI/ScriptsOfTribute.cs
Original file line number Diff line number Diff line change
@@ -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<PatronId>()
.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;

Expand All @@ -25,17 +31,22 @@ 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;
player1.Id = PlayerEnum.PLAYER1;
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<string>? patronTokens)
: this(player1, player2, timeout, ResolvePatrons(patronTokens))
{ }

private Task<PatronId> SelectPatronTask(AI currentPlayer, List<PatronId> availablePatrons, int round)
Expand All @@ -61,11 +72,9 @@ private Task<PatronId> SelectPatronTask(AI currentPlayer, List<PatronId> availab
return (new EndGameState(playerToWin, GameEndReason.PATRON_SELECTION_TIMEOUT), null);
}

private (EndGameState? ,PatronId[]?) PatronSelection()
private (EndGameState?, PatronId[]?) PatronSelection()
{
List<PatronId> patrons = Enum.GetValues(typeof(PatronId)).Cast<PatronId>()
// TODO: Hardcoded banned patrons, improve this.
.Where(patronId => patronId != PatronId.TREASURY && patronId != PatronId.PSIJIC).ToList();
List<PatronId> patrons = _patrons.ToList();

List<PatronId> patronsSelected = new List<PatronId>();
var (endGameState, patron) = SelectPatronWithTimeout(PlayerEnum.PLAYER2, _players[0], patrons, 1);
Expand Down Expand Up @@ -121,7 +130,7 @@ private Task<PatronId> SelectPatronTask(AI currentPlayer, List<PatronId> availab
PatronId[]? patrons;

endGameState = PrepareBots(_players[0], _players[1], 5);

if (endGameState is not null)
{
_players[0].GameEnd(endGameState, null);
Expand Down Expand Up @@ -180,4 +189,66 @@ private Task<PatronId> SelectPatronTask(AI currentPlayer, List<PatronId> availab

return null;
}

public static PatronId[] ResolvePatrons(IEnumerable<string>? 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<PatronId>(tokens.Length);
var invalid = new List<string>();

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;
}
}
31 changes: 23 additions & 8 deletions GameRunner/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
var timeoutOption = CreateOption<int>("--timeout", "Game timeout in seconds.", 30, "-to");
var clientPortOption = CreateOption<int>("--client-port", "Base client port for gRPC bots.", 50000, "-cp");
var serverPortOption = CreateOption<int>("--server-port", "Base server port for gRPC bots.", 49000, "-sp");
var patronsOption = CreateOption<string[]>("--patrons", "Allowed patrons for this run. Use 'default' (alias: 'all') for standard set (all selectable, no TREASURY).", Array.Empty<string>(), "-p");

var bot1NameArgument = CreateBotArgument("bot1", "Name of the first bot or command.");
var bot2NameArgument = CreateBotArgument("bot2", "Name of the second bot or command.");
Expand All @@ -49,6 +50,7 @@
timeoutOption,
clientPortOption,
serverPortOption,
patronsOption,
bot1NameArgument,
bot2NameArgument,
};
Expand Down Expand Up @@ -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<string>? 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,
};
Expand Down Expand Up @@ -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<string>? patrons = context.ParseResult.GetValueForOption(patronsOption);
BotInfo? bot1Info = context.ParseResult.GetValueForArgument(bot1NameArgument);
BotInfo? bot2Info = context.ParseResult.GetValueForArgument(bot2NameArgument);

Expand All @@ -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(
Expand All @@ -234,6 +241,7 @@ void RunSingleThreaded(
int timeout,
int baseClientPort,
int baseServerPort,
IEnumerable<string>? patrons,
string baseHost = "localhost"
)
{
Expand Down Expand Up @@ -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();
Expand Down Expand Up @@ -301,6 +309,7 @@ void RunMultiThreaded(
int timeout,
int baseClientPort,
int baseServerPort,
IEnumerable<string>? patrons,
string baseHost = "localhost"
)
{
Expand All @@ -310,7 +319,13 @@ void RunMultiThreaded(
var gamesPerThreadRemainder = runs % noOfThreads;
var threads = new Task<List<ScriptsOfTribute.Board.EndGameState>>[noOfThreads];

List<ScriptsOfTribute.Board.EndGameState> PlayGames(int amount, BotInfo bot1Info, BotInfo bot2Info, int threadNo, ulong seed)
List<ScriptsOfTribute.Board.EndGameState> PlayGames(
int amount,
BotInfo bot1Info,
BotInfo bot2Info,
int threadNo,
ulong seed,
IEnumerable<string>? patronsLocal)
{
var results = new ScriptsOfTribute.Board.EndGameState[amount];
var timeMeasurements = new long[amount];
Expand All @@ -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();
Expand Down Expand Up @@ -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;
}

Expand Down
Loading