Skip to content

Commit 011f849

Browse files
committed
Feature: Refactor AI code for optimization.
1 parent 43e5583 commit 011f849

File tree

10 files changed

+89
-83
lines changed

10 files changed

+89
-83
lines changed

DTC.Core

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

Lines changed: 11 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -23,17 +23,9 @@ protected AiBrainBase(int inputSize, int[] hiddenLayers, int outputSize)
2323
m_qNet = new NeuralNetwork(inputSize, hiddenLayers, outputSize, learningRate: 0.05);
2424
}
2525

26-
protected int ChooseHighestOutput(IAiGameState state)
27-
{
28-
var outputs = GetOutputs(state);
29-
return ArgMax(outputs);
30-
}
26+
protected int ChooseHighestOutput(IAiGameState state) => ArgMax(GetOutputs(state));
3127

32-
protected double[] GetOutputs(IAiGameState state)
33-
{
34-
lock (m_qNet)
35-
return m_qNet.Predict(state.ToInputVector());
36-
}
28+
protected double[] GetOutputs(IAiGameState state) => m_qNet.Predict(state.ToInputVector());
3729

3830
/// <summary>
3931
/// Finds the index of the maximum value in the array.
@@ -53,51 +45,34 @@ private static int ArgMax(double[] values)
5345
return bestIndex;
5446
}
5547

56-
public byte[] Save()
57-
{
58-
lock (m_qNet)
59-
return JsonConvert.SerializeObject(this).Compress();
60-
}
48+
public byte[] Save() => JsonConvert.SerializeObject(this).Compress();
6149

62-
public void Load(byte[] brainBytes)
63-
{
64-
lock (m_qNet)
65-
JsonConvert.PopulateObject(brainBytes.DecompressToString(), this);
66-
}
50+
public void Load(byte[] brainBytes) => JsonConvert.PopulateObject(brainBytes.DecompressToString(), this);
6751

6852
public AiBrainBase InitWithLerp(AiBrainBase first, AiBrainBase second, double mix)
6953
{
70-
lock (m_qNet)
71-
lock (first.m_qNet)
72-
lock (second.m_qNet)
73-
m_qNet = first.m_qNet.CreateLerped(second.m_qNet, mix);
54+
m_qNet = first.m_qNet.CreateLerped(second.m_qNet, mix);
7455
return this;
7556
}
7657

