Skip to content

Commit

Permalink
simplify AIPlayers: get rid of Intellect, AIPlayer class
Browse files Browse the repository at this point in the history
  • Loading branch information
Konrad Jamrozik committed Jul 8, 2024
1 parent a7bcaac commit 11c872d
Show file tree
Hide file tree
Showing 16 changed files with 135 additions and 79 deletions.
16 changes: 3 additions & 13 deletions src/api/AdvanceTurnsRoute.cs
Original file line number Diff line number Diff line change
Expand Up @@ -61,19 +61,9 @@ private static AdvanceTurnsSuccessResponse AdvanceTurnsInternal(
GameSession gameSession = ApiUtils.NewGameSessionFromTurn(initialTurn);
var controller = new GameSessionController(config, log, gameSession);

// kja strongly type aiPlayer, similarly to PlayerActionName
AIPlayer.Intellect intellect = aiPlayer switch
{
"Basic" => AIPlayer.Intellect.Basic,
"DoNothing" => AIPlayer.Intellect.DoNothing,
null => AIPlayer.Intellect.DoNothing,
_ => throw new ArgumentException(
$"Invalid AI player: {aiPlayer}. Must be 'basic' or 'doNothing'.")
};

var aiPlayerInstance = new AIPlayer(
log,
intellect);
var aiPlayerName = new AIPlayerName(aiPlayer ?? AIPlayerName.DoNothing.ToString());

var aiPlayerInstance = IAIPlayer.New(log, aiPlayerName);

controller.PlayGameSession(turnLimit: parsedTurnLimit, aiPlayerInstance);

Expand Down
14 changes: 7 additions & 7 deletions src/game-lib-tests/AIPlayerTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -32,21 +32,21 @@ public void Scratchpad()

[Test]
public void DoNothingAIPlayerIntellectPlaysGameUntilConclusion()
=> AIPlayerPlaysGameUntilConclusion(AIPlayer.Intellect.DoNothing, turnLimit: 10);
=> AIPlayerPlaysGameUntilConclusion(AIPlayerName.DoNothing, turnLimit: 10);

[Test]
public void BasicAIPlayerIntellectPlaysGameUntilConclusion()
=> AIPlayerPlaysGameUntilConclusion(AIPlayer.Intellect.Basic, turnLimit: 100);
=> AIPlayerPlaysGameUntilConclusion(AIPlayerName.Basic, turnLimit: 100);

[Test]
public void ExampleGameSessionForApi()
{
var config = new Configuration(new SimulatedFileSystem());
var log = new Log(config);
var randomGen = new RandomGen();
var intellect = AIPlayer.Intellect.Basic;
var aiPlayerName = AIPlayerName.Basic;
var controller = new GameSessionController(config, log, new GameSession(randomGen));
var aiPlayer = new AIPlayer(log, intellect);
var aiPlayer = IAIPlayer.New(log, aiPlayerName);

// Act
controller.PlayGameSession(turnLimit: 30, aiPlayer);
Expand All @@ -61,7 +61,7 @@ public void RunSimulations()
// Also, as each game session progresses, no GameStates in it should be kept except the current one.
for (int i = 0; i < 100; i++)
{
AIPlayerPlaysGameUntilConclusion(AIPlayer.Intellect.Basic, turnLimit: 300);
AIPlayerPlaysGameUntilConclusion(AIPlayerName.Basic, turnLimit: 300);
}
}

Expand All @@ -71,10 +71,10 @@ public void TearDown()
_log.Dispose();
}

