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 readonly List MauCardBuffer = new(); public CardType? NextAllowedCardType { get; set; } 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(_room.Connections, connection); _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); _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; } // Set the next card type that is allowed to be played NextAllowedCardType = cardType; // Change the turns _turnManager.ChangeTurnTo(); // 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 * * When there are multiple mau cards played, the player has to draw the combined amount of mau cards played. * * The player that drew a card * A string that can be serialized to a drawcard instance */ private void Draw(Player player) { // If there are cards in the MauCardBuffer, this means there are multiple cards that need to be drawn // Otherwise, just draw a single card if (MauCardBuffer.Count > 0) { // Count the amount of cards that need to be drawn var totalCards = CountMauCardBuffer(); // Draw the cards from the deck var drawnCards = _deck.DrawCards(totalCards); // Give the cards to the player player.GiveCards(drawnCards); // Change the turn _turnManager.ChangeTurnTo(); } else { // 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 there is a specific card type that is allowed to be played if (NextAllowedCardType != null) { // If the card is not the allowed card type, not the same value or a joker, ignore the message if (playerCard.CardType != NextAllowedCardType && playerCard.CardType != CardType.JOKER && playerCard.CardValue != _deck.CurrentCard.CardValue) { return; } // Reset the allowed card type, so the next player has the normal same type and same value rules NextAllowedCardType = null; } else if (MauCardBuffer.Count > 0) { // If there are queued mau cards, the player can only another mau card (or draw) if (playerCard.CardType != CardType.JOKER && playerCard.CardValue != CardValue.TWO) { return; } } else { // Check if the card can be played on the current card, if not, ignore the message 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: { MauCardBuffer.Add(card); _turnManager.CurrentPlayer.State = PlayerState.CHOOSE; break; } case CardValue.TWO: { MauCardBuffer.Add(card); _turnManager.ChangeTurnTo(); 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; } } /** * * Count the amount of cards that need to be drawn from the MauCardBuffer. This method also clears the buffer. * * The amount of cards that need to be drawn */ private int CountMauCardBuffer() { var totalCards = 0; foreach (var card in MauCardBuffer) { if (card.CardType == CardType.JOKER) { totalCards += 5; continue; } if (card.CardValue == CardValue.TWO) { totalCards += 2; continue; } } MauCardBuffer.Clear(); return totalCards; } /** * * 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, NextAllowedCardType, _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); } }