diff --git a/Documentation/doc-images/choose_dice.png b/Documentation/doc-images/choose_dice.png new file mode 100644 index 0000000..db5f05c Binary files /dev/null and b/Documentation/doc-images/choose_dice.png differ diff --git a/Documentation/doc-images/choose_player.png b/Documentation/doc-images/choose_player.png new file mode 100644 index 0000000..66c6621 Binary files /dev/null and b/Documentation/doc-images/choose_player.png differ diff --git a/Documentation/doc-images/cr_d_colorface.png b/Documentation/doc-images/cr_d_colorface.png new file mode 100644 index 0000000..28b2f53 Binary files /dev/null and b/Documentation/doc-images/cr_d_colorface.png differ diff --git a/Documentation/doc-images/cr_d_dice.png b/Documentation/doc-images/cr_d_dice.png new file mode 100644 index 0000000..9a3d1f8 Binary files /dev/null and b/Documentation/doc-images/cr_d_dice.png differ diff --git a/Documentation/doc-images/cr_d_favgroup.png b/Documentation/doc-images/cr_d_favgroup.png new file mode 100644 index 0000000..fe33142 Binary files /dev/null and b/Documentation/doc-images/cr_d_favgroup.png differ diff --git a/Documentation/doc-images/cr_d_imageface.png b/Documentation/doc-images/cr_d_imageface.png new file mode 100644 index 0000000..eabbd5b Binary files /dev/null and b/Documentation/doc-images/cr_d_imageface.png differ diff --git a/Documentation/doc-images/cr_d_numberface.png b/Documentation/doc-images/cr_d_numberface.png new file mode 100644 index 0000000..89b60aa Binary files /dev/null and b/Documentation/doc-images/cr_d_numberface.png differ diff --git a/Documentation/doc-images/crud_player.png b/Documentation/doc-images/crud_player.png new file mode 100644 index 0000000..ed4b18e Binary files /dev/null and b/Documentation/doc-images/crud_player.png differ diff --git a/Documentation/doc-images/main_menu.png b/Documentation/doc-images/main_menu.png new file mode 100644 index 0000000..26ad2a7 Binary files /dev/null and b/Documentation/doc-images/main_menu.png differ diff --git a/Documentation/doc-images/playing.png b/Documentation/doc-images/playing.png new file mode 100644 index 0000000..d83747c Binary files /dev/null and b/Documentation/doc-images/playing.png differ diff --git a/Documentation/doc-images/playing_pencil.png b/Documentation/doc-images/playing_pencil.png new file mode 100644 index 0000000..6f99934 Binary files /dev/null and b/Documentation/doc-images/playing_pencil.png differ diff --git a/Documentation/doc-images/zoom_cr___colorface.png b/Documentation/doc-images/zoom_cr___colorface.png new file mode 100644 index 0000000..b2dfb6e Binary files /dev/null and b/Documentation/doc-images/zoom_cr___colorface.png differ diff --git a/Documentation/doc-images/zoom_cr___favgroup.png b/Documentation/doc-images/zoom_cr___favgroup.png new file mode 100644 index 0000000..5a81cd5 Binary files /dev/null and b/Documentation/doc-images/zoom_cr___favgroup.png differ diff --git a/Documentation/doc-images/zoom_cr___imageface.png b/Documentation/doc-images/zoom_cr___imageface.png new file mode 100644 index 0000000..d2e1f24 Binary files /dev/null and b/Documentation/doc-images/zoom_cr___imageface.png differ diff --git a/Documentation/doc-images/zoom_cru__player.png b/Documentation/doc-images/zoom_cru__player.png new file mode 100644 index 0000000..7a2c065 Binary files /dev/null and b/Documentation/doc-images/zoom_cru__player.png differ diff --git a/README.md b/README.md index bafc8a4..6fa6ad8 100644 --- a/README.md +++ b/README.md @@ -11,16 +11,28 @@ [![Technical Debt](https://codefirst.iut.uca.fr/sonar/api/project_badges/measure?project=dice-app&metric=sqale_index&token=bf024850973b7556eef0b981a1b838867848005c)](https://codefirst.iut.uca.fr/sonar/dashboard?id=dice-app) [![Vulnerabilities](https://codefirst.iut.uca.fr/sonar/api/project_badges/measure?project=dice-app&metric=vulnerabilities&token=bf024850973b7556eef0b981a1b838867848005c)](https://codefirst.iut.uca.fr/sonar/dashboard?id=dice-app) # dice_app: the die throwing app + +## To use the app + +TLDR: you can't really + +Open the *DiceApp* solution and navigate to the *App* project. The *Program.cs* file has a `Main()` method that can be launched. It will soon be able to load a stub. Then you will be able to play a console prototype / perform functional tests. But it isn't ready yet. + ## To contribute (workflow) + We are using the feature branch workflow ([details here](https://www.atlassian.com/git/tutorials/comparing-workflows/feature-branch-workflow), or see the summary below) + ### 1 - Sync with the remote + Make sure you're working with the latest version of the project ``` git checkout main git fetch origin git reset --hard origin/main ``` + ### 2 - Create a new branch + Give your new branch a name referring to an issue (or maybe a group of similar issues) ``` git checkout -b new-feature @@ -30,16 +42,22 @@ Regularly, you might want to get all the new code from your main branch, to work ``` git pull --rebase origin main ``` + ### 3 - Code + :fire::technologist::bug::fire:............:white_check_mark: + ### 4 - Save your changes to your new branch + For a refresher, see details about `add`, `commit`, `push`, etc. [here](https://www.atlassian.com/git/tutorials/saving-changes) It should involve creating a corresponding feature branch on the remote repository ``` git push -u origin new-feature ``` + ### 5 - Create a Pull Request + On [the repository's main page](https://codefirst.iut.uca.fr/git/alexis.drai/dice_app), or on your new branch's main page, look for a `New Pull Request` button. It should then allow you to `merge into: ...:main` and `pull from: ...:new-feature` diff --git a/Sources/App/Program.cs b/Sources/App/Program.cs index 6d60ed5..521fa48 100644 --- a/Sources/App/Program.cs +++ b/Sources/App/Program.cs @@ -1,5 +1,6 @@ using Data; -using Model; +using Model.Games; +using System.Diagnostics; namespace App { diff --git a/Sources/Data/ILoader.cs b/Sources/Data/ILoader.cs index 1d96554..21a3cd5 100644 --- a/Sources/Data/ILoader.cs +++ b/Sources/Data/ILoader.cs @@ -3,7 +3,7 @@ using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; -using Model; +using Model.Games; namespace Data { diff --git a/Sources/Data/Stub.cs b/Sources/Data/Stub.cs index 7896c10..ace25d7 100644 --- a/Sources/Data/Stub.cs +++ b/Sources/Data/Stub.cs @@ -1,4 +1,7 @@ -using Model; +using Model.Dice; +using Model.Dice.Faces; +using Model.Games; +using Model.Players; namespace Data { @@ -11,8 +14,49 @@ namespace Data public GameRunner LoadApp() { - // this doesn't do much for now, because the class isn't coded - return new GameRunner(); + string g1 = "game1", g2 = "game2", g3 = "game3"; + + Player player1 = new("Alice"), player2 = new("Bob"), player3 = new("Clyde"); + + FavGroupManager favGroupManager = new(new DieManager()); + + // create at least one fav group in there + // ... + + Game game1 = new(name: g1, playerManager: new PlayerManager(), favGroup: favGroupManager.GetAll().First()); + Game game2 = new(name: g2, playerManager: new PlayerManager(), favGroup: favGroupManager.GetAll().Last()); + Game game3 = new(name: g3, playerManager: new PlayerManager(), favGroup: favGroupManager.GetAll().First()); + + List games = new() { game1, game2, game3 }; + + PlayerManager globalPlayerManager = new(); + globalPlayerManager.Add(player1); + globalPlayerManager.Add(player2); + globalPlayerManager.Add(player3); + + GameRunner gameRunner = new(globalPlayerManager, favGroupManager, games); + + game1.AddPlayerToGame(player1); + game1.AddPlayerToGame(player2); + + game2.AddPlayerToGame(player1); + game2.AddPlayerToGame(player2); + game2.AddPlayerToGame(player3); + + game3.AddPlayerToGame(player1); + game3.AddPlayerToGame(player3); + + foreach (Game game in games) + { + for (int i = 0; i < 10; i++) + { + Player currentPlayer = game.GetWhoPlaysNow(); + game.PerformTurn(currentPlayer); + game.PrepareNextPlayer(currentPlayer); + } + } + + return gameRunner; } public static List LoadPlayers() @@ -29,6 +73,7 @@ namespace Data return list; } +<<<<<<< HEAD public static List LoadDices() { List list = new() @@ -44,6 +89,8 @@ namespace Data return list; } +======= +>>>>>>> main public static List LoadNumFaces() { List list = new() diff --git a/Sources/Model/Dice/Die.cs b/Sources/Model/Dice/Die.cs new file mode 100644 index 0000000..7f2314a --- /dev/null +++ b/Sources/Model/Dice/Die.cs @@ -0,0 +1,22 @@ +using System; +using System.Collections.Generic; +using Model.Dice.Faces; + +namespace Model.Dice +{ + public class Die + { + private static readonly Random random = new Random(); + + private List faces; + + public AbstractDieFace Throw() + { + // next(x, y) --> [x, y[ + return faces[random.Next(0, faces.Count)]; + // replace with better algo later + } + } + + +} diff --git a/Sources/Model/Dice/DieManager.cs b/Sources/Model/Dice/DieManager.cs new file mode 100644 index 0000000..200ea6b --- /dev/null +++ b/Sources/Model/Dice/DieManager.cs @@ -0,0 +1,9 @@ +using System.Collections.Generic; + +namespace Model.Dice +{ + public class DieManager + { + private readonly IEnumerable dice = new List(); + } +} diff --git a/Sources/Model/AbstractDieFace.cs b/Sources/Model/Dice/Faces/AbstractDieFace.cs similarity index 95% rename from Sources/Model/AbstractDieFace.cs rename to Sources/Model/Dice/Faces/AbstractDieFace.cs index b9baca9..a8071c3 100644 --- a/Sources/Model/AbstractDieFace.cs +++ b/Sources/Model/Dice/Faces/AbstractDieFace.cs @@ -4,7 +4,7 @@ using System.Linq; using System.Text; using System.Threading.Tasks; -namespace Model +namespace Model.Dice.Faces { public abstract class AbstractDieFace { diff --git a/Sources/Model/ColorDieFace.cs b/Sources/Model/Dice/Faces/ColorDieFace.cs similarity index 87% rename from Sources/Model/ColorDieFace.cs rename to Sources/Model/Dice/Faces/ColorDieFace.cs index ace2392..2c8fe12 100644 --- a/Sources/Model/ColorDieFace.cs +++ b/Sources/Model/Dice/Faces/ColorDieFace.cs @@ -4,7 +4,7 @@ using System.Linq; using System.Text; using System.Threading.Tasks; -namespace Model +namespace Model.Dice.Faces { public class ColorDieFace : AbstractDieFace { @@ -38,7 +38,12 @@ namespace Model { // https://stackoverflow.com/questions/1139957/convert-integer-to-hexadecimal-and-back-again // maybe prepend it with a "#"... - return Value.ToString("X"); + return Value.ToString("X6").Insert(0, "#"); + } + + public override string ToString() + { + return GetPracticalValue().ToString(); } } } diff --git a/Sources/Model/ImageDieFace.cs b/Sources/Model/Dice/Faces/ImageDieFace.cs similarity index 81% rename from Sources/Model/ImageDieFace.cs rename to Sources/Model/Dice/Faces/ImageDieFace.cs index 946052a..e33aa95 100644 --- a/Sources/Model/ImageDieFace.cs +++ b/Sources/Model/Dice/Faces/ImageDieFace.cs @@ -4,7 +4,7 @@ using System.Linq; using System.Text; using System.Threading.Tasks; -namespace Model +namespace Model.Dice.Faces { public class ImageDieFace : AbstractDieFace { @@ -29,7 +29,12 @@ namespace Model public override object GetPracticalValue() { - return String.Format("Assets/images/{0}", Value); + return string.Format("Assets/images/{0}", Value); + } + + public override string ToString() + { + return GetPracticalValue().ToString(); } } } diff --git a/Sources/Model/NumberDieFace.cs b/Sources/Model/Dice/Faces/NumberDieFace.cs similarity index 74% rename from Sources/Model/NumberDieFace.cs rename to Sources/Model/Dice/Faces/NumberDieFace.cs index f340c13..82cd950 100644 --- a/Sources/Model/NumberDieFace.cs +++ b/Sources/Model/Dice/Faces/NumberDieFace.cs @@ -4,7 +4,7 @@ using System.Linq; using System.Text; using System.Threading.Tasks; -namespace Model +namespace Model.Dice.Faces { public class NumberDieFace : AbstractDieFace { @@ -18,5 +18,10 @@ namespace Model { return Value; } + + public override string ToString() + { + return GetPracticalValue().ToString(); + } } } diff --git a/Sources/Model/Dice/FavGroup.cs b/Sources/Model/Dice/FavGroup.cs new file mode 100644 index 0000000..b718e38 --- /dev/null +++ b/Sources/Model/Dice/FavGroup.cs @@ -0,0 +1,13 @@ +using Model.Players; +using System.Collections.Generic; + +namespace Model.Dice +{ + public class FavGroup + { + public IEnumerable Dice { get; private set; } + + public string Name { get; private set; } + + } +} diff --git a/Sources/Model/Dice/FavGroupManager.cs b/Sources/Model/Dice/FavGroupManager.cs new file mode 100644 index 0000000..4fcf5d3 --- /dev/null +++ b/Sources/Model/Dice/FavGroupManager.cs @@ -0,0 +1,21 @@ +using Model.Players; +using System.Collections.Generic; +using System.Linq; + +namespace Model.Dice +{ + public class FavGroupManager + { + private readonly List favGroups = new(); + + private readonly DieManager dieManager; + + public FavGroupManager(DieManager dieManager) + { + this.dieManager = dieManager; + } + + public IEnumerable GetAll() => favGroups.AsEnumerable(); + + } +} diff --git a/Sources/Model/GameRunner.cs b/Sources/Model/GameRunner.cs deleted file mode 100644 index 255a75a..0000000 --- a/Sources/Model/GameRunner.cs +++ /dev/null @@ -1,12 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace Model -{ - public class GameRunner - { - } -} diff --git a/Sources/Model/Games/Game.cs b/Sources/Model/Games/Game.cs new file mode 100644 index 0000000..2ee0a41 --- /dev/null +++ b/Sources/Model/Games/Game.cs @@ -0,0 +1,175 @@ +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; + + /// + /// whether this game is new or not + /// + public bool IsFirstTurn { get; private set; } = false; + + /// + /// 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 PlayerManager playerManager; + + /// + /// the group of dice used for this game + /// + private readonly FavGroup favGroup; + + /// + /// 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, PlayerManager playerManager, FavGroup favGroup, IEnumerable turns) + { + Name = name; + this.turns = turns.ToList() ?? new List(); + this.playerManager = playerManager; + this.favGroup = favGroup; + } + + /// + /// 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, PlayerManager playerManager, FavGroup favGroup) + : this(name, playerManager, favGroup, null) + { } + + /// + /// performs a Turn, marks this Game as "started", and logs that Turn + /// + /// the player whose turn it is + public void PerformTurn(Player player) + { + if (IsFirstTurn) { IsFirstTurn = false; } // only true one time (on the first turn...) + Turn turn = Turn.CreateWithDefaultTime( + new Player(player), + ThrowAll() //using a copy so that next line doesn't "change history" + ); + turns.Add(turn); + } + + /// + /// finds whose turn it is + /// + /// the Player whose turn it is + public Player GetWhoPlaysNow() + { + return playerManager.WhoPlaysNow(IsFirstTurn); + } + + /// + /// throws all the Dice in FavGroup and returns a list of their Faces + /// + /// list of AbstractDieFaces after a throw + private List ThrowAll() + { + List faces = new(); + foreach (Die die in favGroup.Dice) + { + faces.Add(die.Throw()); + } + return faces; + } + + /// + /// asks the PlayerManager to prepare the next Player + /// + /// the Player whose turn it was + public void PrepareNextPlayer(Player currentPlayer) + { + playerManager.PrepareNextPlayer(currentPlayer); + } + + 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.AppendFormat("Game: {0}===========\n" + + "{1} are playing. {2} is next.\n" + + "Log:\n", + Name, + playerManager.GetAll().ToString(), + playerManager.WhoPlaysNow(IsFirstTurn)); + + foreach (Turn turn in this.turns) + { + sb.Append("\t" + turn.ToString()); + } + + return sb.ToString(); + } + } +} diff --git a/Sources/Model/Games/GameRunner.cs b/Sources/Model/Games/GameRunner.cs new file mode 100644 index 0000000..c2a3cd3 --- /dev/null +++ b/Sources/Model/Games/GameRunner.cs @@ -0,0 +1,43 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Model.Dice; +using Model.Players; + +namespace Model.Games +{ + public class GameRunner + { + private readonly PlayerManager globalPlayerManager; + private readonly FavGroupManager favGroupManager; + private readonly List games; + + public GameRunner(PlayerManager globalPlayerManager, FavGroupManager favGroupManager, List games) + { + this.globalPlayerManager = globalPlayerManager; + this.favGroupManager = favGroupManager; + this.games = games ?? new(); + } + + public IEnumerable GetAll() => games.AsEnumerable(); + + /// + /// finds the game with that name and returns it + ///
+ /// that copy does not belong to this gamerunner's games, so it should not be modified + ///
+ /// a games's name + /// game with said name, or null if no such game was found + public Game GetOneGameByName(string name) + { + if (!string.IsNullOrWhiteSpace(name)) + { + Game result = games.FirstOrDefault(g => g.Name == name); + return result; // may return null + } + throw new ArgumentException("param should not be null or blank", nameof(name)); + } + } +} diff --git a/Sources/Model/Turn.cs b/Sources/Model/Games/Turn.cs similarity index 58% rename from Sources/Model/Turn.cs rename to Sources/Model/Games/Turn.cs index 6a07f78..8ae0a37 100644 --- a/Sources/Model/Turn.cs +++ b/Sources/Model/Games/Turn.cs @@ -1,6 +1,13 @@ using System; - -namespace Model +using System.Collections; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Linq; +using System.Text; +using Model.Dice.Faces; +using Model.Players; + +namespace Model.Games { /// /// a Turn consists of a Player, a DateTime, and a IEnumerable of AbstractDieFace @@ -22,22 +29,22 @@ namespace Model /// public readonly Player player; - // ... faces - + /// + /// the collection of Face that were rolled + /// + private readonly IEnumerable faces; /// /// this private constructor is to be used only by factories /// /// date and time of the turn /// player who played the turn - - // ... faces - private Turn(DateTime when, Player player/*, IEnumerable faces*/) + /// faces that were rolled + private Turn(DateTime when, Player player, IEnumerable faces) { this.when = when; this.player = player; - // ... faces - + this.faces = faces; } /// @@ -48,14 +55,16 @@ namespace Model /// /// date and time of the turn /// player who played the turn + /// faces that were rolled /// a new Turn object - - // ... faces - public static Turn CreateWithSpecifiedTime(DateTime when, Player player/*, IEnumerable faces*/) + public static Turn CreateWithSpecifiedTime(DateTime when, Player player, IEnumerable faces) { - // ... faces - if (player == null) + if (faces is null || !faces.Any()) + { + throw new ArgumentException("param should not be null or empty", nameof(faces)); + } + if (player is null) { throw new ArgumentNullException(nameof(player), "param should not be null"); } @@ -64,38 +73,43 @@ namespace Model when = when.ToUniversalTime(); } - return new Turn(when, player/*, faces*/); + return new Turn(when, player, faces); } /// /// creates a Turn with a default time, which is "now" in UTC. /// /// player who played the turn + /// faces that were rolled /// a new Turn object - - // ... faces - public static Turn CreateWithDefaultTime(Player player/*, IEnumerable faces*/) + public static Turn CreateWithDefaultTime(Player player, IEnumerable faces) { - return CreateWithSpecifiedTime(DateTime.UtcNow, player/*, faces*/); + return CreateWithSpecifiedTime(DateTime.UtcNow, player, faces); } - // ... faces - /// /// represents a turn in string format /// /// a turn in string format public override string ToString() { - return String.Format("{0} -- {1} rolled {2}", - ToStringIsoWithZ(), - this.player.ToString(), - ", , ..."); - } + string[] datetime = when.ToString("s", System.Globalization.CultureInfo.InvariantCulture).Split("T"); + string date = datetime[0]; + string time = datetime[1]; - private string ToStringIsoWithZ() - { - return this.when.ToString("s", System.Globalization.CultureInfo.InvariantCulture) + "Z"; + StringBuilder sb = new(); + + sb.AppendFormat("{0} {1} -- {2} rolled:", + date, + time, + player.ToString()); + + foreach (AbstractDieFace face in faces) + { + sb.Append(" " + face.ToString()); + } + + return sb.ToString(); } } } diff --git a/Sources/Model/Player.cs b/Sources/Model/Players/Player.cs similarity index 92% rename from Sources/Model/Player.cs rename to Sources/Model/Players/Player.cs index 32cb8b4..e89c98c 100644 --- a/Sources/Model/Player.cs +++ b/Sources/Model/Players/Player.cs @@ -1,6 +1,6 @@ using System; -namespace Model +namespace Model.Players { /// /// A player for the purpose of a game of dice, consists of a name @@ -15,7 +15,7 @@ namespace Model public string Name { get; private set; } public Player(string name) { - if (!String.IsNullOrWhiteSpace(name)) + if (!string.IsNullOrWhiteSpace(name)) { Name = name.Trim(); } @@ -40,7 +40,7 @@ namespace Model return Name.ToUpper() == other.Name.ToUpper(); // equality is case insensitive } - public override bool Equals(Object obj) + public override bool Equals(object obj) { if (obj is not Player) { diff --git a/Sources/Model/PlayerManager.cs b/Sources/Model/Players/PlayerManager.cs similarity index 55% rename from Sources/Model/PlayerManager.cs rename to Sources/Model/Players/PlayerManager.cs index 806ef4b..f57159d 100644 --- a/Sources/Model/PlayerManager.cs +++ b/Sources/Model/Players/PlayerManager.cs @@ -2,16 +2,21 @@ using System.Collections.Generic; using System.Linq; -namespace Model +namespace Model.Players { public class PlayerManager : IManager { /// - /// a hashset of the players that this manager is in charge of + /// a collection of the players that this manager is in charge of + /// + private readonly List players; + + /// + /// references the position in list of the current player, for a given game. ///
- /// each player is unique (set), and the hash is based on the player's values (name) + /// ASSUMING that each Game made its own instance of PlayerManager ///
- private readonly HashSet players; + public int NextIndex { get; private set; } = 0; public PlayerManager() { @@ -24,12 +29,16 @@ namespace Model /// added player, or null if was null public Player Add(Player toAdd) { - if (toAdd != null) + if (toAdd is null) + { + throw new ArgumentNullException(nameof(toAdd), "param should not be null"); + } + if (players.Contains(toAdd)) { - players.Add(toAdd); - return toAdd; + throw new ArgumentException("this username is already taken", nameof(toAdd)); } - throw new ArgumentNullException(nameof(toAdd), "param should not be null"); + players.Add(toAdd); + return toAdd; } /// @@ -52,7 +61,7 @@ namespace Model /// player with said name, or null if no such player was found public Player GetOneByName(string name) { - if (!String.IsNullOrWhiteSpace(name)) + if (!string.IsNullOrWhiteSpace(name)) { Player wanted = new(name); Player result = players.FirstOrDefault(p => p.Equals(wanted)); @@ -68,6 +77,64 @@ namespace Model /// a readonly enumerable of all this manager's players public IEnumerable GetAll() => players.AsEnumerable(); + /// + /// finds and returns the player whose turn it is + /// + /// + /// + public Player WhoPlaysNow(bool isFirstTurn) + { + if (players.Count == 0) + { + throw new MemberAccessException("you are exploring an empty collection\nthis should not have happened"); + } + + Player result; + if (isFirstTurn) + { + result = players[0]; + } + else + { + result = players[NextIndex]; + } + return result; + } + + /// + /// this feels very dirty + /// + /// + /// + /// + /// + public void PrepareNextPlayer(Player current) + { + if (players.Count == 0) + { + 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 (!players.Contains(current)) + { + throw new ArgumentException("param could not be found in this collection\n did you forget to add it?", nameof(current)); + } + + if (players.Last() == current) + { + // if we've reached the last index, we need to loop back around + NextIndex = 0; + } + else + { + // else we can just move up one from current + NextIndex++; + } + } + /// /// update a player from to /// @@ -101,7 +168,7 @@ namespace Model { throw new ArgumentNullException(nameof(toRemove), "param should not be null"); } - // the built-in HashSet.Remove() method will use our redefined Equals(), using Name only + // the built-in Remove() method will use our redefined Equals(), using Name only players.Remove(toRemove); } } diff --git a/Sources/Tests/Model_UTs/DieTest.cs b/Sources/Tests/Model_UTs/DieTest.cs index 9b42ce0..e4ef9f7 100644 --- a/Sources/Tests/Model_UTs/DieTest.cs +++ b/Sources/Tests/Model_UTs/DieTest.cs @@ -8,8 +8,8 @@ namespace Tests.Model_UTs [Fact] public void TestConstructor() { - AbstractDie die = new AbstractDie("Ben"); - Assert.Equal("Ben", die.Name); + //AbstractDie die = new AbstractDie("Ben"); + //Assert.Equal("Ben", die.Name); } } } diff --git a/Sources/Tests/Model_UTs/PlayerManagerTest.cs b/Sources/Tests/Model_UTs/PlayerManagerTest.cs index f793455..3058929 100644 --- a/Sources/Tests/Model_UTs/PlayerManagerTest.cs +++ b/Sources/Tests/Model_UTs/PlayerManagerTest.cs @@ -1,4 +1,4 @@ -using Model; +using Model.Players; using System; using System.Collections.Generic; using System.Collections.ObjectModel; diff --git a/Sources/Tests/Model_UTs/PlayerTest.cs b/Sources/Tests/Model_UTs/PlayerTest.cs index 9ab7bb8..baaf009 100644 --- a/Sources/Tests/Model_UTs/PlayerTest.cs +++ b/Sources/Tests/Model_UTs/PlayerTest.cs @@ -1,4 +1,4 @@ -using Model; +using Model.Players; using System; using Xunit; diff --git a/Sources/Tests/Model_UTs/TurnTest.cs b/Sources/Tests/Model_UTs/TurnTest.cs index dc85b99..a801f3d 100644 --- a/Sources/Tests/Model_UTs/TurnTest.cs +++ b/Sources/Tests/Model_UTs/TurnTest.cs @@ -1,11 +1,35 @@ -using Model; +using Model.Dice.Faces; +using Model.Games; +using Model.Players; +using Newtonsoft.Json.Linq; using System; +using System.Collections; +using System.Collections.Generic; +using System.Diagnostics; using Xunit; namespace Tests.Model_UTs { public class TurnTest + { + private readonly List FACES; + private readonly int FACE_ONE = 1; + private readonly int FACE_TWO = 12; + private readonly int FACE_THREE = 54; + private readonly int FACE_FOUR = 16548; + + public TurnTest() + { + FACES = new List + { + new NumberDieFace(FACE_ONE), + new NumberDieFace(FACE_TWO), + new ImageDieFace(FACE_THREE), + new ColorDieFace(FACE_FOUR) + }; + } + [Fact] public void TestCreateWithSpecifiedTimeNotUTCThenValid() { @@ -15,7 +39,7 @@ namespace Tests.Model_UTs Assert.NotEqual(DateTimeKind.Utc, dateTime.Kind); // Act - Turn turn = Turn.CreateWithSpecifiedTime(dateTime, player); + Turn turn = Turn.CreateWithSpecifiedTime(dateTime, player, FACES); // Assert Assert.Equal(DateTimeKind.Utc, turn.when.Kind); @@ -31,7 +55,7 @@ namespace Tests.Model_UTs Assert.Equal(DateTimeKind.Utc, dateTime.Kind); // Act - Turn turn = Turn.CreateWithSpecifiedTime(dateTime, player); + Turn turn = Turn.CreateWithSpecifiedTime(dateTime, player, FACES); // Assert Assert.Equal(DateTimeKind.Utc, turn.when.Kind); @@ -45,38 +69,54 @@ 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); + void action() => Turn.CreateWithSpecifiedTime(dateTime, null, FACES); // Assert Assert.Throws(action); } [Fact] - public void TestCreateWithDefaultTimeThenValid() + public void TestCreateWithSpecifiedTimeNullFacesThenException() { // Arrange - Player player = new("Chloe"); + DateTime dateTime = new(year: 2018, month: 06, day: 15, hour: 16, minute: 30, second: 0, kind: DateTimeKind.Utc); + Player player = new("Chucky"); // Act - Turn turn = Turn.CreateWithDefaultTime(player); + void action() => Turn.CreateWithSpecifiedTime(dateTime, player, null); // Assert - Assert.Equal(DateTimeKind.Utc, turn.when.Kind); - Assert.Equal(DateTime.Now.ToUniversalTime().Date, turn.when.Date); - /*** N.B.: might fail between 11:59:59PM and 00:00:00AM ***/ + Assert.Throws(action); } [Fact] - public void TestCreateWithDefaultTimeNullPlayerThenException() + public void TestCreateWithSpecifiedTimeEmptyFacesThenException() { // Arrange - Player player = new("Devon"); + DateTime dateTime = new(year: 2018, month: 06, day: 15, hour: 16, minute: 30, second: 0, kind: DateTimeKind.Utc); + Player player = new("Chucky"); + FACES.Clear(); // Act - static void action() => Turn.CreateWithDefaultTime(null); + void action() => Turn.CreateWithSpecifiedTime(dateTime, player, FACES); // Assert - Assert.Throws(action); + Assert.Throws(action); + } + + [Fact] + public void TestCreateWithDefaultTimeThenValid() + { + // Arrange + Player player = new("Chloe"); + + // Act + Turn turn = Turn.CreateWithDefaultTime(player, FACES); + + // Assert + Assert.Equal(DateTimeKind.Utc, turn.when.Kind); + Assert.Equal(DateTime.Now.ToUniversalTime().Date, turn.when.Date); + /*** N.B.: might fail between 11:59:59PM and 00:00:00AM ***/ } [Fact] @@ -86,11 +126,17 @@ namespace Tests.Model_UTs DateTime dateTime = new(year: 2018, month: 06, day: 15, hour: 16, minute: 30, second: 0, kind: DateTimeKind.Utc); string name = "Bobby"; Player player = new(name); - string expected = $"2018-06-15T16:30:00Z -- {name} rolled , , ..."; - Turn turn = Turn.CreateWithSpecifiedTime(dateTime, player); + string expected = $"2018-06-15 16:30:00 -- {name} rolled: " + + FACE_ONE + " " + + FACE_TWO + + " Assets/images/" + FACE_THREE + " " + + FACE_FOUR.ToString("X6").Insert(0, "#"); + + Turn turn = Turn.CreateWithSpecifiedTime(dateTime, player, FACES); // Act string actual = turn.ToString(); + Debug.WriteLine(actual); // Assert Assert.Equal(expected, actual);