private void AIPlayerPlaysGameUntilConclusion(AIPlayer.Intellect intellect, int turnLimit)
private void AIPlayerPlaysGameUntilConclusion(AIPlayerName name, int turnLimit)
{
var controller = new GameSessionController(_config, _log, new GameSession(_randomGen));
var aiPlayer = new AIPlayer(_log, intellect);
var aiPlayer = IAIPlayer.New(_log, name);

// Act
controller.PlayGameSession(turnLimit, aiPlayer);
Expand Down
2 changes: 1 addition & 1 deletion src/game-lib-tests/GameSessionControllerTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ public void CurrentTurnControllerKeepsTrackOfTheTurn()
Assert.That(controller.CurrentTurnController.CurrentTurn, Is.EqualTo(initialTurn));

// Act
controller.PlayGameSession(2, new AIPlayer(_log, AIPlayer.Intellect.DoNothing));
controller.PlayGameSession(2, IAIPlayer.New(_log, AIPlayerName.DoNothing));

int nextTurn = session.CurrentGameState.Timeline.CurrentTurn;
Contract.Assert(initialTurn + 1 == nextTurn);
Expand Down
4 changes: 2 additions & 2 deletions src/game-lib/Controller/GameSessionController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ public GameSessionController(Configuration config, ILog log, GameSession gameSes
public GameStatePlayerView CurrentGameStatePlayerView
=> new GameStatePlayerView(() => GameSession.CurrentGameState);

public void PlayGameSession(int turnLimit, IPlayer player)
public void PlayGameSession(int turnLimit, IAIPlayer player)
{
// Assert:
// IF the GameSession was ctored with null initialGameState,
Expand Down Expand Up @@ -114,7 +114,7 @@ public void PlayGameSession(int turnLimit, IPlayer player)
_log.Flush();
}

private void PlayGameUntilOver(IPlayer player, int turnLimit)
private void PlayGameUntilOver(IAIPlayer player, int turnLimit)
{
// Note: in the boundary case of
//
Expand Down
20 changes: 20 additions & 0 deletions src/game-lib/Controller/IAIPlayer.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
using UfoGameLib.Lib;
using UfoGameLib.Players;
using UfoGameLib.State;

namespace UfoGameLib.Controller;

public interface IAIPlayer
{
public void PlayGameTurn(GameStatePlayerView state, GameTurnController controller);

public static IAIPlayer New(ILog log, AIPlayerName name)
{
var playerMap = new Dictionary<AIPlayerName, IAIPlayer>
{
[AIPlayerName.Basic] = new BasicAIPlayer(log),
[AIPlayerName.DoNothing] = new DoNothingAIPlayer(),
};
return playerMap[name];
}
}
8 changes: 0 additions & 8 deletions src/game-lib/Controller/IPlayer.cs

This file was deleted.

15 changes: 4 additions & 11 deletions src/game-lib/Controller/PlayerActionName.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,28 +3,21 @@
using System.Text.Json.Serialization;
using Lib.Contracts;
using Lib.Json;
using UfoGameLib.Lib;

namespace UfoGameLib.Controller;

[JsonConverter(typeof(StringJsonConverter<PlayerActionName>))]
public class PlayerActionName
{
private readonly string _name;

private static readonly ImmutableList<string> ValidNames = GetValidNames();

public static bool IsValid(string name) => ValidNames.Contains(name);

private static ImmutableList<string> GetValidNames()
{
// Get the assembly that contains the PlayerAction class
Assembly assembly = Assembly.GetAssembly(typeof(PlayerAction))!;

// Get all types in the assembly that inherit from PlayerAction
var derivedTypes = assembly.GetTypes().Where(t => t.IsSubclassOf(typeof(PlayerAction)));
private readonly string _name;

return derivedTypes.Select(t => t.Name).ToImmutableList();
}
private static ImmutableList<string> GetValidNames()
=> Reflection.GetDerivedTypeNames<PlayerAction>();

public PlayerActionName(string name)
{
Expand Down
2 changes: 1 addition & 1 deletion src/game-lib/Lib/RandomGen.cs
Original file line number Diff line number Diff line change
Expand Up @@ -112,4 +112,4 @@ public virtual int RandomizeMissionSiteCountdown()
double result = baseValue * modifier;
return (result, variationRoll);
}
}
}
25 changes: 25 additions & 0 deletions src/game-lib/Lib/Reflection.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
using System.Collections.Immutable;
using System.Reflection;

namespace UfoGameLib.Lib;

public static class Reflection
{
public static ImmutableList<string> GetDerivedTypeNames<T>()
{
Assembly assembly = Assembly.GetAssembly(typeof(T))!;

var derivedTypes = assembly.GetTypes().Where(t => t.IsSubclassOf(typeof(T)));

return derivedTypes.Select(t => t.Name).ToImmutableList();
}

public static ImmutableList<string> GetInterfaceImplementationNames<T>()
{
Assembly assembly = Assembly.GetAssembly(typeof(T))!;

var derivedTypes = assembly.GetTypes().Where(t => typeof(T).IsAssignableFrom(t));

return derivedTypes.Select(t => t.Name).ToImmutableList();
}
}
29 changes: 0 additions & 29 deletions src/game-lib/Players/AIPlayer.cs

This file was deleted.

65 changes: 65 additions & 0 deletions src/game-lib/Players/AIPlayerName.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
using System.Collections.Immutable;
using System.Text.Json.Serialization;
using Lib.Contracts;
using Lib.Json;
using UfoGameLib.Controller;
using UfoGameLib.Lib;

namespace UfoGameLib.Players;

[JsonConverter(typeof(StringJsonConverter<AIPlayerName>))]
public class AIPlayerName : IEquatable<AIPlayerName>
{
private static readonly ImmutableList<string> ValidNames = GetValidNames();

public static readonly AIPlayerName Basic = new AIPlayerName(nameof(BasicAIPlayer));
public static readonly AIPlayerName DoNothing = new AIPlayerName(nameof(DoNothingAIPlayer));

private static bool IsValid(string name) => ValidNames.Contains(name);

private readonly string _name;

private static ImmutableList<string> GetValidNames()
=> Reflection.GetInterfaceImplementationNames<IAIPlayer>();

public AIPlayerName(string name)
{
Contract.Assert(
IsValid(name),
$"The type name '{name}' is not a valid name of AIPlayer-derived class.");
_name = name;
}


public override string ToString()
{
return $"{_name}";
}

public bool Equals(AIPlayerName? other)
{
if (ReferenceEquals(null, other))
return false;
if (ReferenceEquals(this, other))
return true;
return _name == other._name;
}

public override bool Equals(object? obj)
{
if (ReferenceEquals(null, obj))
return false;
if (ReferenceEquals(this, obj))
return true;
return obj.GetType() == GetType() && Equals((AIPlayerName)obj);
}

public override int GetHashCode()
=> _name.GetHashCode();

public static bool operator ==(AIPlayerName? left, AIPlayerName? right)
=> Equals(left, right);

public static bool operator !=(AIPlayerName? left, AIPlayerName? right)
=> !Equals(left, right);
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,13 @@

namespace UfoGameLib.Players;

public class BasicAIPlayerIntellect : IPlayer
public class BasicAIPlayer : IAIPlayer
{
private const int MinimumAcceptableAgentSurvivalChance = 20; // percent
private const int MoneyBufferToBuyTransportCapacity = 600;
private readonly ILog _log;

public BasicAIPlayerIntellect(ILog log)
public BasicAIPlayer(ILog log)
{
_log = log;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

namespace UfoGameLib.Players;

public class DoNothingAIPlayerIntellect : IPlayer
public class DoNothingAIPlayer : IAIPlayer
{
public void PlayGameTurn(GameStatePlayerView state, GameTurnController controller)
{
Expand Down
2 changes: 1 addition & 1 deletion web/src/components/controlPanels/AIPlayerControlPanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ export function AIPlayerControlPanel(
): React.JSX.Element {
const [startTurn, setStartTurn] = useState<number>(defaultStartTurn)
const [targetTurn, setTargetTurn] = useState<number>(defaultTargetTurn)
const [aiPlayer, setAiPlayer] = useState<AIPlayerName>('Basic')
const [aiPlayer, setAiPlayer] = useState<AIPlayerName>('BasicAIPlayer')

const theme = useTheme()
const smallDisplay = useMediaQuery(theme.breakpoints.down('sm'))
Expand Down
4 changes: 2 additions & 2 deletions web/src/components/controlPanels/aiPlayerOptions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,6 @@ import type { AIPlayerName } from '../../lib/codesync/aiPlayer'
export const aiPlayerOptionLabel: {
[key in AIPlayerName]: string
} = {
Basic: 'Basic',
DoNothing: 'Do nothing',
BasicAIPlayer: 'Basic',
DoNothingAIPlayer: 'Do nothing',
}
2 changes: 1 addition & 1 deletion web/src/lib/codesync/aiPlayer.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
// codesync: UfoGameLib.Players

export type AIPlayerName = 'DoNothing' | 'Basic'
export type AIPlayerName = 'BasicAIPlayer' | 'DoNothingAIPlayer'

0 comments on commit 11c872d

Please sign in to comment.