Skip to content

Commit af0c294

Browse files
committed
Feature: AI brains train on multiple games instead of just one.
...reducing the chance of them getting a good score on an 'easy' game.
1 parent 6f3a6d4 commit af0c294

File tree

5 files changed

+52
-38
lines changed

5 files changed

+52
-38
lines changed

G33kShell.Desktop/Console/Screensavers/AI/AiBrainBase.cs

Lines changed: 17 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@
88
// about your modifications. Your contributions are valued!
99
//
1010
// THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND.
11+
using System;
12+
using System.Linq;
1113
using CSharp.Core.AI;
1214
using CSharp.Core.Extensions;
1315
using Newtonsoft.Json;
@@ -17,23 +19,25 @@ namespace G33kShell.Desktop.Console.Screensavers.AI;
1719
public abstract class AiBrainBase
1820
{
1921
[JsonProperty] private NeuralNetwork m_qNet;
20-
private readonly double[] m_inputVector;
22+
private double[] m_inputVector;
2123

2224
public int InputSize { get; }
23-
public int[] HiddenLayers { get; private set; }
24-
public int OutputSize { get; private set; }
25+
public int[] HiddenLayers { get; }
26+
public int OutputSize { get; }
2527

26-
protected AiBrainBase(int inputSize, int[] hiddenLayers, int outputSize)
28+
protected AiBrainBase(int inputSize, int[] hiddenLayers, int outputSize, NeuralNetwork qNet = null)
2729
{
2830
InputSize = inputSize;
29-
HiddenLayers = hiddenLayers;
31+
HiddenLayers = (int[])hiddenLayers.Clone();
3032
OutputSize = outputSize;
31-
m_qNet = new NeuralNetwork(inputSize, hiddenLayers, outputSize, learningRate: 0.05);
33+
m_qNet = qNet?.Clone() ?? new NeuralNetwork(inputSize, hiddenLayers, outputSize, learningRate: 0.05);
3234
m_inputVector = new double[inputSize];
3335
}
3436

35-
protected AiBrainBase(AiBrainBase toCopy) =>
36-
m_qNet = toCopy.m_qNet.Clone();
37+
protected AiBrainBase(AiBrainBase toCopy)
38+
: this(toCopy.InputSize, toCopy.HiddenLayers, toCopy.OutputSize, toCopy.m_qNet)
39+
{
40+
}
3741

3842
protected int ChooseHighestOutput(IAiGameState state) => ArgMax(GetOutputs(state));
3943

@@ -72,8 +76,11 @@ private static int ArgMax(double[] values)
7276

7377
public AiBrainBase Load(byte[] brainBytes)
7478
{
75-
if (brainBytes != null)
76-
JsonConvert.PopulateObject(brainBytes.DecompressToString(), this);
79+
if (brainBytes == null || brainBytes.Length == 0)
80+
throw new ArgumentException("Brain data cannot be null or empty.", nameof(brainBytes));
81+
82+
JsonConvert.PopulateObject(brainBytes.DecompressToString(), this);
83+
m_inputVector = new double[InputSize];
7784
return this;
7885
}
7986

G33kShell.Desktop/Console/Screensavers/AI/AiGameCanvasBase.cs

Lines changed: 30 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
using System.Collections.Generic;
1313
using System.Linq;
1414
using System.Threading.Tasks;
15+
using CSharp.Core;
1516
using CSharp.Core.Extensions;
1617
using G33kShell.Desktop.Console.Controls;
1718
using JetBrains.Annotations;
@@ -86,44 +87,47 @@ public override void StopScreensaver()
8687
private void TrainAiImpl(Action<byte[]> saveBrainBytes)
8788
{
8889
m_nextGenBrains ??= Enumerable.Range(0, InitialPopSize).Select(_ => CreateBrain()).ToList();
90+
m_generation++;
8991

90-
var games = new (double AverageRating, AiGameBase Game, AiBrainBase Brain)[m_nextGenBrains.Count];
91-
Parallel.For(0, games.Length, i =>
92+
var gameResults = new (double AverageRating, double BestRating, int GameSeed, AiBrainBase Brain, string GameStats)[m_nextGenBrains.Count];
93+
Parallel.For(0, gameResults.Length, i =>
9294
{
93-
// Play the base game.
94-
var baseGame = CreateGameWithSeed(m_generation);
95-
while (!baseGame.IsGameOver && !m_stopTraining)
96-
baseGame.Tick();
97-
98-
var totalRating = baseGame.Rating;
99-
if (baseGame.Rating > 0.0)
95+
try
10096
{
101-
// Play several more games.
102-
var gameCount = 1;
103-
for (var trial = 0; trial < 4 && !m_stopTraining; trial++, gameCount++)
97+
// Play a few games.
98+
var brain = m_nextGenBrains[i];
99+
var seeds = Enumerable.Range(0, 4).Select(_ => Random.Shared.Next(10000)).ToArray();
100+
var games = seeds.Select(seed => CreateGameWithSeed(seed, brain)).ToArray();
101+
for (var gameIndex = 0; gameIndex < games.Length; gameIndex++)
104102
{
105-
var game = CreateGameWithSeed(Random.Shared.Next(), baseGame.Brain);
106-
while (!game.IsGameOver)
107-
game.Tick();
108-
totalRating += game.Rating;
103+
while (!games[gameIndex].IsGameOver && !m_stopTraining)
104+
games[gameIndex].Tick();
109105
}
106+
107+
// Get the average rating.
108+
var averageRating = games.FastAvg(o => o.Rating);
109+
var bestGame = games.FastFindMax(o => o.Rating);
110+
var bestGameSeed = seeds[games.FastFindIndexOf(bestGame)];
111+
112+
// Capture game stats.
113+
var extraStats = bestGame.ExtraGameStats().Select(o => $"{o.Name} {o.Value}").ToCsv('|');
110114

111-
totalRating /= gameCount;
115+
gameResults[i] = (averageRating, bestGame.Rating, bestGameSeed, brain, extraStats);
116+
}
117+
catch (Exception e)
118+
{
119+
Logger.Instance.Exception("Training failed.", e);
112120
}
113-
114-
games[i] = (totalRating, baseGame, baseGame.Brain);
115121
});
116122

117123
// Select the breeders.
118-
var orderedGames = games.OrderByDescending(o => o.AverageRating).ToArray();
124+
var orderedGames = gameResults.OrderByDescending(o => o.AverageRating).ToArray();
119125
var theBest = orderedGames[0];
120126

121127
// Report summary of results.
122-
m_generation++;
123-
var stats = $"Gen {m_generation}|Pop {m_currentPopSize}|Rating {theBest.AverageRating:F1}|GOAT {m_savedRating:F1}";
124-
var extraStats = theBest.Game.ExtraGameStats().Select(o => $" {o.Name}: {o.Value}").ToArray().ToCsv().Trim();
125-
if (!string.IsNullOrEmpty(extraStats))
126-
stats += $"|{extraStats}";
128+
var stats = $"Gen {m_generation}|Pop {m_currentPopSize}|GOAT {m_savedRating:F1}|Rating {theBest.AverageRating:F1}|Seed {theBest.GameSeed}";
129+
if (!string.IsNullOrEmpty(theBest.GameStats))
130+
stats += $"|{theBest.GameStats}";
127131
System.Console.WriteLine(stats);
128132

129133
// Remember the GOAT brains.
@@ -163,7 +167,7 @@ private void TrainAiImpl(Action<byte[]> saveBrainBytes)
163167
}
164168

165169
// Build the brains for the next generation.
166-
var nextBrains = new List<AiBrainBase>(games.Length);
170+
var nextBrains = new List<AiBrainBase>(gameResults.Length);
167171
nextBrains.AddRange(m_goatBrains.Select(o => o.Brain.Clone()));
168172

169173
// Spawn 5% pure randoms.

G33kShell.Desktop/Console/Screensavers/Asteroids/AsteroidsCanvas.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,8 @@ private void PlayGame(ScreenData screen)
4848
if (m_game == null)
4949
{
5050
var brain = new Brain().Load(Settings.Instance.AsteroidsBrain);
51-
m_game = (Game)CreateGame(brain).ResetGame();
51+
m_game = (Game)CreateGame(brain);
52+
m_game.ResetGame();
5253
}
5354

5455
m_game.Tick();

G33kShell.sln.DotSettings.user

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AMath_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FSourcesCache_003F57a1f82f884de2785cf31f799c2f1a6b36437f82afdb52feb11e5e580572c_003FMath_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
2424
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AMatrix4x4_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FSourcesCache_003F2978e6fd12761c5f515215d431bbe018d4542ea5395e9eb9c28ba4b739155_003FMatrix4x4_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
2525
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AMonitor_002ECoreCLR_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FSourcesCache_003F8056cd3f452fefb9834f05cdb275b762dd41f27b7766cd71174e78592dc495b_003FMonitor_002ECoreCLR_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
26+
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AMonitor_002ECoreCLR_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FSourcesCache_003F8056cd3f452fefb9834f05cdb275b762dd41f27b7766cd71174e78592dc495b_003FMonitor_002ECoreCLR_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
2627
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AOperatingSystem_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FSourcesCache_003F785dfedff7393b469f157cbbdf518c4b918f92c3b923a985ce0c42c1ac9cb20_003FOperatingSystem_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
2728
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003APlatformRenderInterface_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FSourcesCache_003Fe899c08cf056329f3d82321cdd92e0115a944cb4e7a1e11ba2b1e2706f25a_003FPlatformRenderInterface_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
2829
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ARenderTargetBitmap_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FSourcesCache_003Faceeee81cdceb7bba79ca2ab87551b4770424feb5c55b65a7563fc828fe0ba9a_003FRenderTargetBitmap_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
@@ -38,6 +39,7 @@
3839
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AThrowHelper_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FSourcesCache_003F6780d13016c376c4491c5618b257d84da7eacf747ed2719783e775546b79b_003FThrowHelper_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
3940
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AThrowHelper_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FSourcesCache_003Fffc196dcaa72b99bef7ac446415884fe13cbff486fc89fe87d78638ac873_003FThrowHelper_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
4041
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AThrowHelper_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FSourcesCache_003Fffc196dcaa72b99bef7ac446415884fe13cbff486fc89fe87d78638ac873_003FThrowHelper_002Ecs_002Fz_003A2_002D1/@EntryIndexedValue">ForceIncluded</s:String>
42+
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AThrowHelper_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FSourcesCache_003F6780d13016c376c4491c5618b257d84da7eacf747ed2719783e775546b79b_003FThrowHelper_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
4143
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AVector3_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FSourcesCache_003F6edafe13d8727aa238b865f5dc91dbc984b5abfbc60bece3744f6311c2c_003FVector3_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
4244
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AVideoCapture_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FSourcesCache_003Fd64bbf85e0b617cdec319ebfa127ac6e3473ed87483bad446523e38e137783a_003FVideoCapture_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
4345
<s:String x:Key="/Default/CodeInspection/Highlighting/SweaWarningsMode/@EntryValue">ShowAndRun</s:String>

0 commit comments

Comments
 (0)