diff --git a/src/LittleTown.Core.Tests/MatchUpdaterTesting.cs b/src/LittleTown.Core.Tests/MatchUpdaterTesting.cs new file mode 100644 index 0000000..6ea6d59 --- /dev/null +++ b/src/LittleTown.Core.Tests/MatchUpdaterTesting.cs @@ -0,0 +1,36 @@ +using LittleTown.Core.Actions; +using LittleTown.StaticDataAcces; + +namespace LittleTown.Core.Tests; + +public class MatchUpdaterTesting +{ + private class TestAction : IAction + { + public Action CB { get; init; } = delegate { }; + + public void Execute(MatchUpdater updater) + { + CB(updater); + } + } + + [Fact] + public void EnforcePlayerCountInMatchCreation() + { + StaticDataGetter getter = new StaticDataGetter(); + + Match match = new Match(getter); + match.AddPlayer("Player1"); + match.AddPlayer("Player2"); + match.Init(); + + TestAction ta = new TestAction() + { + CB = (updater) => + { + Assert.True(updater.GetMatch() == match); + } + }; + } +} \ No newline at end of file diff --git a/src/LittleTown.Core.Tests/MatchWorkflowTesting.cs b/src/LittleTown.Core.Tests/MatchWorkflowTesting.cs new file mode 100644 index 0000000..2f92cae --- /dev/null +++ b/src/LittleTown.Core.Tests/MatchWorkflowTesting.cs @@ -0,0 +1,34 @@ +using LittleTown.Core.Actions; +using LittleTown.Core.Exceptions; +using LittleTown.StaticDataAcces; + +namespace LittleTown.Core.Tests; + +public class MatchWorkflowTesting +{ + + + [Fact] + public void Simple2PlayerGame() + { + StaticDataGetter getter = new StaticDataGetter(); + + Match match = new Match(getter); + match.AddPlayer("Player1"); + match.AddPlayer("Player2"); + match.Init(); + + int count = 0; + while (!match.IsDone) + { + EmptyAction action = new EmptyAction(); + match.ExecuteAction(action); + count++; + if (count > 40) + { + Assert.Fail("Trop d'action pour une partie vide"); + } + } + Assert.Throws(() => match.ExecuteAction(new EmptyAction())); + } +} \ No newline at end of file diff --git a/src/LittleTown.Core/Exceptions/MatchFinishedException.cs b/src/LittleTown.Core/Exceptions/MatchFinishedException.cs new file mode 100644 index 0000000..4a1f8ae --- /dev/null +++ b/src/LittleTown.Core/Exceptions/MatchFinishedException.cs @@ -0,0 +1,21 @@ +namespace LittleTown.Core.Exceptions; + +/// +/// Exception levee quand on essais d'executer une actin dans un match terminé +/// +public class MatchFinishedException : Exception +{ + /// constructeur de base + public MatchFinishedException() : base() { } + + /// Constructeur avec un message d'erreur + /// le message decrivant l'exception en detail + public MatchFinishedException(string message) : base(message) { } + + /// + /// Constructeur avec un message et une exception interne + /// + /// Le message de l'erreur + /// l'exception encapsulée + public MatchFinishedException(string message, Exception innerException) : base(message, innerException) { } +} \ No newline at end of file diff --git a/src/LittleTown.Core/LittleTown.Core.csproj b/src/LittleTown.Core/LittleTown.Core.csproj index 85c685f..d7b5bc5 100644 --- a/src/LittleTown.Core/LittleTown.Core.csproj +++ b/src/LittleTown.Core/LittleTown.Core.csproj @@ -9,8 +9,6 @@ true true All - full - true diff --git a/src/LittleTown.Core/MatchAggregate/Actions/EmptyAction.cs b/src/LittleTown.Core/MatchAggregate/Actions/EmptyAction.cs new file mode 100644 index 0000000..05da48d --- /dev/null +++ b/src/LittleTown.Core/MatchAggregate/Actions/EmptyAction.cs @@ -0,0 +1,15 @@ +namespace LittleTown.Core.Actions; + +/// +/// Un action de test qui permet de faire le déroulé d'une partie, cette action ne fait rien qu'utiliser un ouvrier du joueur et passer au suivant +/// +public class EmptyAction : IAction +{ + /// + public void Execute(MatchUpdater updater) + { + ArgumentNullException.ThrowIfNull(updater); + + updater.NextPlayer(); + } +} \ No newline at end of file diff --git a/src/LittleTown.Core/MatchAggregate/Actions/IAction.cs b/src/LittleTown.Core/MatchAggregate/Actions/IAction.cs new file mode 100644 index 0000000..011ae46 --- /dev/null +++ b/src/LittleTown.Core/MatchAggregate/Actions/IAction.cs @@ -0,0 +1,13 @@ +namespace LittleTown.Core.Actions; + +/// +/// Interface qui représente ce que doivent implémenter les actions +/// +public interface IAction +{ + /// + /// Methode demandant a l'action d'appliquer ses changement au match + /// + /// une interface permettant de modifier le match, elle est valide que la durée de l'appel d'execute + public void Execute(MatchUpdater updater); +} \ No newline at end of file diff --git a/src/LittleTown.Core/MatchAggregate/IMatchUpdater.cs b/src/LittleTown.Core/MatchAggregate/IMatchUpdater.cs new file mode 100644 index 0000000..43278d5 --- /dev/null +++ b/src/LittleTown.Core/MatchAggregate/IMatchUpdater.cs @@ -0,0 +1,21 @@ +namespace LittleTown.Core.Actions; + +using LittleTown.Core; +using LittleTown.Core.Enums; + +/// +/// Ceci est une structure offrant des méthodes pour modifier les données d'un match +/// Il permet aux actions de modifier des valeurs qui sont privées +/// +public class MatchUpdater +{ + /// Permet de récupérer le match qui sera modifié par cet updater + public required Func GetMatch { get; init; } + + /// Permet d'ajouter des ressources a un joueur + public required Action AddRessourceToPlayer { get; init; } + + /// demande au match de changer de joueur en cours + public required Action NextPlayer { get; init; } + +} \ No newline at end of file diff --git a/src/LittleTown.Core/MatchAggregate/Match.cs b/src/LittleTown.Core/MatchAggregate/Match.cs index 4ddbb74..4da3b5e 100644 --- a/src/LittleTown.Core/MatchAggregate/Match.cs +++ b/src/LittleTown.Core/MatchAggregate/Match.cs @@ -1,3 +1,4 @@ +using LittleTown.Core.Actions; using LittleTown.Core.Exceptions; using LittleTown.Core.Ports; @@ -8,6 +9,18 @@ namespace LittleTown.Core; /// public class Match { + /// LE numero du tour en cours (Partant de 1) + public int CurrentTurn { get; private set; } = 1; + + /// l'id du joueur a qui c'est le tour de jouer + public string CurrentPlayer { get => _players[_playerTurnsOrder[_currentPlayerIndex]].PlayerName; } + + /// Indique si le match est terminé + public bool IsDone { get; private set; } + + /// la liste indiquant l'ordre des joueurs, _playerTurnsOrder[0] donne l'index du 1er joueur, _playerTurnsOrder[1] du second..... + private List _playerTurnsOrder = new List(); + private const int _minPlayerCount = 2; private const int _maxPlayerCount = 4; @@ -20,18 +33,12 @@ public class Match private Random _random = new Random(); - private Dictionary _players = new(); - - /// LE numero du tour en cours (Partant de 1) - public int CurrentTurn { get; private set; } = 1; - - /// la liste indiquant l'ordre des joueurs, _playerTurnsOrder[0] donne l'index du 1er joueur, _playerTurnsOrder[1] du second..... - private List _playerTurnsOrder = new List(); + private List _players = new(); + private int _currentPlayerIndex; /// /// Constructeur d'une nouvelle partie avec un nombre de joueurs données en parametres /// - /// /// un objet permettant de récupérer les données statiques du jeu public Match(IStaticDataGetter staticData) { @@ -48,11 +55,14 @@ public class Match { if (_players.Count < _maxPlayerCount) { - if (_players.ContainsKey(playerName)) + if (_players.Any(p => p.PlayerName == playerName)) { throw new MatchConfigException("Un joueur existe déjà avec ce nom"); } - _players.Add(playerName, new PlayerZone()); + _players.Add(new PlayerZone() + { + PlayerName = playerName + }); } else { @@ -60,6 +70,30 @@ public class Match } } + + /// + /// Demander au match d'executer une action si elle est autorisée + /// + /// l'action a réaliser + public void ExecuteAction(IAction action) + { + ArgumentNullException.ThrowIfNull(action); + + //quelques vérification génériques pour savoir si l'action peut être jouée + if (IsDone) + { + throw new MatchFinishedException("Impossible d'effectuer une action sur un match terminé"); + } + + //autoriser l'action a s'executer en lui donner les getters dont elle a besoin + action.Execute(new MatchUpdater() + { + GetMatch = () => { return this; }, + AddRessourceToPlayer = (resourceType) => { }, + NextPlayer = NextPlayer + }); + } + /// Initialiser la partie, il faut avoir ajouté les joueurs au préalable /// public void Init() @@ -70,13 +104,14 @@ public class Match ArgumentOutOfRangeException.ThrowIfGreaterThan(nbPlayer, _maxPlayerCount); List freeObjectiveIndexs = Enumerable.Range(0, _objectives.Count).ToList(); - foreach (PlayerZone zone in _players.Values) + foreach (PlayerZone zone in _players) { zone.AddObjectives(GetRandomObjectives(nbPlayer switch { 2 => 4, 3 => 3, 4 => 2, + _ => throw new MatchConfigException("Mauvais nombre de joueurs lors Workers") }, freeObjectiveIndexs)); zone.AddRessources(Enums.ResourceType.Piece, 3); } @@ -106,6 +141,8 @@ public class Match index = 0; } + _currentPlayerIndex = 0; + } /// Permet de récuperer une player zone(une copie) @@ -113,7 +150,10 @@ public class Match /// public PlayerZone GetPlayerZone(string playerName) { - if (!_players.TryGetValue(playerName, out PlayerZone? value)) + var value = _players.Where(p => p.PlayerName == playerName).FirstOrDefault(); + + + if (null == value) throw new ArgumentException("playerID is out of bound"); return value.Clone() as PlayerZone ?? throw new ArgumentException("Cast exception in GetPlayerZone"); ; @@ -131,4 +171,17 @@ public class Match } return result; } + private void NextPlayer() + { + _currentPlayerIndex++; + if (_currentPlayerIndex >= _players.Count) + { + _currentPlayerIndex = 0; + CurrentTurn++; + } + if (CurrentTurn >= 4) + { + IsDone = true; + } + } } \ No newline at end of file diff --git a/src/LittleTown.Core/PlayerZone/PlayerZone.cs b/src/LittleTown.Core/PlayerZone/PlayerZone.cs index 1598ad3..ac3afc7 100644 --- a/src/LittleTown.Core/PlayerZone/PlayerZone.cs +++ b/src/LittleTown.Core/PlayerZone/PlayerZone.cs @@ -7,7 +7,8 @@ public class PlayerZone : ICloneable /// Les ressources que possede le joueur public IDictionary Ressources { get; init; } = new Dictionary(); - + /// l'id du joueur a cette zone + public required string PlayerName { get; init; } /// La liste des objectifs que le joueur possede/// public IReadOnlyCollection Objectives { get => _objectives.AsReadOnly(); init => _objectives = new List(value); } @@ -32,6 +33,10 @@ public class PlayerZone : ICloneable } } + /// + /// Assigner des objectifs au joueur + /// + /// public void AddObjectives(ICollection objectives) { _objectives.AddRange(objectives); @@ -45,7 +50,8 @@ public class PlayerZone : ICloneable { Ressources = new Dictionary(Ressources), Objectives = new List(Objectives), - ScoreMarker = ScoreMarker + ScoreMarker = ScoreMarker, + PlayerName = PlayerName }; return result;