using MauMau_Server.Mau.GameMessages; using MauMau_Server.Mau.Managers; using MauMau_Server.Websockets; using MauMau_Server.Room; using MauMau_Server.Room.Messages; using Newtonsoft.Json; namespace MauMau_Server.Mau; public class Game : RoomType { private readonly Deck _deck = new(); private readonly TurnManager _turnManager = new(); public Game(Room.Room room, IEnumerable connections) : base(room) { // Convert all the connections to players List players = new(); foreach (var player in connections.Select(connection => new Player(connection))) { // Give the new player a hand of cards var initialHand = _deck.DrawCards(8); player.GiveCards(initialHand); players.Add(player); } // Add all the players to the turn manager _turnManager.Initialize(players); // Broadcast new game state SendGameState(); } /** * */ public override void OnMessage(ConnectionInstance sender, RoomMessage message) { // If the message type is not a game message, ignore the message if (message.Type is not ("CHOOSE" or "DRAW" or "PLAY")) { return; } // Get the player that sent the message var player = _turnManager.Players.FirstOrDefault(x => x.IsMe(sender.Id)); // If the player is not the player that is currently playing, ignore the message if (_turnManager.CurrentPlayer != player) return; // Based on the message intent, handle the message switch (message.Type) { case "CHOOSE": Choose(player, message.Data); break; case "PLAY": Play(player, message.Data); break; default: Draw(player); break; } } /** * */ public override void OnConnect(ConnectionInstance connection) { // Broadcast that a player joined var joinMessage = new JoinMessage(connection.Id, connection.Name + " has joined the room."); _room.BroadCast(new RoomMessage("JOIN", joinMessage)); // Create a new player, give them a new hand and add them to the game var player = new Player(connection); var initialHand = _deck.DrawCards(8); player.GiveCards(initialHand); _turnManager.Players.Add(player); // Broadcast new game state SendGameState(); } /** * */ public override void OnDisconnect(ConnectionInstance connection) { // Broadcast that the player left var leaveMessage = new LeaveMessage(connection.Id, connection.Name + " has left the room."); _room.BroadCast(new RoomMessage("LEAVE", leaveMessage)); // Get the player that left var player = _turnManager.Players.FirstOrDefault(x => x.IsMe(connection.Id)); if (player is null) return; // Add the player's hand to the used deck var playerHand = player.Hand; _deck.AddCardsToUsedDeck(playerHand); // Change the turn if the player that left was the current player if (player == _turnManager.CurrentPlayer) { _turnManager.ChangeTurnTo(); } // Remove the player from the game _turnManager.Players.Remove(player); // Broadcast new game state SendGameState(); } /** * * The player had to choose a new card type * * The player that chose a new card type * A string that represents a CardType */ private void Choose(Player player, string data) { // Convert the data to a CardType, if it fails, ignore the message if (!Enum.TryParse(data, out CardType cardType)) { return; } // Does the current card require the next player to draw cards? var isCardGivingCard = _deck.CurrentCard.CardType == CardType.JOKER || _deck.CurrentCard.CardValue == CardValue.TWO; // Can the player that received a card giving card counter the card? var skippedPlayerCanPlay = player.CanPlayCard(_deck.CurrentCard); // If the card is a card giving card and the player cannot counter it, skip the next player var shouldSkipPlayer = isCardGivingCard && !skippedPlayerCanPlay; var nextPlayer = shouldSkipPlayer ? _turnManager.GetNextPlayer(2) : _turnManager.GetNextPlayer(); // TODO: Not make it a jack, as it would not work when a Joker was played // Set the new current card _deck.CurrentCard = new Card(cardType, CardValue.JACK); // Change the turns _turnManager.ChangeTurnTo(nextPlayer); // Broadcast new game state SendGameState(); } /** * * The player either: * * Could not play a card and had to draw a card * Chose to draw a card as a strategic move * * * The player that drew a card * A string that can be serialized to a drawcard instance */ private void Draw(Player player) { // Draw a card from the deck var drawnCard = _deck.DrawCard(); // Give the card to the player player.GiveCard(drawnCard); // Change the player if the drawn card cannot be played if (!drawnCard.CanBePlayedOn(_deck.CurrentCard)) { _turnManager.ChangeTurnTo(); } // Broadcast new game state SendGameState(); } /** * * The player plays a card, this will only be possible if: * * The player has the correct state * The player has the card in their hand * The card is playable on the current card * * * The player that played * A string that can be serialized to a playcard instance */ private void Play(Player player, string data) { // Check if the player has the correct state to play a card if (player.State != PlayerState.TURN) { return; } // Convert the data to a Card instance var cardData = JsonConvert.DeserializeObject(data).ToCard(); // Check if the player indeed has the card they claim to have var playerCard = player.TakeCardFromHand(cardData); if (playerCard is null) { return; } // Check if the played card is compatible with the current card // If not, ignore the play if (!playerCard.CanBePlayedOn(_deck.CurrentCard)) { return; } // Remove the card from the player's hand player.Hand.Remove(playerCard); // Add the card to the used deck _deck.AddCardToUsedDeck(playerCard); // Set the new current card _deck.CurrentCard = playerCard; if (player.Hand.Count == 0) { EndGame(player); return; } // Change the turn based on the played card HandleNextPlayer(playerCard); // Broadcast new game state SendGameState(); } /** * * Based on the played card, change the turn to the next player. * * The card that was played */ private void HandleNextPlayer(Card card) { switch (card.CardValue) { case CardValue.RED: case CardValue.BLACK: { _turnManager.GetNextPlayer().GiveCards(_deck.DrawCards(5)); _turnManager.CurrentPlayer.State = PlayerState.CHOOSE; break; } case CardValue.TWO: { _turnManager.GetNextPlayer().GiveCards(_deck.DrawCards(2)); _turnManager.ChangeTurnTo(_turnManager.GetNextPlayer(2)); break; } case CardValue.SEVEN: case CardValue.KING: break; case CardValue.EIGHT: _turnManager.ChangeTurnTo(_turnManager.GetNextPlayer(2)); break; case CardValue.ACE: if (_turnManager.Players.Count > 2) { _turnManager.ChangeDirection(); _turnManager.ChangeTurnTo(); } break; case CardValue.JACK: _turnManager.CurrentPlayer.State = PlayerState.CHOOSE; break; case CardValue.THREE: case CardValue.FOUR: case CardValue.FIVE: case CardValue.SIX: case CardValue.NINE: case CardValue.TEN: case CardValue.QUEEN: default: _turnManager.ChangeTurnTo(); break; } } /** * * Create a game state for each player and send it to them. * By making a unique game state for each player we can hide information (like other player's hands). * */ private void SendGameState() { foreach (var player in _turnManager.Players) { var gameState = new GameState(player, _deck.CurrentCard, _turnManager.CurrentPlayer, _turnManager.Players); player.Connection.SendMessageAsync(JsonConvert.SerializeObject(new RoomMessage("GAME", gameState))); } } /** * * Broadcast the winner of the game and change the room's type to a lobby. * * The player that won the game */ private void EndGame(Player winner) { var winMessage = new EndMessage(winner.Connection.Id, winner.Connection.Name); _room.BroadCast(new RoomMessage("END", winMessage)); _room.RoomType = new Lobby(_room); } }