7758
public AiBrainBase InitWithSpliced(AiBrainBase first, AiBrainBase second)
7859
{
79-
lock (m_qNet)
80-
lock (first.m_qNet)
81-
lock (second.m_qNet)
82-
m_qNet = first.m_qNet.CreateSpliced(second.m_qNet);
60+
m_qNet = first.m_qNet.CreateSpliced(second.m_qNet);
8361
return this;
8462
}
85-
63+
8664
public AiBrainBase InitWithNudgedWeights(AiBrainBase brain, NeuralNetwork.NudgeFactor nudge)
8765
{
88-
lock (m_qNet)
89-
lock (brain.m_qNet)
90-
m_qNet = brain.m_qNet.CloneWithNudgeWeights(nudge);
66+
m_qNet = brain.m_qNet.CloneWithNudgeWeights(nudge);
9167
return this;
9268
}
93-
69+
9470
public AiBrainBase NudgeWeights(NeuralNetwork.NudgeFactor nudge)
9571
{
96-
lock (m_qNet)
97-
m_qNet = m_qNet.CloneWithNudgeWeights(nudge);
72+
m_qNet = m_qNet.CloneWithNudgeWeights(nudge);
9873
return this;
9974
}
100-
75+
10176
public AiBrainBase InitWithBrain(AiBrainBase brain)
10277
{
10378
lock (m_qNet)

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

Lines changed: 35 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
using System;
1212
using System.Collections.Generic;
1313
using System.Linq;
14+
using System.Threading.Tasks;
1415
using CSharp.Core.AI;
1516
using CSharp.Core.Extensions;
1617
using G33kShell.Desktop.Console.Controls;
@@ -30,6 +31,8 @@ public abstract class AiGameCanvasBase : ScreensaverBase
3031
private const int MinPopSize = 80;
3132
private int m_generationsSinceImprovement;
3233
private int m_currentPopSize = InitialPopSize;
34+
private Task m_trainingTask;
35+
private bool m_stopTraining;
3336

3437
protected AiGameBase[] m_games;
3538

@@ -47,16 +50,42 @@ protected AiGameCanvasBase(int width, int height, int targetFps = 30) : base(wid
4750
[UsedImplicitly]
4851
protected void TrainAi(ScreenData screen, Action<byte[]> saveBrainBytes, Func<AiBrainBase> createBrain)
4952
{
50-
m_games ??= Enumerable.Range(0, m_currentPopSize).Select(_ => CreateGameWithSeed(m_generation)).ToArray();
51-
52-
m_games.AsParallel().ForAll(o =>
53+
const string animChars = "/-\\|";
54+
var animFrame = Environment.TickCount64 / 100 % animChars.Length;
55+
screen.PrintAt(0, 0, $"Training... {animChars[(int)animFrame]}");
56+
57+
if (m_trainingTask != null)
58+
{
59+
// We're already training - Do nothing.
60+
return;
61+
}
62+
63+
m_stopTraining = false;
64+
m_trainingTask = Task.Run(() =>
5365
{
54-
while (!o.IsGameOver)
55-
o.Tick();
66+
while (!m_stopTraining)
67+
TrainAiImpl(saveBrainBytes, createBrain);
5668
});
69+
}
70+
71+
public override void StopScreensaver()
72+
{
73+
base.StopScreensaver();
74+
75+
m_stopTraining = true;
76+
}
5777

58-
DrawGame(screen, m_games[0]);
78+
private void TrainAiImpl(Action<byte[]> saveBrainBytes, Func<AiBrainBase> createBrain)
79+
{
80+
m_games ??= Enumerable.Range(0, m_currentPopSize).Select(_ => CreateGameWithSeed(m_generation)).ToArray();
5981

82+
Parallel.For(0, m_games.Length, i =>
83+
{
84+
var game = m_games[i];
85+
while (!game.IsGameOver)
86+
game.Tick();
87+
});
88+
6089
// Select the breeders.
6190
var orderedGames = m_games.OrderByDescending(o => o.Rating).ToArray();
6291
var eliteGames = orderedGames.Take((int)(m_currentPopSize * 0.1)).ToArray();

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

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -124,10 +124,10 @@ public void Explode(List<Asteroid> asteroids)
124124

125125
public float DistanceTo(Vector2 shipPosition)
126126
{
127-
var minDist = Vector2.Distance(shipPosition, Position);
127+
var minDist = Vector2.DistanceSquared(shipPosition, Position);
128128
foreach (var offset in m_offsets)
129-
minDist = MathF.Min(minDist, Vector2.Distance(shipPosition, Position + offset));
129+
minDist = MathF.Min(minDist, Vector2.DistanceSquared(shipPosition, Position + offset));
130130

131-
return minDist;
131+
return MathF.Sqrt(minDist);
132132
}
133133
}

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

Lines changed: 15 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@
1111
using System;
1212
using System.Collections.Generic;
1313
using System.Diagnostics;
14-
using System.Linq;
1514
using System.Numerics;
1615
using CSharp.Core.Extensions;
1716
using G33kShell.Desktop.Console.Screensavers.AI;
@@ -26,6 +25,7 @@ public class Game : AiGameBase
2625
private int m_gameTicks;
2726
private int m_leftTurns;
2827
private int m_rightTurns;
28+
private GameState m_gameState;
2929

3030
/// <summary>
3131
/// Score used for public display.
@@ -53,6 +53,7 @@ public override Game ResetGame()
5353
{
5454
Ship = new Ship(ArenaWidth, ArenaHeight);
5555
Score = 0;
56+
m_gameState = new GameState(Ship, Asteroids, ArenaWidth, ArenaHeight);
5657

5758
Asteroids.Clear();
5859
Bullets.Clear();
@@ -69,7 +70,7 @@ public override Game ResetGame()
6970

7071
private void EnsureMinimumAsteroidCount()
7172
{
72-
while (Asteroids.Sum(o => o.SizeMetric) < 12)
73+
while (Asteroids.FastSum(o => o.SizeMetric) < 12)
7374
{
7475
var pos = new Vector2(GameRand.NextFloat() * ArenaWidth, GameRand.NextFloat() * ArenaHeight);
7576
if (Vector2.Distance(pos, Ship.Position) < 25)
@@ -99,7 +100,7 @@ public override void Tick()
99100
// Spawn new bullets.
100101
if (Ship.IsShooting && Bullets.Count < Ship.MaxBullets)
101102
{
102-
var target = Asteroids.OrderBy(o => Vector2.DistanceSquared(Ship.Position, o.Position)).FirstOrDefault();
103+
var target = Asteroids.FastFindMin(o => Vector2.DistanceSquared(Ship.Position, o.Position));
103104
Bullets.Add(new Bullet(Ship.Position, Ship.Theta, target, ArenaWidth, ArenaHeight));
104105
m_bulletsFired++;
105106
}
@@ -117,8 +118,9 @@ public override void Tick()
117118

118119
// Check for bullet/asteroid collisions.
119120
var bulletsToRemove = new List<Bullet>();
120-
foreach (var bullet in Bullets)
121+
for (var index = 0; index < Bullets.Count; index++)
121122
{
123+
var bullet = Bullets[index];
122124
Asteroid hitAsteroid = null;
123125
// ReSharper disable once ForCanBeConvertedToForeach
124126
// ReSharper disable once LoopCanBeConvertedToQuery
@@ -129,36 +131,37 @@ public override void Tick()
129131
hitAsteroid = Asteroids[i];
130132
break;
131133
}
134+
132135
if (hitAsteroid == null)
133136
continue; // Bullet not hitting anything.
134137

135138
// Bullet hit an asteroid.
136139
bulletsToRemove.Add(bullet);
137140
hitAsteroid.Explode(Asteroids);
138141
Score += hitAsteroid.HitScore;
139-
142+
140143
// Bonus points if it was the asteroid we were aiming for.
141144
if (bullet.Target == hitAsteroid)
142145
m_perfectHits++;
143146
}
144147

145-
bulletsToRemove.ForEach(o => Bullets.Remove(o));
148+
for (var i = 0; i < bulletsToRemove.Count; i++)
149+
Bullets.Remove(bulletsToRemove[i]);
146150

147151
// Check for ship/asteroid collisions.
148-
foreach (var asteroid in Asteroids)
152+
const float shipRadius = 4.5f;
153+
for (var i = 0; i < Asteroids.Count; i++)
149154
{
150-
var distance = asteroid.DistanceTo(Ship.Position);
151-
const float shipRadius = 4.0f;
152-
if (distance < asteroid.Radius + shipRadius)
155+
var distance = Asteroids[i].DistanceTo(Ship.Position);
156+
if (distance < Asteroids[i].Radius + shipRadius)
153157
{
154158
Ship.Shield = Math.Max(0.0, Ship.Shield - 0.05);
155159
break;
156160
}
157161
}
158162

159163
// Apply the AI.
160-
var gameState = new GameState(Ship, Asteroids, ArenaWidth, ArenaHeight);
161-
var moves = ((Brain)Brain).ChooseMoves(gameState);
164+
var moves = ((Brain)Brain).ChooseMoves(m_gameState);
162165
Ship.IsShooting = moves.IsShooting;
163166
Ship.Turning = moves.Turn;
164167
Ship.IsThrusting = moves.IsThrusting;

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

Lines changed: 20 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@
99
//
1010
// THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND.
1111
using System.Collections.Generic;
12-
using System.Linq;
1312
using System.Numerics;
1413
using CSharp.Core.Extensions;
1514
using G33kShell.Desktop.Console.Screensavers.AI;
@@ -21,6 +20,7 @@ namespace G33kShell.Desktop.Console.Screensavers.Asteroids;
2120
/// </summary>
2221
public class GameState : IAiGameState
2322
{
23+
private readonly double[] m_inputVector = new double[3];
2424
private readonly Ship m_ship;
2525
private readonly List<Asteroid> m_asteroids;
2626
private readonly float m_arenaDiagonal;
@@ -35,25 +35,34 @@ public GameState(Ship ship, List<Asteroid> asteroids, int arenaWidth, int arenaH
3535

3636
public double[] ToInputVector()
3737
{
38-
var inputVector = new double[3];
39-
4038
// Bias.
41-
inputVector[0] = 1.0;
39+
m_inputVector[0] = 1.0;
4240

4341
// Find nearest asteroid.
44-
var asteroid =
45-
m_asteroids
46-
.OrderBy(o => Vector2.DistanceSquared(o.Position, m_ship.Position))
47-
.FirstOrDefault();
42+
Asteroid asteroid = null;
43+
var bestDistance = float.MaxValue;
44+
for (var i = 0; i < m_asteroids.Count; i++)
45+
{
46+
var d = Vector2.DistanceSquared(m_asteroids[i].Position, m_ship.Position);
47+
if (d > bestDistance)
48+
continue;
49+
asteroid = m_asteroids[i];
50+
bestDistance = d;
51+
}
4852

4953
if (asteroid != null)
5054
{
5155
var relativePos = asteroid.Position - m_ship.Position;
5256
var angleToAsteroid = Vector2.Dot(Vector2.Normalize(relativePos), m_ship.Theta.ToDirection()).Clamp(-1.0f, 1.0f);
53-
inputVector[1] = angleToAsteroid;
54-
inputVector[2] = 1.0 - relativePos.Length() / m_arenaDiagonal;
57+
m_inputVector[1] = angleToAsteroid;
58+
m_inputVector[2] = 1.0 - relativePos.Length() / m_arenaDiagonal;
59+
}
60+
else
61+
{
62+
m_inputVector[1] = 0.0;
63+
m_inputVector[2] = 0.0;
5564
}
5665

57-
return inputVector;
66+
return m_inputVector;
5867
}
5968
}

G33kShell.Desktop/Console/Screensavers/FluidCanvas.cs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@
1010
// THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND.
1111
using System;
1212
using System.Diagnostics;
13-
using System.Linq;
1413
using CSharp.Core;
1514
using CSharp.Core.Extensions;
1615
using G33kShell.Desktop.Console.Controls;
@@ -98,7 +97,7 @@ public override void UpdateFrame(ScreenData screen)
9897
}
9998

10099
// Turn the flame on/off to prevent too much 'smoke'.
101-
var averageDensity = m_fluid.Density.Average();
100+
var averageDensity = m_fluid.Density.FastAvg();
102101
if (m_isLit)
103102
{
104103
if (averageDensity > 0.45)

G33kShell.Desktop/Terminal/Commands/CopyCommand.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -128,7 +128,7 @@ private void RunSync(string source, string dest)
128128
// Copy the content that does exist.
129129
var filesCopied = sourceDir
130130
.GetFileSystemInfos()
131-
.Sum(o => o.CopyTo(destDir, true));
131+
.FastSum(o => o.CopyTo(destDir, true));
132132

133133
WriteLine($"Operation complete. {excessItems.Length} item(s) removed, {filesCopied} copied.");
134134
}

G33kShell.Desktop/ViewModels/ShellViewModel.cs

Lines changed: 0 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -66,17 +66,6 @@ public ShellViewModel(SkinBase skin)
6666
#pragma warning disable CS1998 // Async method lacks 'await' operators and will run synchronously
6767
private async Task StartAsync()
6868
{
69-
#if !DEBUG
70-
// Start the 'sign in' face-finding background task.
71-
var signInTask = CaptureFaceAsync();
72-
73-
// Log-in awesomeness.
74-
await BiosCheckAsync();
75-
await LoadOsAsync();
76-
77-
var signInResult = await signInTask;
78-
await LogInAsync(signInResult);
79-
#endif
8069

8170
// Run the terminal.
8271
_ = Task.Run(RunTerminal);

G33kShell.sln.DotSettings.user

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,10 @@
3030
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AStringBuilder_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FSourcesCache_003F347bff1849d7b97359342c40541ad1791eb2f169dd778cad825364a5e9a5807e_003FStringBuilder_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
3131
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AString_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003FAppData_003FRoaming_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FSourcesCache_003F13e79b2a7b78394ccf591d3454bff5bfb14e692da011a7e38b66c2312b365a_003FString_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
3232
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AString_002EManipulation_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FSourcesCache_003F89c4224f79e6b275db5cce1623a97eec6e1bbd4755a617dd36d073d2347236a5_003FString_002EManipulation_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
33+
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ASum_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FSourcesCache_003Ff715697da3d6ded86e3e26ad8295d4b5f3ddb1d9c546993b76eb188a2bbf222_003FSum_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
3334
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ATask_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FSourcesCache_003F35f3a54f5acb408a3e219b2de039f1a39557b7e4515f11238cba07b60c0ce_003FTask_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
3435
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AThreadProxyRenderTimer_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FSourcesCache_003Ff3d9d479a026ba8b11c571697cbcd6e633cf34cd844b643e17a62372f3e65ff_003FThreadProxyRenderTimer_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
36+
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AThrowHelper_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FSourcesCache_003F2c8e7ca976f350cba9836d5565dac56b11e0b56656fa786460eb1395857a6fa_003FThrowHelper_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
3537
<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>
3638
<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>
3739
<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>

0 commit comments

Comments
 (0)