using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Xml.Linq; using System.Security.Cryptography; using System.Collections; using System.Collections.Immutable; using QwirkleClassLibrary.Tiles; using QwirkleClassLibrary.Boards; using QwirkleClassLibrary.Events; using QwirkleClassLibrary.Players; namespace QwirkleClassLibrary.Games { public class Game : IPlayer, IRules { public ReadOnlyDictionary ScoreBoard => scoreBoard.AsReadOnly(); private readonly Dictionary scoreBoard = new(); private TileBag? bag = null; public bool GameRunning { get; private set; } private Board _board = new Board(15, 12); public Board Board { get { return _board; } private set { _board = value; } } public ObservableCollection GetCellsInBoard => new ObservableCollection(Board!.GetCells()); public ReadOnlyCollection PlayerList => players.AsReadOnly(); private readonly List players = new(); public ReadOnlyCollection CellsUsed => cellUsed.AsReadOnly(); private readonly List cellUsed = new(); public event EventHandler? PlayerAddNotified; protected virtual void OnPlayerNotified(AddPlayerNotifiedEventArgs args) => PlayerAddNotified?.Invoke(this, args); public event EventHandler? NextPlayerNotified; protected virtual void OnNextPlayer(NextPlayerNotifiedEventArgs args) => NextPlayerNotified?.Invoke(this, args); public event EventHandler? PlaceTileNotified; protected virtual void OnPlaceTile(PlaceTileNotifiedEventArgs args) => PlaceTileNotified?.Invoke(this, args); public event EventHandler? EndOfGameNotified; protected virtual void OnEndOfGame(EndOfGameNotifiedEventArgs args) => EndOfGameNotified?.Invoke(this, args); /// /// Adds a player in the game if the game is not running, if the name is correct, if the game is not full and if the name is not already taken. /// /// /// boolean to check it public bool AddPlayerInGame(List PlayersTag) { if (GameRunning) { OnPlayerNotified(new AddPlayerNotifiedEventArgs("ERROR : The game is running.")); return false; } for (int i = PlayersTag.Count - 1; i >= 0; i--) { if (string.IsNullOrWhiteSpace(PlayersTag[i])) { PlayersTag.RemoveAt(i); } } if (PlayersTag.Count <= 1 || PlayersTag.Count > 4) { PlayersTag.Clear(); OnPlayerNotified(new AddPlayerNotifiedEventArgs("ERROR : It takes a minimum of 2 players and a maximum of 4 players to start a game.")); return false; } for (int i = PlayersTag.Count - 1; i >= 0; i--) { if (!CheckPlayerTag(PlayersTag, i)) { PlayersTag.RemoveAt(i); return false; } } foreach (var tag in PlayersTag) { Player pl = CreatePlayer(tag); players.Add(pl); scoreBoard.Add(pl, 0); } OnPlayerNotified(new AddPlayerNotifiedEventArgs("Players were correctly added.")); return true; } public bool CheckPlayerTag(List PlayersTag, int pos) { if (string.IsNullOrWhiteSpace(PlayersTag[pos])) { OnPlayerNotified(new AddPlayerNotifiedEventArgs("ERROR with " + (pos + 1) + " entry : The name is null or white space.")); return false; } for(int i=0; i /// Creates a player with a name /// /// /// Player public Player CreatePlayer(string playerTag) { var player = new Player(playerTag); return player; } /// /// Returns the Board of the game /// /// Board public Board? GetBoard() { return Board; } /// /// Returns the tile bag of the game /// /// public TileBag? GetTileBag() { return bag; } /// /// Creates a Board with a number of columns and rows /// /// Board public Board CreateBoard() { Board = new Board(15, 12); return Board; } /// /// Creates a bag of tiles with a number of sets of 36 tiles /// /// /// TileBag public TileBag CreateTileBag(int nbSet) { bag = new TileBag(nbSet); return bag; } /// /// Starts the game if there are at least 2 players and at most 4 players /// public void StartGame() { if (players.Count < 2 || players.Count >= 5) return; Board = CreateBoard(); bag = CreateTileBag(3); GameRunning = true; } /// /// Adds a cell to the list of cells used by the player in his turn if the cell is not null /// /// public void AddCellUsed(Cell? c) { if (c != null) cellUsed.Add(c); } /// /// Empty the list of cells used by the player at the end of his turn /// public void EmptyCellUsed() { cellUsed.Clear(); } /// /// Gets the player who is currently playing /// /// Player /// public Player GetPlayingPlayer() { if (GetPlayingPlayerPosition() == -1) { throw new ArgumentException("No player currently playing !"); } return players[GetPlayingPlayerPosition()]; } /// /// Gets the position of the player who is currently playing /// /// int public int GetPlayingPlayerPosition() { for (int i = 0; i < players.Count; i++) { if (players[i].IsPlaying) { return i; } } return -1; } /// /// Returns the tile of the player who is currently playing at the position postile /// /// /// Tile public Tile TileOfPlayerWithPos(int postile) { return players[GetPlayingPlayerPosition()].Tiles[postile]; } /// /// Gives random picked tiles to the players at the beginning of the game /// public void GiveTilesToPlayers() { foreach (var p in players) { for (int j = 0; j < 6; j++) { if (bag != null) { int val = RandomNumberGenerator.GetInt32(0, bag.TilesBag.Count); p.AddTileToPlayer(bag.TilesBag[val]); bag.RemoveTileInBag(bag.TilesBag[val]); } } } } /// /// Sets the first player of the game at the beginning of the game /// /// string /// public string SetFirstPlayer() { if (GameRunning) { players[0].IsPlaying = true; OnNextPlayer(new NextPlayerNotifiedEventArgs(players[0])); return players[0].NameTag; } throw new ArgumentException("Game is not running"); } /// /// Sets the next player of the game. If there's no current player, it sets the first player /// /// public string SetNextPlayer() { int i = GetPlayingPlayerPosition(); if (i == -1) { return SetFirstPlayer(); } players[i].IsPlaying = false; players[(i + 1) % players.Count].IsPlaying = true; OnNextPlayer(new NextPlayerNotifiedEventArgs(players[(i + 1) % players.Count])); return players[GetPlayingPlayerPosition()].NameTag; } /// /// Allows the player to place a tile on the Board at a (x, y) position /// /// /// /// /// /// bool public bool PlaceTile(Player player, Tile tile, int x, int y) { if (!IsMoveCorrect(tile, x, y, Board!)) return false; if (Board!.AddTileInCell(x, y, tile)) { OnPlaceTile(new PlaceTileNotifiedEventArgs(tile, "was correctly placed !")); AddCellUsed(Board.GetCell(x, y)); return player.RemoveTileToPlayer(tile); } OnPlaceTile(new PlaceTileNotifiedEventArgs(tile, " : Cell already used")); return false; } /// /// Allows a player to draw tiles from the bag as soon as he has less than 6 tiles /// /// /// public bool DrawTiles(Player player) { while (player.Tiles.Count < 6) { if (bag!.TilesBag.Count == 0) { return false; } int val = RandomNumberGenerator.GetInt32(0, bag.TilesBag.Count); player.AddTileToPlayer(bag.TilesBag[val]); bag.RemoveTileInBag(bag.TilesBag[val]); } return true; } /// /// Allows a player to swap some of his tile with the ones in the bag if he can't play them /// /// /// /// bool public bool SwapTiles(Player player, List tilesToSwap) { if (tilesToSwap.Count == 0) { return false; } foreach (var t in tilesToSwap) { if (!player.RemoveTileToPlayer(t)) { return false; } } if (!DrawTiles(player)) { return false; } foreach (var t in tilesToSwap) { bag!.AddTileInBag(t); } return true; } /// /// Extension of IsMoveCorrect to check beyond the surrounding cells of the cell where the tile is placed /// /// /// /// /// used to get the direction on the x axis /// used to get the direction on the y axis /// /// bool public bool CheckExtendedSurroundingCells(Tile tile, int x, int y, int dx, int dy, Board b) { for (int i = 1; i < 7; i++) { var extendedCell = b.GetCell(x + i * dx, y + i * dy); if (extendedCell?.GetTile == null) { break; } if (extendedCell.GetTile.GetColor != tile.GetColor && extendedCell.GetTile.GetShape != tile.GetShape) { OnPlaceTile(new PlaceTileNotifiedEventArgs(tile, " : Color / Shape does not match with the surrounding tiles !")); return false; } if (extendedCell.GetTile.GetColor == tile.GetColor && extendedCell.GetTile.GetShape == tile.GetShape) { OnPlaceTile(new PlaceTileNotifiedEventArgs(tile, " : Tile already placed on the same line / column !")); return false; } if (i == 6) { OnPlaceTile(new PlaceTileNotifiedEventArgs(tile, " : Row/Column already are 6 tiles long !")); return false; } } return true; } /// /// Extension of IsMoveCorrect to check if the tiles are on the same line /// /// /// /// /// /// bool public bool CheckTilesInLine(List cells, Board b, int x, int y) { if (cells.Count == 0) { return true; } var x1 = cells[0].GetX; var y1 = cells[0].GetY; if (cells.Count < 2 && (x1 == x || y1 == y)) { return true; } if (x1 != x && y1 != y) { return false; } var x2 = cells[1].GetX; var y2 = cells[1].GetY; if (x1 == x2) { return x == x1; } if (y1 == y2) { return y == y1; } return false; } /// /// Main method to check if the move the player is trying to make is correct /// /// /// /// /// /// bool public bool IsMoveCorrect(Tile t, int x, int y, Board b) { if (!b.HasOccupiedCase()) { return true; } var surroundingCells = new List { b.GetCell(x + 1, y), b.GetCell(x - 1, y), b.GetCell(x, y + 1), b.GetCell(x, y - 1) }; foreach (var cell in surroundingCells) { if (cell?.GetTile == null) { continue; } if (cell.GetTile.GetColor != t.GetColor && cell.GetTile.GetShape != t.GetShape) { OnPlaceTile(new PlaceTileNotifiedEventArgs(t, " : Colors / Shapes do not match with the surrounding tiles !")); return false; } if (cell.GetTile.GetColor == t.GetColor && cell.GetTile.GetShape == t.GetShape) { OnPlaceTile(new PlaceTileNotifiedEventArgs(t, " is already placed on the same line / column !")); return false; } var dx = cell.GetX - x; var dy = cell.GetY - y; if (!CheckExtendedSurroundingCells(t, x, y, dx, dy, b)) { return false; } } if (!CheckTilesInLine(cellUsed, b, x, y)) { OnPlaceTile(new PlaceTileNotifiedEventArgs(t, "isn't on the same line as the ones previously placed !")); return false; } if (surroundingCells.All(cell => cell?.GetTile == null)) { OnPlaceTile(new PlaceTileNotifiedEventArgs(t, " : You can't place a tile that isn't adjacent to another one !")); return false; } return true; } /// /// Main method to get the score of the player after he played his turn /// /// /// /// /// int public int GetPlayerScore(Player player, ReadOnlyCollection cellsPlayed, Board b) { if (cellsPlayed.Count == 0) { return 0; } int score = cellsPlayed.Count; int nbCellsInLine = cellsPlayed.Count; if (cellsPlayed.Count == 6) { score += 6; } int cellsX = cellsPlayed[0].GetX; int cellsY = cellsPlayed[0].GetY; if (cellsPlayed.Count > 1) { foreach (var cell in cellsPlayed) { if (cellsX != cell.GetX && cellsX != -1) { cellsX = -1; } else if (cellsY != cell.GetY && cellsY != -1) { cellsY = -1; } } } else { cellsX = cellsY = -1; } score += cellsPlayed.Sum(cell => CalculateAdjacentScore(cell, b, cellsPlayed, cellsX, cellsY, ref nbCellsInLine)); if (nbCellsInLine == 6) { score += 6; } if (!scoreBoard.TryAdd(player, score)) { scoreBoard[player] += score; } return score; } /// /// Extension of GetPlayerScore to calculate the score of the player based on the adjacent cells /// /// /// /// /// /// /// /// int public int CalculateAdjacentScore(Cell cell, Board b, ReadOnlyCollection cellsPlayed, int cellsX, int cellsY, ref int nbCellsInLine) { int score = 0; var surroundingCells = new[] { b.GetCell(cell.GetX + 1, cell.GetY), b.GetCell(cell.GetX - 1, cell.GetY), b.GetCell(cell.GetX, cell.GetY + 1), b.GetCell(cell.GetX, cell.GetY - 1) }; foreach (var adjacentCell in surroundingCells) { if (adjacentCell?.GetTile == null || cellsPlayed.Contains(adjacentCell)) { continue; } int dx = adjacentCell.GetX - cell.GetX; int dy = adjacentCell.GetY - cell.GetY; score += CalculateLineScore(cell, dx, dy, b, cellsX, cellsY, ref nbCellsInLine); } return score; } /// /// Extension of GetPlayerScore to calculate the score of the player based on the line/column of the adjacent cells /// /// /// /// /// /// /// /// /// int public int CalculateLineScore(Cell cell, int dx, int dy, Board b, int cellsX, int cellsY, ref int nbCellsInLine) { int score = 0; for (int i = 1; i < 6; i++) { var extendedCell = b.GetCell(cell.GetX + i * dx, cell.GetY + i * dy); if (extendedCell?.GetTile == null) { continue; } if (dx != 0 && cellsX == -1 || dy != 0 && cellsY == -1) { nbCellsInLine++; } score++; } if (dx == 0 && cellsX == -1 && cellsY != -1 || dy == 0 && cellsY == -1 && cellsX != -1) { score += 1; } return score; } /// /// Returns the list of the positions of the players who still have tiles in their bag /// /// List public List CheckTilesBag() { List playerTilesBagPos = []; if (bag!.TilesBag.Count == 0) { for (int i = 0; i < players.Count; i++) { if (players[i].Tiles.Count != 0) { playerTilesBagPos.Add(i); } } } return playerTilesBagPos; } /// /// Returns a boolean to check if the player can play a tile on the Board /// /// /// public bool CheckPlacementPossibilities(List playerTilesBagPos) { for (int i = 0; i < playerTilesBagPos.Count; i++) { for (int j = 0; j < players[playerTilesBagPos[i]].Tiles.Count; j++) { for (int b = 0; b < Board!.ReadCells.Count; b++) { int x = Board.ReadCells[b].GetX; int y = Board.ReadCells[b].GetY; if (IsMoveCorrect(players[playerTilesBagPos[i]].Tiles[j], x, y, Board)) { return true; } } } } return false; } /// /// Main method to check if the game is over /// /// /// public bool CheckGameOver(Player player) { List playerTilesBagPos = CheckTilesBag(); if (playerTilesBagPos.Count != 0 && !CheckPlacementPossibilities(playerTilesBagPos)) { OnEndOfGame(new EndOfGameNotifiedEventArgs(player)); GameRunning = false; scoreBoard[player] += 6; return true; } return false; } public void ClearGame() { players.Clear(); scoreBoard.Clear(); cellUsed.Clear(); bag = null; Board = CreateBoard(); GameRunning = false; } } }