From a03c1f452606a2e31844fb83166d18b56437d8e6 Mon Sep 17 00:00:00 2001 From: "alexis.drai" Date: Wed, 28 Sep 2022 10:36:26 +0200 Subject: [PATCH 1/4] Ensure unicity within Game.turns --- Sources/Model/Games/Game.cs | 421 ++++++++++++++++++------------------ 1 file changed, 212 insertions(+), 209 deletions(-) diff --git a/Sources/Model/Games/Game.cs b/Sources/Model/Games/Game.cs index 96dfbbf..8481a30 100644 --- a/Sources/Model/Games/Game.cs +++ b/Sources/Model/Games/Game.cs @@ -1,209 +1,212 @@ -using Model.Dice; -using Model.Dice.Faces; -using Model.Players; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Numerics; -using System.Text; - -namespace Model.Games -{ - public class Game - { - /// - /// the name of the game 😎 - /// - public string Name - { - get - { - return name; - } - set // GameRunner will need to take care of forbidding - // (or allowing) having two Games with the same name etc. - { - if (string.IsNullOrWhiteSpace(value)) - { - throw new ArgumentException("param should not be null or blank", nameof(value)); - } - name = value; - } - } - private string name; - - /// - /// references the position in list of the current player, for a given game. - /// - private int nextIndex; - - /// - /// the turns that have been done so far - /// - private readonly List turns; - - /// - /// get a READ ONLY enumerable of all turns belonging to this game - /// - /// a readonly enumerable of all this game's turns - public IEnumerable GetHistory() => turns.AsEnumerable(); - - /// - /// the game's player manager, doing CRUD on players and switching whose turn it is - /// - private readonly IManager playerManager; - - /// - /// the group of dice used for this game - /// - public IEnumerable> Dice => dice; - private readonly IEnumerable> dice; - - /// - /// constructs a Game with its own history of Turns. - /// If is null, starts a new history - /// - /// the name of the game 😎 - /// the turns that have been done so far - /// the game's player manager, doing CRUD on players and switching whose turn it is - /// the group of dice used for this game - public Game(string name, IManager playerManager, IEnumerable> dice, IEnumerable turns) - { - Name = name; - this.turns = turns is null ? new List() : turns.ToList(); - this.playerManager = playerManager; - this.dice = dice; - this.nextIndex = 0; - } - - /// - /// constructs a Game with no history of turns. - /// - /// the name of the game 😎 - /// the game's player manager, doing CRUD on players and switching whose turn it is - /// the group of dice used for this game - public Game(string name, IManager playerManager, IEnumerable> dice) - : this(name, playerManager, dice, null) - { } - - /// - /// performs a Turn, marks this Game as "started", and logs that Turn - /// - /// the player whose turn it is - public void PerformTurn(Player player) - { - Turn turn = Turn.CreateWithDefaultTime( - player, - ThrowAll() - ); - turns.Add(turn); - } - - /// - /// finds and returns the player whose turn it is - /// - /// - /// - public Player GetWhoPlaysNow() - { - if (!playerManager.GetAll().Any()) - { - throw new MemberAccessException("you are exploring an empty collection\nthis should not have happened"); - } - return playerManager.GetAll().ElementAt(nextIndex); - } - - /// - /// this feels very dirty - /// - /// the current player - /// - /// - /// - public void PrepareNextPlayer(Player current) - { - if (!playerManager.GetAll().Any()) - { - throw new MemberAccessException("you are exploring an empty collection\nthis should not have happened"); - } - if (current == null) - { - throw new ArgumentNullException(nameof(current), "param should not be null"); - } - if (!playerManager.GetAll().Contains(current)) - { - throw new ArgumentException("param could not be found in this collection\n did you forget to add it?", nameof(current)); - } - if (playerManager.GetAll().Last() == current) - { - // if we've reached the last player, we need the index to loop back around - nextIndex = 0; - } - else - { - // else we can just move up by one from the current index - nextIndex++; - } - } - - /// - /// throws all the Dice in FavGroup and returns a list of their Faces - /// - /// list of AbstractDieFaces after a throw - private Dictionary, AbstractDieFace> ThrowAll() - { - Dictionary, AbstractDieFace> faces = new(); - foreach (AbstractDie die in dice) - { - faces.Add(die, die.GetRandomFace()); - } - return faces; - } - - public Player AddPlayerToGame(Player player) - { - return playerManager.Add(player); - } - - public IEnumerable GetPlayersFromGame() - { - return playerManager.GetAll(); - } - - public Player UpdatePlayerInGame(Player oldPlayer, Player newPlayer) - { - return playerManager.Update(oldPlayer, newPlayer); - } - - public void RemovePlayerFromGame(Player player) - { - playerManager.Remove(player); - } - - /// - /// represents a Game in string format - /// - /// a Game in string format - public override string ToString() - { - StringBuilder sb = new(); - sb.Append($"Game: {Name}"); - - sb.Append("\nPlayers:"); - foreach (Player player in GetPlayersFromGame()) - { - sb.Append($" {player.ToString()}"); - } - - sb.Append($"\nNext: {GetWhoPlaysNow()}"); - - sb.Append("\nLog:\n"); - foreach (Turn turn in this.turns) - { - sb.Append($"\t{turn.ToString()}\n"); - } - - return sb.ToString(); - } - } -} +using Model.Dice; +using Model.Dice.Faces; +using Model.Players; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Numerics; +using System.Text; + +namespace Model.Games +{ + public class Game + { + /// + /// the name of the game 😎 + /// + public string Name + { + get + { + return name; + } + set // GameRunner will need to take care of forbidding + // (or allowing) having two Games with the same name etc. + { + if (string.IsNullOrWhiteSpace(value)) + { + throw new ArgumentException("param should not be null or blank", nameof(value)); + } + name = value; + } + } + private string name; + + /// + /// references the position in list of the current player, for a given game. + /// + private int nextIndex; + + /// + /// the turns that have been done so far + /// + private readonly List turns; + + /// + /// get a READ ONLY enumerable of all turns belonging to this game + /// + /// a readonly enumerable of all this game's turns + public IEnumerable GetHistory() => turns.AsEnumerable(); + + /// + /// the game's player manager, doing CRUD on players and switching whose turn it is + /// + private readonly IManager playerManager; + + /// + /// the group of dice used for this game + /// + public IEnumerable> Dice => dice; + private readonly IEnumerable> dice; + + /// + /// constructs a Game with its own history of Turns. + /// If is null, starts a new history + /// + /// the name of the game 😎 + /// the turns that have been done so far + /// the game's player manager, doing CRUD on players and switching whose turn it is + /// the group of dice used for this game + public Game(string name, IManager playerManager, IEnumerable> dice, IEnumerable turns) + { + Name = name; + this.turns = turns is null ? new List() : turns.ToList(); + this.playerManager = playerManager; + this.dice = dice; + this.nextIndex = 0; + } + + /// + /// constructs a Game with no history of turns. + /// + /// the name of the game 😎 + /// the game's player manager, doing CRUD on players and switching whose turn it is + /// the group of dice used for this game + public Game(string name, IManager playerManager, IEnumerable> dice) + : this(name, playerManager, dice, null) + { } + + /// + /// performs a Turn, marks this Game as "started", and logs that Turn + /// + /// the player whose turn it is + public void PerformTurn(Player player) + { + Turn turn = Turn.CreateWithDefaultTime( + player, + ThrowAll() + ); + if(turn != null && !(turns.Contains(turn))) + { + turns.Add(turn); + } + } + + /// + /// finds and returns the player whose turn it is + /// + /// + /// + public Player GetWhoPlaysNow() + { + if (!playerManager.GetAll().Any()) + { + throw new MemberAccessException("you are exploring an empty collection\nthis should not have happened"); + } + return playerManager.GetAll().ElementAt(nextIndex); + } + + /// + /// this feels very dirty + /// + /// the current player + /// + /// + /// + public void PrepareNextPlayer(Player current) + { + if (!playerManager.GetAll().Any()) + { + throw new MemberAccessException("you are exploring an empty collection\nthis should not have happened"); + } + if (current == null) + { + throw new ArgumentNullException(nameof(current), "param should not be null"); + } + if (!playerManager.GetAll().Contains(current)) + { + throw new ArgumentException("param could not be found in this collection\n did you forget to add it?", nameof(current)); + } + if (playerManager.GetAll().Last() == current) + { + // if we've reached the last player, we need the index to loop back around + nextIndex = 0; + } + else + { + // else we can just move up by one from the current index + nextIndex++; + } + } + + /// + /// throws all the Dice in FavGroup and returns a list of their Faces + /// + /// list of AbstractDieFaces after a throw + private Dictionary, AbstractDieFace> ThrowAll() + { + Dictionary, AbstractDieFace> faces = new(); + foreach (AbstractDie die in dice) + { + faces.Add(die, die.GetRandomFace()); + } + return faces; + } + + public Player AddPlayerToGame(Player player) + { + return playerManager.Add(player); + } + + public IEnumerable GetPlayersFromGame() + { + return playerManager.GetAll(); + } + + public Player UpdatePlayerInGame(Player oldPlayer, Player newPlayer) + { + return playerManager.Update(oldPlayer, newPlayer); + } + + public void RemovePlayerFromGame(Player player) + { + playerManager.Remove(player); + } + + /// + /// represents a Game in string format + /// + /// a Game in string format + public override string ToString() + { + StringBuilder sb = new(); + sb.Append($"Game: {Name}"); + + sb.Append("\nPlayers:"); + foreach (Player player in GetPlayersFromGame()) + { + sb.Append($" {player.ToString()}"); + } + + sb.Append($"\nNext: {GetWhoPlaysNow()}"); + + sb.Append("\nLog:\n"); + foreach (Turn turn in this.turns) + { + sb.Append($"\t{turn.ToString()}\n"); + } + + return sb.ToString(); + } + } +} -- 2.36.3 From 9da428862ff83f0ca602056272c997f793739f19 Mon Sep 17 00:00:00 2001 From: "alexis.drai" Date: Wed, 28 Sep 2022 11:11:47 +0200 Subject: [PATCH 2/4] Override equals and hash, add UTs --- Sources/Model/Games/Game.cs | 15 ++- Sources/Model/Games/Turn.cs | 22 ++++ Sources/Tests/Model_UTs/GameTest.cs | 3 - Sources/Tests/Model_UTs/PlayerTest.cs | 10 -- Sources/Tests/Model_UTs/Point.cs | 19 ++++ Sources/Tests/Model_UTs/TurnTest.cs | 139 ++++++++++++++++++++++++-- 6 files changed, 183 insertions(+), 25 deletions(-) create mode 100644 Sources/Tests/Model_UTs/Point.cs diff --git a/Sources/Model/Games/Game.cs b/Sources/Model/Games/Game.cs index 8481a30..e3eef59 100644 --- a/Sources/Model/Games/Game.cs +++ b/Sources/Model/Games/Game.cs @@ -3,6 +3,7 @@ using Model.Dice.Faces; using Model.Players; using System; using System.Collections.Generic; +using System.Diagnostics; using System.Linq; using System.Numerics; using System.Text; @@ -96,10 +97,22 @@ namespace Model.Games player, ThrowAll() ); - if(turn != null && !(turns.Contains(turn))) + if(AddTurn(turn) == null) + { + // log an error onc we have a logger ? + Debug.WriteLine("tried to add two identical turns: " + turn); + } + } + + + private Turn AddTurn(Turn turn) + { + if (!(turns.Contains(turn))) { turns.Add(turn); + return turn; } + return null; } /// diff --git a/Sources/Model/Games/Turn.cs b/Sources/Model/Games/Turn.cs index 49bf381..01764c4 100644 --- a/Sources/Model/Games/Turn.cs +++ b/Sources/Model/Games/Turn.cs @@ -115,5 +115,27 @@ namespace Model.Games return sb.ToString(); } + + public bool Equals(Turn other) + { + return Player.Equals(other.Player) + && When.Equals(other.When) + && DiceNFaces.SequenceEqual(other.DiceNFaces); + + } + + public override bool Equals(object obj) + { + if (obj is not Turn) + { + return false; + } + return Equals(obj as Turn); + } + + public override int GetHashCode() + { + return HashCode.Combine(Player, When, DiceNFaces); + } } } diff --git a/Sources/Tests/Model_UTs/GameTest.cs b/Sources/Tests/Model_UTs/GameTest.cs index 5a18c07..db90cb6 100644 --- a/Sources/Tests/Model_UTs/GameTest.cs +++ b/Sources/Tests/Model_UTs/GameTest.cs @@ -149,9 +149,6 @@ namespace Tests.Model_UTs int n = 5; - IEnumerable players = game.GetPlayersFromGame(); - Debug.WriteLine(players); - Player currentPlayer; for (int i = 0; i < n; i++) { diff --git a/Sources/Tests/Model_UTs/PlayerTest.cs b/Sources/Tests/Model_UTs/PlayerTest.cs index baaf009..e0c14ee 100644 --- a/Sources/Tests/Model_UTs/PlayerTest.cs +++ b/Sources/Tests/Model_UTs/PlayerTest.cs @@ -79,16 +79,6 @@ namespace Tests.Model_UTs Assert.Equal(expected, actual); } - class Point - { - public int X { get; set; } - public int Y { get; set; } - public Point(int x, int y) - { - X = x; Y = y; - } - } - [Fact] public void TestEqualsFalseIfNotPlayer() { diff --git a/Sources/Tests/Model_UTs/Point.cs b/Sources/Tests/Model_UTs/Point.cs new file mode 100644 index 0000000..a9274f7 --- /dev/null +++ b/Sources/Tests/Model_UTs/Point.cs @@ -0,0 +1,19 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Tests.Model_UTs +{ + public class Point + { + public int X { get; private set; } + public int Y { get; private set; } + public Point(int x, int y) + { + X = x; Y = y; + } + + } +} diff --git a/Sources/Tests/Model_UTs/TurnTest.cs b/Sources/Tests/Model_UTs/TurnTest.cs index 7b3927f..b9e8310 100644 --- a/Sources/Tests/Model_UTs/TurnTest.cs +++ b/Sources/Tests/Model_UTs/TurnTest.cs @@ -13,7 +13,9 @@ namespace Tests.Model_UTs public class TurnTest { - private readonly Dictionary, AbstractDieFace> DICE_N_FACES; + private readonly Dictionary, AbstractDieFace> DICE_N_FACES_1; + private readonly Dictionary, AbstractDieFace> DICE_N_FACES_2; + private static readonly AbstractDieFace FACE_ONE = new NumberDieFace(1); private static readonly AbstractDieFace FACE_TWO = new NumberDieFace(12); private static readonly AbstractDieFace FACE_THREE = new ImageDieFace(54); @@ -57,13 +59,19 @@ namespace Tests.Model_UTs public TurnTest() { - DICE_N_FACES = new() + DICE_N_FACES_1 = new() { { NUM1, FACE_ONE }, { NUM2, FACE_TWO }, { IMG1, FACE_THREE }, { CLR1, FACE_FOUR } }; + DICE_N_FACES_2 = new() + { + { NUM1, FACE_TWO }, + { IMG1, FACE_THREE }, + { CLR1, FACE_FOUR } + }; } @@ -77,7 +85,7 @@ namespace Tests.Model_UTs Assert.NotEqual(DateTimeKind.Utc, dateTime.Kind); // Act - Turn turn = Turn.CreateWithSpecifiedTime(dateTime, player, DICE_N_FACES); + Turn turn = Turn.CreateWithSpecifiedTime(dateTime, player, DICE_N_FACES_1); // Assert Assert.Equal(DateTimeKind.Utc, turn.When.Kind); @@ -95,7 +103,7 @@ namespace Tests.Model_UTs Assert.Equal(DateTimeKind.Utc, dateTime.Kind); // Act - Turn turn = Turn.CreateWithSpecifiedTime(dateTime, player, DICE_N_FACES); + Turn turn = Turn.CreateWithSpecifiedTime(dateTime, player, DICE_N_FACES_1); // Assert Assert.Equal(DateTimeKind.Utc, turn.When.Kind); @@ -110,7 +118,7 @@ namespace Tests.Model_UTs DateTime dateTime = new(year: 2018, month: 06, day: 15, hour: 16, minute: 30, second: 0, kind: DateTimeKind.Utc); // Act - void action() => Turn.CreateWithSpecifiedTime(dateTime, null, DICE_N_FACES); + void action() => Turn.CreateWithSpecifiedTime(dateTime, null, DICE_N_FACES_1); // Assert Assert.Throws(action); @@ -137,10 +145,10 @@ namespace Tests.Model_UTs // Arrange DateTime dateTime = new(year: 2018, month: 06, day: 15, hour: 16, minute: 30, second: 0, kind: DateTimeKind.Utc); Player player = new("Chucky"); - DICE_N_FACES.Clear(); + DICE_N_FACES_1.Clear(); // Act - void action() => Turn.CreateWithSpecifiedTime(dateTime, player, DICE_N_FACES); + void action() => Turn.CreateWithSpecifiedTime(dateTime, player, DICE_N_FACES_1); // Assert Assert.Throws(action); @@ -155,7 +163,7 @@ namespace Tests.Model_UTs Player player = new("Chloe"); // Act - Turn turn = Turn.CreateWithDefaultTime(player, DICE_N_FACES); + Turn turn = Turn.CreateWithDefaultTime(player, DICE_N_FACES_1); // Assert Assert.Equal(DateTimeKind.Utc, turn.When.Kind); @@ -178,7 +186,7 @@ namespace Tests.Model_UTs + FACE_THREE.ToString() + " " + FACE_FOUR.ToString(); - Turn turn = Turn.CreateWithSpecifiedTime(dateTime, player, DICE_N_FACES); + Turn turn = Turn.CreateWithSpecifiedTime(dateTime, player, DICE_N_FACES_1); // Act string actual = turn.ToString(); @@ -195,11 +203,120 @@ namespace Tests.Model_UTs Player player = new("Erika"); // Act - Turn turn = Turn.CreateWithDefaultTime(player, DICE_N_FACES); - IEnumerable, AbstractDieFace>> expected = DICE_N_FACES.AsEnumerable(); + Turn turn = Turn.CreateWithDefaultTime(player, DICE_N_FACES_1); + IEnumerable, AbstractDieFace>> expected = DICE_N_FACES_1.AsEnumerable(); // Assert Assert.Equal(expected, turn.DiceNFaces); + } + + [Fact] + public void TestEqualsFalseIfNotTurn() + { + // Arrange + Point point; + Turn turn; + Player player = new("Freddie"); + + // Act + point = new(1, 2); + turn = Turn.CreateWithDefaultTime(player, DICE_N_FACES_1); + + // Assert + Assert.False(point.Equals(turn)); + Assert.False(point.GetHashCode().Equals(turn.GetHashCode())); + Assert.False(turn.Equals(point)); + Assert.False(turn.GetHashCode().Equals(point.GetHashCode())); + } + + [Fact] + public void TestGoesThruToSecondMethodIfObjIsTypeTurn() + { + // Arrange + Object t1; + Turn t2; + Player player1 = new Player("Marvin"); + Player player2 = new Player("Noah"); + + // Act + t1 = Turn.CreateWithDefaultTime(player1, DICE_N_FACES_1); + t2 = Turn.CreateWithDefaultTime(player2, DICE_N_FACES_2); + + // Assert + Assert.False(t1.Equals(t2)); + Assert.False(t1.GetHashCode().Equals(t2.GetHashCode())); + Assert.False(t2.Equals(t1)); + Assert.False(t2.GetHashCode().Equals(t1.GetHashCode())); + } + + + [Fact] + public void TestEqualsFalseIfNotSamePlayer() + { + // Arrange + Player player1= new("Panama"); + Player player2= new("Clyde"); + + // Act + Turn t1 = Turn.CreateWithDefaultTime(player1, DICE_N_FACES_2); + Turn t2 = Turn.CreateWithDefaultTime(player2, DICE_N_FACES_2); + + // Assert + Assert.False(t1.Equals(t2)); + Assert.False(t1.GetHashCode().Equals(t2.GetHashCode())); + Assert.False(t2.Equals(t1)); + Assert.False(t2.GetHashCode().Equals(t1.GetHashCode())); + } + + [Fact] + public void TestEqualsFalseIfNotSameTime() + { + // Arrange + Player player = new("Oscar"); + + // Act + Turn t1 = Turn.CreateWithSpecifiedTime(new DateTime(1994, 07, 10), player, DICE_N_FACES_1); + Turn t2 = Turn.CreateWithSpecifiedTime(new DateTime(1991, 08, 20), player, DICE_N_FACES_1); + + // Assert + Assert.False(t1.Equals(t2)); + Assert.False(t1.GetHashCode().Equals(t2.GetHashCode())); + Assert.False(t2.Equals(t1)); + Assert.False(t2.GetHashCode().Equals(t1.GetHashCode())); + } + + [Fact] + public void TestEqualsFalseIfNotSameDiceNFaces() + { + // Arrange + Player player = new("Django"); + + // Act + Turn t1 = Turn.CreateWithDefaultTime(player, DICE_N_FACES_1); + Turn t2 = Turn.CreateWithDefaultTime(player, DICE_N_FACES_2); + + // Assert + Assert.False(t1.Equals(t2)); + Assert.False(t1.GetHashCode().Equals(t2.GetHashCode())); + Assert.False(t2.Equals(t1)); + Assert.False(t2.GetHashCode().Equals(t1.GetHashCode())); + } + + [Fact] + public void TestEqualsTrueIfExactlySameProperties() + { + // Arrange + Player player = new("Elyse"); + + // Act + Turn t1 = Turn.CreateWithSpecifiedTime(new DateTime(1990, 04, 29), player, DICE_N_FACES_1); + Turn t2 = Turn.CreateWithSpecifiedTime(new DateTime(1990, 04, 29), player, DICE_N_FACES_1); + + // Assert + Assert.True(t1.Equals(t2)); + Assert.True(t1.GetHashCode().Equals(t2.GetHashCode())); + Assert.True(t2.Equals(t1)); + Assert.True(t2.GetHashCode().Equals(t1.GetHashCode())); } } } -- 2.36.3 From 3631af4e8a20fd3d54db956e5d73af160c2d737c Mon Sep 17 00:00:00 2001 From: "alexis.drai" Date: Wed, 28 Sep 2022 11:14:50 +0200 Subject: [PATCH 3/4] :rotating_light: Seal Turn and implement IEquatable --- Sources/Model/Games/Turn.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/Model/Games/Turn.cs b/Sources/Model/Games/Turn.cs index 01764c4..ea202d4 100644 --- a/Sources/Model/Games/Turn.cs +++ b/Sources/Model/Games/Turn.cs @@ -17,7 +17,7 @@ namespace Model.Games /// Two turns are equal if they are litterally the same instance in RAM /// (default behaviors Equals() and GetHashCode()) /// - public class Turn + public sealed class Turn : IEquatable { /// -- 2.36.3 From 295d43e3cef50f7a364511071fd26381f4aef72b Mon Sep 17 00:00:00 2001 From: "alexis.drai" Date: Wed, 28 Sep 2022 11:18:03 +0200 Subject: [PATCH 4/4] =?UTF-8?q?=F0=9F=94=A5=20Simplify=20adding=20turns?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Sources/Model/Games/Game.cs | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/Sources/Model/Games/Game.cs b/Sources/Model/Games/Game.cs index e3eef59..ba75d6a 100644 --- a/Sources/Model/Games/Game.cs +++ b/Sources/Model/Games/Game.cs @@ -97,22 +97,16 @@ namespace Model.Games player, ThrowAll() ); - if(AddTurn(turn) == null) - { - // log an error onc we have a logger ? - Debug.WriteLine("tried to add two identical turns: " + turn); - } + AddTurn(turn); } - private Turn AddTurn(Turn turn) + private void AddTurn(Turn turn) { if (!(turns.Contains(turn))) { turns.Add(turn); - return turn; } - return null; } /// -- 2.36.3