From 694bc6147a1f399a2a2c21149ac8b1fedf3ce3ec Mon Sep 17 00:00:00 2001 From: DTieman Date: Sat, 4 May 2024 19:55:11 +0200 Subject: [PATCH 1/4] Partial rewrite, missing: - Correctly parsing incoming messages - Sending the gamestate after relevant actions --- Controllers/RoomController.cs | 5 +- Mau/Card.cs | 25 +++ Mau/Deck.cs | 91 ++++++--- Mau/Game.cs | 334 ++++++++++++++++++------------- Mau/GameMessages/ChooseCard.cs | 6 + Mau/GameMessages/DrawCard.cs | 0 Mau/GameMessages/GameMessage.cs | 14 ++ Mau/GameMessages/PlayCard.cs | 30 +++ Mau/GameState.cs | 2 +- Mau/Managers/TurnManager.cs | 80 ++++++++ Mau/Player.cs | 22 +- Program.cs | 1 + Room/Chat/Chat.cs | 5 +- Room/Lobby.cs | 39 ++++ Room/MessageDTO.cs | 2 +- Room/Room.cs | 152 ++++---------- Room/RoomManager.cs | 2 +- Room/RoomMessage.cs | 39 ++++ Room/RoomType.cs | 38 ++++ Websockets/ConnectionInstance.cs | 19 +- 20 files changed, 612 insertions(+), 294 deletions(-) create mode 100644 Mau/GameMessages/ChooseCard.cs create mode 100644 Mau/GameMessages/DrawCard.cs create mode 100644 Mau/GameMessages/GameMessage.cs create mode 100644 Mau/GameMessages/PlayCard.cs create mode 100644 Mau/Managers/TurnManager.cs create mode 100644 Room/Lobby.cs create mode 100644 Room/RoomMessage.cs create mode 100644 Room/RoomType.cs diff --git a/Controllers/RoomController.cs b/Controllers/RoomController.cs index 7d5af5e..f6e27d7 100644 --- a/Controllers/RoomController.cs +++ b/Controllers/RoomController.cs @@ -1,6 +1,5 @@ -using System.Text; -using System.Text.Json; -using MauMau_Server.Websockets; +using System.Text.Json; +using MauMau_Server.Room; using Microsoft.AspNetCore.Mvc; namespace MauMau_Server.Controllers; diff --git a/Mau/Card.cs b/Mau/Card.cs index b9b48e4..204a1ae 100644 --- a/Mau/Card.cs +++ b/Mau/Card.cs @@ -25,6 +25,31 @@ public class Card } } +public static class CardExtensions +{ + public static bool IsSameCard(this Card card1, Card card2) + { + return card1.IsSameCardType(card2) && card1.IsSameCardValue(card2); + } + + public static bool IsSameCardType(this Card card1, Card card2) + { + return card1.CardType == card2.CardType; + } + + public static bool IsSameCardValue(this Card card1, Card card2) + { + return card1.CardValue == card2.CardValue; + } + + public static bool CanBePlayedOn(this Card playedCard, Card currentCard) + { + return playedCard.IsSameCardType(currentCard) + || playedCard.IsSameCardValue(currentCard) + || playedCard.CardType == CardType.JOKER; + } +} + public enum CardType { SPADES, diff --git a/Mau/Deck.cs b/Mau/Deck.cs index 50ffedc..54dff4a 100644 --- a/Mau/Deck.cs +++ b/Mau/Deck.cs @@ -2,47 +2,86 @@ public class Deck { - public List UnusedDeck = new(); - public List UsedDeck = new(); + private List _unusedDeck = new(); + private List _usedDeck = new(); + /** + * + * Creates a new deck instance with a new shuffled set of cards + * + */ public Deck() { CreateSet(); ShuffleDeck(); } + + /** + * + * Adds the given card to the used cards deck. + * + * The card to add to the used cards deck. + */ + public void AddCardToUsedDeck(Card card) + { + _usedDeck.Add(card); + } + /** + * + * Adds the given list of cards to the used cards deck. + * + * The list of cards to add to the used cards deck. + */ + public void AddCardsToUsedDeck(IEnumerable cards) + { + _usedDeck.AddRange(cards); + } + + /** + * + * Creates a new deck of cards and adds them to the unused deck. + * + */ private void CreateSet() { foreach (CardType cardType in Enum.GetValues(typeof(CardType))) { if (cardType == CardType.JOKER) { - UnusedDeck.Add(new Card(cardType, CardValue.RED)); - UnusedDeck.Add(new Card(cardType, CardValue.BLACK)); + _unusedDeck.Add(new Card(cardType, CardValue.RED)); + _unusedDeck.Add(new Card(cardType, CardValue.BLACK)); continue; } foreach (CardValue cardValue in Enum.GetValues(typeof(CardValue))) { if (cardValue is CardValue.RED or CardValue.BLACK) continue; - UnusedDeck.Add(new Card(cardType, cardValue)); + _unusedDeck.Add(new Card(cardType, cardValue)); } } } - public List GetUnusedDeck() - { - return UnusedDeck; - } - + /** + * + * Draws a card from the deck. + * If the deck is empty, the deck is reshuffled with . + * + */ public Card DrawCard() { - if (UnusedDeck.Count == 0) ReshuffleDeck(); - var card = UnusedDeck[0]; - UnusedDeck.RemoveAt(0); + if (_unusedDeck.Count == 0) ReshuffleDeck(); + var card = _unusedDeck[0]; + _unusedDeck.RemoveAt(0); return card; } - public List DrawCards(int amount) + /** + * + * Take a given amount of cards from the deck. This method calls for each card. + * + * The amount of cards to draw from the deck. + */ + public IEnumerable DrawCards(int amount) { var cards = new List(); for (var i = 0; i < amount; i++) @@ -51,18 +90,19 @@ public class Deck } return cards; } - - public void AddCardToUsedDeck(Card card) - { - UsedDeck.Add(card); - } + /** + * + * Moves all the used cards back to the unused deck and shuffles it. + * If there are no cards to reshuffle, a new set of cards is created and shuffled. + * + */ private void ReshuffleDeck() { - UnusedDeck.AddRange(UsedDeck); - UsedDeck.Clear(); + _unusedDeck.AddRange(_usedDeck); + _usedDeck.Clear(); - if (UnusedDeck.Count == 0) + if (_unusedDeck.Count == 0) { CreateSet(); } @@ -70,8 +110,13 @@ public class Deck ShuffleDeck(); } + /** + * + * Shuffles all the cards in the deck using the Fisher-Yates algorithm. + * + */ private void ShuffleDeck() { - UnusedDeck = UnusedDeck.OrderBy(x => Guid.NewGuid()).ToList(); + _unusedDeck = _unusedDeck.OrderBy(x => Guid.NewGuid()).ToList(); } } \ No newline at end of file diff --git a/Mau/Game.cs b/Mau/Game.cs index 3f15e0f..e2ff774 100644 --- a/Mau/Game.cs +++ b/Mau/Game.cs @@ -1,92 +1,207 @@ -using System.Text.Json; +using MauMau_Server.Mau.GameMessages; +using MauMau_Server.Mau.Managers; using MauMau_Server.Websockets; +using MauMau_Server.Room; +using Newtonsoft.Json; namespace MauMau_Server.Mau; -public class Game +public class Game : RoomType { - public readonly Deck Deck = new(); + private readonly Deck _deck = new(); public Card CurrentCard; - public List Players = new(); - public Player CurrentPlayer; - public int TurnDirection = 1; - private readonly Room _room; + private readonly TurnManager _turnManager = new(); - public Game(Room room) + public Game(Room.Room room, IEnumerable connections) : base(room) { - _room = room; - CurrentCard = Deck.DrawCard(); - Deck.AddCardToUsedDeck(CurrentCard); - } + CurrentCard = _deck.DrawCard(); + _deck.AddCardToUsedDeck(CurrentCard); - public void AddPlayerToGame(ConnectionInstance connection) - { - var player = new Player(connection) + List players = new(); + foreach (var player in connections.Select(connection => new Player(connection))) { - Hand = Deck.DrawCards(8) - }; - Players.Add(player); - if (Players.Count > 1) return; - CurrentPlayer = player; - CurrentPlayer.State = PlayerState.TURN; - } - - public void RemovePlayer(string playerId) - { - var player = GetPlayer(playerId); - Players.Remove(player); + var initialHand = _deck.DrawCards(8); + player.GiveCards(initialHand); + players.Add(player); + } + + _turnManager.Initialize(players); } - public void HandleAction(string playerId, ActionDTO action) + /** + * + */ + public override void OnMessage(ConnectionInstance sender, string message) { - var player = GetPlayer(playerId); - if (CurrentPlayer != player) return; - switch (action.Action) + // 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; + + // Deserialize the message + var gameMessage = JsonConvert.DeserializeObject(message); + + // Based on the message intent, handle the message + switch (gameMessage.Intent) { - case "PLAYCARD": - { - if (player.State != PlayerState.TURN) - { - break; - } - var card = JsonSerializer.Deserialize(action.Data).ToCard(); - PlayCard(player, card); + case GameIntent.CHOOSE: + Choose(player, gameMessage.Data); break; - } - case "CHOOSE": - var choice = action.Data; - if (!Enum.TryParse(choice, out CardType cardType)) - { - break; - } - CurrentPlayer.State = PlayerState.WAIT; - CurrentPlayer = CurrentCard.CardType == CardType.JOKER ? GetNextPlayer(2) : GetNextPlayer(); - CurrentPlayer.State = PlayerState.TURN; - CurrentCard = new Card(cardType, CardValue.JACK); + case GameIntent.DRAW: + default: + Draw(player, gameMessage.Data); break; - case "DRAW": - if (player.State != PlayerState.TURN) - { - break; - } - DrawCard(player); + case GameIntent.PLAY: + Play(player, gameMessage.Data); break; } } - private void PlayCard(Player player, Card card) + /** + * + */ + public override void OnConnect(ConnectionInstance connection) { - var hand = player.Hand; - if (!IsCardInHand(hand, card) || !IsCardPlayable(CurrentCard, card)) return; - Deck.AddCardToUsedDeck(card); - hand.Remove(GetSameCardFromHand(hand, card)); - CurrentCard = card; - if (hand.Count == 0) + var player = new Player(connection); + var initialHand = _deck.DrawCards(8); + player.GiveCards(initialHand); + _turnManager.Players.Add(player); + } + + /** + * + */ + public override void OnDisconnect(ConnectionInstance connection) + { + var player = _turnManager.Players.FirstOrDefault(x => x.IsMe(connection.Id)); + if (player is null) return; + var playerHand = player.Hand; + _deck.AddCardsToUsedDeck(playerHand); + if (player == _turnManager.CurrentPlayer) + { + _turnManager.ChangeTurn(); + } + + _turnManager.Players.Remove(player); + } + + /** + * + * 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)) { - _room.EndGame(player); return; } - HandleNextPlayer(card); + + // Does the current card require the next player to draw cards? + var isCardGivingCard = CurrentCard.CardType == CardType.JOKER || CurrentCard.CardValue == CardValue.TWO; + + // Can the player that received a card giving card counter the card? + var skippedPlayerCanPlay = player.CanPlayCard(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 is played + // Set the new current card + CurrentCard = new Card(cardType, CardValue.JACK); + + // Change the turns + _turnManager.ChangeTurn(nextPlayer); + } + + /** + * + * 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, string data) + { + // TODO: data will contain the amount of cards to draw in the future, for now, just draw 1 card + + // Draw a card from the deck + var drawnCard = _deck.DrawCard(); + + // Give the card to the player + player.GiveCard(drawnCard); + + // If the player can play the card, do not change the player + if (drawnCard.CanBePlayedOn(CurrentCard)) return; + + // Change the turn to the next player + _turnManager.ChangeTurn(); + } + + /** + * + * 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(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 + CurrentCard = playerCard; + + if (player.Hand.Count == 0) + { + EndGame(player); + return; + } + + HandleNextPlayer(playerCard); } private void HandleNextPlayer(Card card) @@ -96,41 +211,31 @@ public class Game case CardValue.RED: case CardValue.BLACK: { - var nextPlayer = GetNextPlayer(); - var cardsToDraw = Deck.DrawCards(5); - foreach (var drawnCard in cardsToDraw) - { - nextPlayer.Hand.Add(drawnCard); - } - CurrentPlayer.State = PlayerState.CHOOSE; + _turnManager.GetNextPlayer().GiveCards(_deck.DrawCards(5)); + _turnManager.CurrentPlayer.State = PlayerState.CHOOSE; break; } case CardValue.TWO: { - var nextPlayer = GetNextPlayer(); - var cardsToDraw = Deck.DrawCards(2); - foreach (var drawnCard in cardsToDraw) - { - nextPlayer.Hand.Add(drawnCard); - } - HandleNextPlayer(CurrentPlayer, GetNextPlayer(2)); + _turnManager.GetNextPlayer().GiveCards(_deck.DrawCards(2)); + _turnManager.ChangeTurn(_turnManager.GetNextPlayer(2)); break; } case CardValue.SEVEN: case CardValue.KING: break; case CardValue.EIGHT: - HandleNextPlayer(CurrentPlayer, GetNextPlayer(2)); + _turnManager.ChangeTurn(_turnManager.GetNextPlayer(2)); break; case CardValue.ACE: - if (Players.Count > 2) + if (_turnManager.Players.Count > 2) { - TurnDirection *= -1; - HandleNextPlayer(CurrentPlayer, GetNextPlayer()); + _turnManager.ChangeDirection(); + _turnManager.ChangeTurn(); } break; case CardValue.JACK: - CurrentPlayer.State = PlayerState.CHOOSE; + _turnManager.CurrentPlayer.State = PlayerState.CHOOSE; break; case CardValue.THREE: case CardValue.FOUR: @@ -140,69 +245,18 @@ public class Game case CardValue.TEN: case CardValue.QUEEN: default: - HandleNextPlayer(CurrentPlayer, GetNextPlayer()); + _turnManager.ChangeTurn(); break; } } - private void HandleNextPlayer(Player current, Player next) + private void EndGame(Player winner) { - current.State = PlayerState.WAIT; - next.State = PlayerState.TURN; - CurrentPlayer = next; + _room._roomType = new Lobby(_room, winner.Connection); } - private void DrawCard(Player player) + public Player? GetPlayer(string playerId) { - player.Hand.Add(Deck.DrawCard()); - HandleNextPlayer(player, GetNextPlayer()); - } - - private Player GetNextPlayer(int numberOfPlayers = 1) - { - var index = Players.IndexOf(CurrentPlayer); - for (var i = 0; i < numberOfPlayers; i++) - { - index += TurnDirection; - if (index >= Players.Count) index = 0; - if (index < 0) index = Players.Count - 1; - } - - return Players[index]; - } - - public Player GetPlayer(string playerId) - { - return Players.FirstOrDefault(p => p.IsMe(playerId)); - } - - private static Card GetSameCardFromHand(IEnumerable hand, Card card) - { - return hand.FirstOrDefault(handCard => IsSameCard(handCard, card)); - } - - private static bool IsCardPlayable(Card currentCard, Card playedCard) - { - return IsSameCardType(currentCard, playedCard) || IsSameCardValue(currentCard, playedCard) || playedCard.CardType == CardType.JOKER; - } - - private static bool IsCardInHand(IEnumerable hand, Card card) - { - return hand.Any(handCard => IsSameCard(handCard, card)); - } - - private static bool IsSameCard(Card card1, Card card2) - { - return IsSameCardType(card1, card2) && IsSameCardValue(card1, card2); - } - - private static bool IsSameCardType(Card card1, Card card2) - { - return card1.CardType == card2.CardType; - } - - private static bool IsSameCardValue(Card card1, Card card2) - { - return card1.CardValue == card2.CardValue; + return _turnManager.Players.FirstOrDefault(p => p.IsMe(playerId)); } } \ No newline at end of file diff --git a/Mau/GameMessages/ChooseCard.cs b/Mau/GameMessages/ChooseCard.cs new file mode 100644 index 0000000..9460ac2 --- /dev/null +++ b/Mau/GameMessages/ChooseCard.cs @@ -0,0 +1,6 @@ +namespace MauMau_Server.Mau.GameMessages; + +public class ChooseCard +{ + +} \ No newline at end of file diff --git a/Mau/GameMessages/DrawCard.cs b/Mau/GameMessages/DrawCard.cs new file mode 100644 index 0000000..e69de29 diff --git a/Mau/GameMessages/GameMessage.cs b/Mau/GameMessages/GameMessage.cs new file mode 100644 index 0000000..bdb5643 --- /dev/null +++ b/Mau/GameMessages/GameMessage.cs @@ -0,0 +1,14 @@ +namespace MauMau_Server.Mau.GameMessages; + +public class GameMessage +{ + public GameIntent Intent { get; set; } + public string Data { get; set; } +} + +public enum GameIntent +{ + CHOOSE, + DRAW, + PLAY, +} \ No newline at end of file diff --git a/Mau/GameMessages/PlayCard.cs b/Mau/GameMessages/PlayCard.cs new file mode 100644 index 0000000..aecb732 --- /dev/null +++ b/Mau/GameMessages/PlayCard.cs @@ -0,0 +1,30 @@ +namespace MauMau_Server.Mau.GameMessages; + +public class PlayCard +{ + public string CardType { get; set; } + public string CardValue { get; set; } + + public PlayCard(Card card) + { + CardType = card.CardType.ToString(); + CardValue = card.CardValue.ToString(); + } + + public PlayCard(string cardType, string cardValue) + { + CardType = cardType; + CardValue = cardValue; + } + + public PlayCard() + { + + } + + public Card ToCard() + { + return new Card((CardType)Enum.Parse(typeof(CardType), CardType), + (CardValue)Enum.Parse(typeof(CardValue), CardValue)); + } +} \ No newline at end of file diff --git a/Mau/GameState.cs b/Mau/GameState.cs index 91cac2e..e690327 100644 --- a/Mau/GameState.cs +++ b/Mau/GameState.cs @@ -38,7 +38,7 @@ public class PlayerDTO public PlayerDTO(Player player) { Name = player.Connection.Name; - Id = player.Connection.ConnectionId; + Id = player.Connection.Id; CardsLeft = player.Hand.Count; } } \ No newline at end of file diff --git a/Mau/Managers/TurnManager.cs b/Mau/Managers/TurnManager.cs new file mode 100644 index 0000000..14d4a23 --- /dev/null +++ b/Mau/Managers/TurnManager.cs @@ -0,0 +1,80 @@ +namespace MauMau_Server.Mau.Managers; + +public class TurnManager +{ + private const int CLOCKWISE = 1; + private const int COUNTER_CLOCKWISE = -1; + + private int _currentDirection { get; set; } = CLOCKWISE; + public List Players; + public Player CurrentPlayer; + + /** + * + * Initialize the turn manager with a list of players. + * + * The list of players to initialize the turn manager with. + */ + public void Initialize(IEnumerable players) + { + Players = players.OrderBy(x => Guid.NewGuid()).ToList(); + ShufflePlayers(); + CurrentPlayer = Players.First(); + CurrentPlayer.State = PlayerState.TURN; + } + + /** + * + * Change the direction of the turn. + * If the direction is clockwise, it will change to counter-clockwise and vice versa. + * + */ + public void ChangeDirection() + { + _currentDirection *= COUNTER_CLOCKWISE; + } + + /** + * + * Change the turn to another player. + * If no player is given, the next player in the current direction is chosen. + * + * The player to change the turn to. Defaults to the next player in the current direction. + */ + public void ChangeTurn(Player? nextPlayer = null) + { + var player = nextPlayer ?? GetNextPlayer(); + CurrentPlayer.State = PlayerState.WAIT; + player.State = PlayerState.TURN; + CurrentPlayer = player; + } + + /** + * + * Get a player that is a given amount of players away from the current player in the current direction. + * + * The amount of players to skip. Defaults to 1 + */ + public Player GetNextPlayer(int numberOfPlayers = 1) + { + var playerIndex = Players.IndexOf(CurrentPlayer); + for (var i = 0; i < numberOfPlayers; i++) + { + playerIndex += _currentDirection; + if (playerIndex >= Players.Count) playerIndex = 0; + if (playerIndex < 0) playerIndex = Players.Count - 1; + } + + return Players[playerIndex]; + } + + /** + * + * Shuffle the list of players for a play random order. + * + */ + private void ShufflePlayers() + { + Players = Players.OrderBy(x => Guid.NewGuid()).ToList(); + } +} \ No newline at end of file diff --git a/Mau/Player.cs b/Mau/Player.cs index 0c269c3..de169f6 100644 --- a/Mau/Player.cs +++ b/Mau/Player.cs @@ -13,5 +13,25 @@ public class Player Connection = connection; } - public bool IsMe(string playerId) => Connection.ConnectionId == playerId; + public void GiveCard(Card card) + { + Hand.Add(card); + } + + public void GiveCards(IEnumerable cards) + { + Hand.AddRange(cards); + } + + public Card? TakeCardFromHand(Card card) + { + return Hand.FirstOrDefault(handCard => handCard.IsSameCard(card)); + } + + public bool IsMe(string playerId) => Connection.Id == playerId; + + public bool CanPlayCard(Card currentCard) + { + return Hand.Any(card => card.CanBePlayedOn(currentCard)); + } } \ No newline at end of file diff --git a/Program.cs b/Program.cs index 057d2f3..98f6017 100644 --- a/Program.cs +++ b/Program.cs @@ -1,5 +1,6 @@ using Hangfire; using Hangfire.MemoryStorage; +using MauMau_Server.Room; using MauMau_Server.Websockets; var builder = WebApplication.CreateBuilder(args); diff --git a/Room/Chat/Chat.cs b/Room/Chat/Chat.cs index 338321f..4052e98 100644 --- a/Room/Chat/Chat.cs +++ b/Room/Chat/Chat.cs @@ -1,8 +1,7 @@ -using System.Net.WebSockets; -using System.Text.Json; +using System.Text.Json; using MauMau_Server.Websockets; -namespace MauMau_Server.Mau; +namespace MauMau_Server.Room.Chat; public class Chat { diff --git a/Room/Lobby.cs b/Room/Lobby.cs new file mode 100644 index 0000000..64f138a --- /dev/null +++ b/Room/Lobby.cs @@ -0,0 +1,39 @@ +using MauMau_Server.Mau; +using MauMau_Server.Websockets; + +namespace MauMau_Server.Room; + +public class Lobby : RoomType +{ + private readonly Room _room; + + public Lobby(Room room) + { + _room = room; + } + + public Lobby(Room room, ConnectionInstance connection) + { + _room = room; + Console.WriteLine(connection.Name + " won the game!"); + } + + public void OnMessage(ConnectionInstance sender, string message) + { + // TODO: Add a way to change game settings + if (sender == _room._host) + { + _room._roomType = new Game(_room, _room.Connections); + } + } + + public void OnConnect(ConnectionInstance connection) + { + var roomMessage = new RoomMessage("LOBBY", MessageType.INFO, _room.Connections, connection.Name + " joined!"); + } + + public void OnDisconnect(ConnectionInstance connection) + { + throw new NotImplementedException(); + } +} \ No newline at end of file diff --git a/Room/MessageDTO.cs b/Room/MessageDTO.cs index 6d0fa3f..bb6ed8f 100644 --- a/Room/MessageDTO.cs +++ b/Room/MessageDTO.cs @@ -1,4 +1,4 @@ -namespace MauMau_Server.Mau; +namespace MauMau_Server.Room; public class MessageDTO { diff --git a/Room/Room.cs b/Room/Room.cs index c6bda50..74d2abb 100644 --- a/Room/Room.cs +++ b/Room/Room.cs @@ -1,75 +1,55 @@ using System.Net.WebSockets; using System.Text.Json; using System.Text.RegularExpressions; -using MauMau_Server.Mau; +using MauMau_Server.Websockets; -namespace MauMau_Server.Websockets; +namespace MauMau_Server.Room; public class Room { private readonly IRoomManager _roomManager; private readonly string _roomId; - private readonly List _connections = new(); - private ConnectionInstance? _host; - private readonly Chat _chat; - private Game? _game; - private RoomState _state = RoomState.LOBBY; + public readonly List Connections = new(); + public ConnectionInstance? _host; + private readonly Chat.Chat _chat; + public RoomType _roomType; public Room(IRoomManager roomManager, string roomId) { _roomManager = roomManager; - _chat = new Chat(this); + _chat = new Chat.Chat(this); + _roomType = new Lobby(this); _roomId = roomId; } public async Task InstantiateConnection(WebSocket socket, string name) { var connection = AddConnection(socket, name); - _game?.AddPlayerToGame(connection); + _roomType.OnConnect(connection); await HandleConnection(connection); } private async Task HandleConnection(ConnectionInstance connection) { - BroadcastState(); var webSocketResponse = await WebsocketManager.ReceiveAsync(connection.Socket); while (!webSocketResponse.Result!.CloseStatus.HasValue) { var message = JsonSerializer.Deserialize(webSocketResponse.SlicedBuffer); - switch (message.Type) + if (message.Type == "CHAT") { - case "GAME": - { - if (_state != RoomState.GAME) break; - var gameInput = JsonSerializer.Deserialize(message.Payload); - _game.HandleAction(connection.ConnectionId, gameInput); - break; - } - case "CHAT": - { - var cleanedMessage = StripHTML(message.Payload); - if (string.IsNullOrWhiteSpace(cleanedMessage)) - { - cleanedMessage = "Mau!"; - }; - _chat.SendChatMessage(connection, cleanedMessage); - break; - } - case "LOBBY": - { - if (connection.ConnectionId == _host?.ConnectionId) - { - ChangeLobbyState(RoomState.GAME); - } - break; - } + var cleanedMessage = StripHTML(message.Payload); + if (string.IsNullOrWhiteSpace(cleanedMessage)) cleanedMessage = "Mau!"; + _chat.SendChatMessage(connection, cleanedMessage); + } + else + { + _roomType.OnMessage(connection, message.Payload); } - BroadcastState(); webSocketResponse = await WebsocketManager.ReceiveAsync(connection.Socket); } WebsocketManager.CloseAsync(connection.Socket, webSocketResponse.Result); - HandleDisconnect(connection.ConnectionId); + HandleDisconnect(connection); } private ConnectionInstance AddConnection(WebSocket socket, string name) @@ -77,108 +57,44 @@ public class Room var connectionId = Guid.NewGuid().ToString(); var connection = new ConnectionInstance(name, connectionId, socket); if (IsEmpty()) _host = connection; - _connections.Add(connection); + Connections.Add(connection); return connection; } - private void RemoveConnection(string socketId) + private void HandleDisconnect(ConnectionInstance connection) { - _connections.RemoveAll(connection => connection.ConnectionId == socketId); - } - - private void BroadcastGameState() - { - if (_game == null) - { - return; - } - foreach (var connection in _connections) - { - var gameState = new GameState(_game, connection.ConnectionId); - var message = new MessageDTO("GAME", JsonSerializer.Serialize(gameState)); - WebsocketManager.SendAsync(connection.Socket, JsonSerializer.Serialize(message)); - } - } - - private void BroadcastState() - { - switch (_state) - { - case RoomState.LOBBY: - var message = new MessageDTO("LOBBY", JsonSerializer.Serialize("a")); - WebsocketManager.BroadcastAsync(GetWebsockets(), JsonSerializer.Serialize(message)); - break; - case RoomState.GAME: - BroadcastGameState(); - break; - default: - // - break; - } - } - - private void ChangeLobbyState(RoomState targetState) - { - switch (targetState) - { - case RoomState.LOBBY: - _state = RoomState.LOBBY; - _game = null; - break; - case RoomState.GAME: - { - _state = RoomState.GAME; - _game = new Game(this); - foreach (var connectionInstance in _connections) - { - _game.AddPlayerToGame(connectionInstance); - } - break; - } - default: - throw new ArgumentOutOfRangeException(nameof(targetState), targetState, null); - } - } - - private void HandleDisconnect(string socketId) - { - RemoveConnection(socketId); - _game?.RemovePlayer(socketId); + Connections.Remove(connection); + _roomType.OnDisconnect(connection); if (IsEmpty()) { _roomManager.RemoveRoom(_roomId); } - else if (socketId == _host.ConnectionId) + else if (connection == _host) { - _host = _connections.First(); + _host = Connections.First(); + } + } + + public void BroadCast(RoomMessage message) + { + foreach (var connection in Connections) + { + connection.SendMessageAsync(JsonSerializer.Serialize(message)); } } public List GetWebsockets() { - return _connections.Select(connection => connection.Socket).ToList(); + return Connections.Select(connection => connection.Socket).ToList(); } public bool IsEmpty() { - return _connections.Count == 0; + return Connections.Count == 0; } private static string StripHTML(string input) { return Regex.Replace(input, "<.*?>", string.Empty); } - - public void EndGame(Player player) - { - var message = new MessageDTO("END", JsonSerializer.Serialize(new PlayerDTO(player))); - WebsocketManager.BroadcastAsync(GetWebsockets(), JsonSerializer.Serialize(message)); - ChangeLobbyState(RoomState.LOBBY); - } -} - -public enum RoomState -{ - LOBBY, - GAME } \ No newline at end of file diff --git a/Room/RoomManager.cs b/Room/RoomManager.cs index 4b9dab2..7735133 100644 --- a/Room/RoomManager.cs +++ b/Room/RoomManager.cs @@ -1,4 +1,4 @@ -namespace MauMau_Server.Websockets; +namespace MauMau_Server.Room; public class RoomManager : IRoomManager { diff --git a/Room/RoomMessage.cs b/Room/RoomMessage.cs new file mode 100644 index 0000000..cf5df13 --- /dev/null +++ b/Room/RoomMessage.cs @@ -0,0 +1,39 @@ +using MauMau_Server.Websockets; + +namespace MauMau_Server.Room; + +public class RoomMessage +{ + public string CurrentRoomType; + public string? MessageType; + public List JoinedPlayers; + public string? Message; + + public RoomMessage(string currentRoomType, IEnumerable joinedPlayers, string? message) + { + CurrentRoomType = currentRoomType; + JoinedPlayers = GetNamesFromConnections(joinedPlayers); + Message = message; + } + + public RoomMessage(string currentRoomType, MessageType messageType, IEnumerable joinedPlayers, string? message) + { + CurrentRoomType = currentRoomType; + MessageType = messageType.ToString(); + JoinedPlayers = GetNamesFromConnections(joinedPlayers); + Message = message; + } + + private static List GetNamesFromConnections(IEnumerable connections) + { + return connections.Select(player => player.Name).ToList(); + } +} + +public enum MessageType +{ + INFO, + SUCCES, + WARNING, + ERROR +} \ No newline at end of file diff --git a/Room/RoomType.cs b/Room/RoomType.cs new file mode 100644 index 0000000..5999a57 --- /dev/null +++ b/Room/RoomType.cs @@ -0,0 +1,38 @@ +using MauMau_Server.Websockets; + +namespace MauMau_Server.Room; + +public abstract class RoomType +{ + protected readonly Room _room; + + protected RoomType(Room room) + { + _room = room; + } + + /** + * + * This method is called when a message is received from a ConnectionInstance + * + * The ConnectionInstance that sent the message + * The message received + */ + public abstract void OnMessage(ConnectionInstance sender, string message); + + /** + * + * This method is called when a new ConnectionInstance is added to the Room. + * + * The ConnectionInstance that was added to the Room + */ + public abstract void OnConnect(ConnectionInstance connection); + + /** + * + * This method is called when a ConnectionInstance is either removed from the Room or the ConnectionInstance's WebSocket is closed. + * + * The ConnectionInstance that was removed from the Room + */ + public abstract void OnDisconnect(ConnectionInstance connection); +} \ No newline at end of file diff --git a/Websockets/ConnectionInstance.cs b/Websockets/ConnectionInstance.cs index 8b1884d..58e034f 100644 --- a/Websockets/ConnectionInstance.cs +++ b/Websockets/ConnectionInstance.cs @@ -1,17 +1,30 @@ using System.Net.WebSockets; +using System.Text; namespace MauMau_Server.Websockets; public class ConnectionInstance { public string Name { get; set; } - public string ConnectionId { get; set; } + public string Id { get; set; } public WebSocket Socket { get; set; } - public ConnectionInstance(string name, string connectionId, WebSocket socket) + public ConnectionInstance(string name, string id, WebSocket socket) { Name = name; - ConnectionId = connectionId; + Id = id; Socket = socket; } + + /** + * + * Sends a message to the client. This method is asynchronous and formats the message to be ready to be sent. + * + */ + public void SendMessageAsync(string message) + { + var bytes = Encoding.Default.GetBytes(message); + var arraySegment = new ArraySegment(bytes); + Socket.SendAsync(arraySegment, WebSocketMessageType.Text, true, CancellationToken.None); + } } \ No newline at end of file -- 2.49.1 From 90c9b0031c29c2de915e041cf60f10998d85cf6d Mon Sep 17 00:00:00 2001 From: DTieman Date: Sun, 5 May 2024 17:08:02 +0200 Subject: [PATCH 2/4] Game seems to work --- Mau/ActionDTO.cs | 6 +- Mau/Deck.cs | 3 + Mau/Game.cs | 126 ++++++++++++++++++++++--------- Mau/GameMessages/ChooseCard.cs | 6 -- Mau/GameMessages/DrawCard.cs | 0 Mau/GameMessages/GameMessage.cs | 14 ---- Mau/GameState.cs | 19 +++-- Mau/Player.cs | 2 +- Room/Chat/Chat.cs | 33 -------- Room/Chat/ChatOutput.cs | 18 ----- Room/Lobby.cs | 28 +++---- Room/Messages/ChatMessage.cs | 13 ++++ Room/Messages/EndMessage.cs | 13 ++++ Room/Messages/JoinMessage.cs | 21 ++++++ Room/Messages/LeaveMessage.cs | 13 ++++ Room/Room.cs | 50 ++++++------ Room/RoomMessage.cs | 39 +++------- Room/RoomType.cs | 2 +- Websockets/ConnectionInstance.cs | 4 +- Websockets/WebsocketManager.cs | 15 ---- 20 files changed, 213 insertions(+), 212 deletions(-) delete mode 100644 Mau/GameMessages/ChooseCard.cs delete mode 100644 Mau/GameMessages/DrawCard.cs delete mode 100644 Mau/GameMessages/GameMessage.cs delete mode 100644 Room/Chat/Chat.cs delete mode 100644 Room/Chat/ChatOutput.cs create mode 100644 Room/Messages/ChatMessage.cs create mode 100644 Room/Messages/EndMessage.cs create mode 100644 Room/Messages/JoinMessage.cs create mode 100644 Room/Messages/LeaveMessage.cs diff --git a/Mau/ActionDTO.cs b/Mau/ActionDTO.cs index 496c375..2b72500 100644 --- a/Mau/ActionDTO.cs +++ b/Mau/ActionDTO.cs @@ -2,12 +2,12 @@ public class ActionDTO { - public string Action { get; set; } + public string Type { get; set; } public string Data { get; set; } - public ActionDTO(string action, string data) + public ActionDTO(string type, string data) { - Action = action; + Type = type; Data = data; } diff --git a/Mau/Deck.cs b/Mau/Deck.cs index 54dff4a..ad83aad 100644 --- a/Mau/Deck.cs +++ b/Mau/Deck.cs @@ -4,6 +4,7 @@ public class Deck { private List _unusedDeck = new(); private List _usedDeck = new(); + public Card CurrentCard; /** * @@ -14,6 +15,8 @@ public class Deck { CreateSet(); ShuffleDeck(); + CurrentCard = DrawCard(); + _usedDeck.Add(CurrentCard); } /** diff --git a/Mau/Game.cs b/Mau/Game.cs index e2ff774..d22c0cf 100644 --- a/Mau/Game.cs +++ b/Mau/Game.cs @@ -2,6 +2,7 @@ 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; @@ -9,51 +10,55 @@ namespace MauMau_Server.Mau; public class Game : RoomType { private readonly Deck _deck = new(); - public Card CurrentCard; private readonly TurnManager _turnManager = new(); public Game(Room.Room room, IEnumerable connections) : base(room) { - CurrentCard = _deck.DrawCard(); - _deck.AddCardToUsedDeck(CurrentCard); - + // 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, string message) + 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; - // Deserialize the message - var gameMessage = JsonConvert.DeserializeObject(message); - // Based on the message intent, handle the message - switch (gameMessage.Intent) + switch (message.Type) { - case GameIntent.CHOOSE: - Choose(player, gameMessage.Data); + case "CHOOSE": + Choose(player, message.Data); + break; + case "PLAY": + Play(player, message.Data); break; - case GameIntent.DRAW: default: - Draw(player, gameMessage.Data); - break; - case GameIntent.PLAY: - Play(player, gameMessage.Data); + Draw(player); break; } } @@ -63,10 +68,18 @@ public class Game : RoomType */ 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(); } /** @@ -74,16 +87,29 @@ public class Game : RoomType */ 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.ChangeTurn(); } + // Remove the player from the game _turnManager.Players.Remove(player); + + // Broadcast new game state + SendGameState(); } /** @@ -102,10 +128,10 @@ public class Game : RoomType } // Does the current card require the next player to draw cards? - var isCardGivingCard = CurrentCard.CardType == CardType.JOKER || CurrentCard.CardValue == CardValue.TWO; + 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(CurrentCard); + 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; @@ -113,12 +139,15 @@ public class Game : RoomType ? _turnManager.GetNextPlayer(2) : _turnManager.GetNextPlayer(); - // TODO: Not make it a jack, as it would not work when a Joker is played + // TODO: Not make it a jack, as it would not work when a Joker was played // Set the new current card - CurrentCard = new Card(cardType, CardValue.JACK); + _deck.CurrentCard = new Card(cardType, CardValue.JACK); // Change the turns _turnManager.ChangeTurn(nextPlayer); + + // Broadcast new game state + SendGameState(); } /** @@ -132,21 +161,22 @@ public class Game : RoomType * The player that drew a card * A string that can be serialized to a drawcard instance */ - private void Draw(Player player, string data) + private void Draw(Player player) { - // TODO: data will contain the amount of cards to draw in the future, for now, just draw 1 card - // Draw a card from the deck var drawnCard = _deck.DrawCard(); // Give the card to the player player.GiveCard(drawnCard); - // If the player can play the card, do not change the player - if (drawnCard.CanBePlayedOn(CurrentCard)) return; - - // Change the turn to the next player - _turnManager.ChangeTurn(); + // Change the player if the drawn card cannot be played + if (!drawnCard.CanBePlayedOn(_deck.CurrentCard)) + { + _turnManager.ChangeTurn(); + } + + // Broadcast new game state + SendGameState(); } /** @@ -181,7 +211,7 @@ public class Game : RoomType // Check if the played card is compatible with the current card // If not, ignore the play - if (!playerCard.CanBePlayedOn(CurrentCard)) + if (!playerCard.CanBePlayedOn(_deck.CurrentCard)) { return; } @@ -193,7 +223,7 @@ public class Game : RoomType _deck.AddCardToUsedDeck(playerCard); // Set the new current card - CurrentCard = playerCard; + _deck.CurrentCard = playerCard; if (player.Hand.Count == 0) { @@ -201,9 +231,19 @@ public class Game : RoomType 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) @@ -250,13 +290,31 @@ public class Game : RoomType } } - private void EndGame(Player winner) + /** + * + * 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() { - _room._roomType = new Lobby(_room, winner.Connection); + 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))); + } } - public Player? GetPlayer(string playerId) + /** + * + * 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) { - return _turnManager.Players.FirstOrDefault(p => p.IsMe(playerId)); + var winMessage = new EndMessage(winner.Connection.Id, winner.Connection.Name); + _room.BroadCast(new RoomMessage("END", winMessage)); + _room.RoomType = new Lobby(_room); } } \ No newline at end of file diff --git a/Mau/GameMessages/ChooseCard.cs b/Mau/GameMessages/ChooseCard.cs deleted file mode 100644 index 9460ac2..0000000 --- a/Mau/GameMessages/ChooseCard.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace MauMau_Server.Mau.GameMessages; - -public class ChooseCard -{ - -} \ No newline at end of file diff --git a/Mau/GameMessages/DrawCard.cs b/Mau/GameMessages/DrawCard.cs deleted file mode 100644 index e69de29..0000000 diff --git a/Mau/GameMessages/GameMessage.cs b/Mau/GameMessages/GameMessage.cs deleted file mode 100644 index bdb5643..0000000 --- a/Mau/GameMessages/GameMessage.cs +++ /dev/null @@ -1,14 +0,0 @@ -namespace MauMau_Server.Mau.GameMessages; - -public class GameMessage -{ - public GameIntent Intent { get; set; } - public string Data { get; set; } -} - -public enum GameIntent -{ - CHOOSE, - DRAW, - PLAY, -} \ No newline at end of file diff --git a/Mau/GameState.cs b/Mau/GameState.cs index e690327..d544afb 100644 --- a/Mau/GameState.cs +++ b/Mau/GameState.cs @@ -3,29 +3,28 @@ public class GameState { public PlayerDTO Me { get; set; } - public string CurrentState { get; set; } + public string MyState { get; set; } public List Hand { get; set; } = new(); public string CurrentCard { get; set; } public PlayerDTO CurrentPlayer { get; set; } public List Players { get; set; } = new(); - public GameState(Game game, string playerId) + public GameState(Player me, Card currentCard, Player currentPlayer, List others) { - var p = game.GetPlayer(playerId); - Me = new PlayerDTO(game.GetPlayer(playerId)); - CurrentState = p.State.ToString(); - foreach (var card in p.Hand) + Me = new PlayerDTO(me); + MyState = me.State.ToString(); + foreach (var card in me.Hand) { Hand.Add(card.ToString()); } - foreach (var player in game.Players) + foreach (var player in others) { Players.Add(new PlayerDTO(player)); } - CurrentCard = game.CurrentCard.ToString(); - CurrentPlayer = new PlayerDTO(game.CurrentPlayer); + CurrentCard = currentCard.ToString(); + CurrentPlayer = new PlayerDTO(currentPlayer); } } @@ -38,7 +37,7 @@ public class PlayerDTO public PlayerDTO(Player player) { Name = player.Connection.Name; - Id = player.Connection.Id; + Id = player.Connection.Id.ToString(); CardsLeft = player.Hand.Count; } } \ No newline at end of file diff --git a/Mau/Player.cs b/Mau/Player.cs index de169f6..53074c2 100644 --- a/Mau/Player.cs +++ b/Mau/Player.cs @@ -28,7 +28,7 @@ public class Player return Hand.FirstOrDefault(handCard => handCard.IsSameCard(card)); } - public bool IsMe(string playerId) => Connection.Id == playerId; + public bool IsMe(Guid playerId) => Connection.Id == playerId; public bool CanPlayCard(Card currentCard) { diff --git a/Room/Chat/Chat.cs b/Room/Chat/Chat.cs deleted file mode 100644 index 4052e98..0000000 --- a/Room/Chat/Chat.cs +++ /dev/null @@ -1,33 +0,0 @@ -using System.Text.Json; -using MauMau_Server.Websockets; - -namespace MauMau_Server.Room.Chat; - -public class Chat -{ - private readonly Room _room; - - public Chat(Room room) - { - _room = room; - } - - public void SendChatMessage(ConnectionInstance connection, string message) - { - var chatMessage = new ChatMessage(connection.Name, message); - var formattedMessage = new MessageDTO("CHAT", JsonSerializer.Serialize(chatMessage)); - WebsocketManager.BroadcastAsync(_room.GetWebsockets(), JsonSerializer.Serialize(formattedMessage)); - } -} - -public class ChatMessage -{ - public string Sender { get; set; } - public string Message { get; set; } - - public ChatMessage(string sender, string message) - { - Sender = sender; - Message = message; - } -} \ No newline at end of file diff --git a/Room/Chat/ChatOutput.cs b/Room/Chat/ChatOutput.cs deleted file mode 100644 index b7cd9c9..0000000 --- a/Room/Chat/ChatOutput.cs +++ /dev/null @@ -1,18 +0,0 @@ -namespace MauMau_Server.Mau; - -public class ChatOutput -{ - public string PlayerName { get; set; } - public string Message { get; set; } - - public ChatOutput(string playerName, string message) - { - PlayerName = playerName; - Message = message; - } - - public ChatOutput() - { - - } -} \ No newline at end of file diff --git a/Room/Lobby.cs b/Room/Lobby.cs index 64f138a..9559eac 100644 --- a/Room/Lobby.cs +++ b/Room/Lobby.cs @@ -1,39 +1,33 @@ using MauMau_Server.Mau; +using MauMau_Server.Room.Messages; using MauMau_Server.Websockets; namespace MauMau_Server.Room; public class Lobby : RoomType { - private readonly Room _room; - - public Lobby(Room room) + public Lobby(Room room) : base(room) { - _room = room; - } - - public Lobby(Room room, ConnectionInstance connection) - { - _room = room; - Console.WriteLine(connection.Name + " won the game!"); } - public void OnMessage(ConnectionInstance sender, string message) + public override void OnMessage(ConnectionInstance sender, RoomMessage message) { // TODO: Add a way to change game settings - if (sender == _room._host) + if (message.Type == "LOBBY" && sender == _room.Host) { - _room._roomType = new Game(_room, _room.Connections); + _room.RoomType = new Game(_room, _room.Connections); } } - public void OnConnect(ConnectionInstance connection) + public override void OnConnect(ConnectionInstance connection) { - var roomMessage = new RoomMessage("LOBBY", MessageType.INFO, _room.Connections, connection.Name + " joined!"); + var joinMessage = new JoinMessage(connection.Id, connection.Name + " has joined the room."); + _room.BroadCast(new RoomMessage("JOIN", joinMessage)); } - public void OnDisconnect(ConnectionInstance connection) + public override void OnDisconnect(ConnectionInstance connection) { - throw new NotImplementedException(); + var leaveMessage = new LeaveMessage(connection.Id, connection.Name + " has left the room."); + _room.BroadCast(new RoomMessage("LEAVE", leaveMessage)); } } \ No newline at end of file diff --git a/Room/Messages/ChatMessage.cs b/Room/Messages/ChatMessage.cs new file mode 100644 index 0000000..56b85dc --- /dev/null +++ b/Room/Messages/ChatMessage.cs @@ -0,0 +1,13 @@ +namespace MauMau_Server.Room.Messages; + +public class ChatMessage +{ + public string Sender { get; set; } + public string Message { get; set; } + + public ChatMessage(string sender, string message) + { + Sender = sender; + Message = message; + } +} \ No newline at end of file diff --git a/Room/Messages/EndMessage.cs b/Room/Messages/EndMessage.cs new file mode 100644 index 0000000..47e88c4 --- /dev/null +++ b/Room/Messages/EndMessage.cs @@ -0,0 +1,13 @@ +namespace MauMau_Server.Room.Messages; + +public class EndMessage +{ + public Guid WinnerId { get; set; } + public string WinnerName { get; set; } + + public EndMessage(Guid winnerId, string winnerName) + { + WinnerId = winnerId; + WinnerName = winnerName; + } +} \ No newline at end of file diff --git a/Room/Messages/JoinMessage.cs b/Room/Messages/JoinMessage.cs new file mode 100644 index 0000000..b2f492e --- /dev/null +++ b/Room/Messages/JoinMessage.cs @@ -0,0 +1,21 @@ +namespace MauMau_Server.Room.Messages; + +public class JoinMessage +{ + public Guid Id { get; set; } + public string ChatMessage { get; set; } + public string? Data { get; set; } + + public JoinMessage(Guid id, string chatMessage, string data) + { + Id = id; + ChatMessage = chatMessage; + Data = data; + } + + public JoinMessage(Guid id, string chatMessage) + { + Id = id; + ChatMessage = chatMessage; + } +} \ No newline at end of file diff --git a/Room/Messages/LeaveMessage.cs b/Room/Messages/LeaveMessage.cs new file mode 100644 index 0000000..32bdd0f --- /dev/null +++ b/Room/Messages/LeaveMessage.cs @@ -0,0 +1,13 @@ +namespace MauMau_Server.Room.Messages; + +public class LeaveMessage +{ + public Guid Id { get; set; } + public string ChatMessage { get; set; } + + public LeaveMessage(Guid id, string chatMessage) + { + Id = id; + ChatMessage = chatMessage; + } +} \ No newline at end of file diff --git a/Room/Room.cs b/Room/Room.cs index 74d2abb..b5d6da3 100644 --- a/Room/Room.cs +++ b/Room/Room.cs @@ -1,6 +1,7 @@ using System.Net.WebSockets; using System.Text.Json; using System.Text.RegularExpressions; +using MauMau_Server.Room.Messages; using MauMau_Server.Websockets; namespace MauMau_Server.Room; @@ -10,22 +11,24 @@ public class Room private readonly IRoomManager _roomManager; private readonly string _roomId; public readonly List Connections = new(); - public ConnectionInstance? _host; - private readonly Chat.Chat _chat; - public RoomType _roomType; + public ConnectionInstance? Host; + public RoomType RoomType; public Room(IRoomManager roomManager, string roomId) { _roomManager = roomManager; - _chat = new Chat.Chat(this); - _roomType = new Lobby(this); + RoomType = new Lobby(this); _roomId = roomId; } public async Task InstantiateConnection(WebSocket socket, string name) { - var connection = AddConnection(socket, name); - _roomType.OnConnect(connection); + var connectionId = Guid.NewGuid(); + var connection = new ConnectionInstance(name, connectionId, socket); + if (IsEmpty()) Host = connection; + Connections.Add(connection); + + RoomType.OnConnect(connection); await HandleConnection(connection); } @@ -34,16 +37,17 @@ public class Room var webSocketResponse = await WebsocketManager.ReceiveAsync(connection.Socket); while (!webSocketResponse.Result!.CloseStatus.HasValue) { - var message = JsonSerializer.Deserialize(webSocketResponse.SlicedBuffer); + var message = JsonSerializer.Deserialize>(webSocketResponse.SlicedBuffer); if (message.Type == "CHAT") { - var cleanedMessage = StripHTML(message.Payload); + var cleanedMessage = StripHTML(message.Data); if (string.IsNullOrWhiteSpace(cleanedMessage)) cleanedMessage = "Mau!"; - _chat.SendChatMessage(connection, cleanedMessage); + var chatMessage = new ChatMessage(connection.Name, cleanedMessage); + BroadCast(new RoomMessage("CHAT", chatMessage)); } else { - _roomType.OnMessage(connection, message.Payload); + RoomType.OnMessage(connection, message); } webSocketResponse = await WebsocketManager.ReceiveAsync(connection.Socket); @@ -52,30 +56,23 @@ public class Room HandleDisconnect(connection); } - private ConnectionInstance AddConnection(WebSocket socket, string name) - { - var connectionId = Guid.NewGuid().ToString(); - var connection = new ConnectionInstance(name, connectionId, socket); - if (IsEmpty()) _host = connection; - Connections.Add(connection); - return connection; - } - private void HandleDisconnect(ConnectionInstance connection) { Connections.Remove(connection); - _roomType.OnDisconnect(connection); + RoomType.OnDisconnect(connection); if (IsEmpty()) { _roomManager.RemoveRoom(_roomId); + return; } - else if (connection == _host) + + if (connection == Host) { - _host = Connections.First(); + Host = Connections.First(); } } - public void BroadCast(RoomMessage message) + public void BroadCast(RoomMessage message) { foreach (var connection in Connections) { @@ -83,11 +80,6 @@ public class Room } } - public List GetWebsockets() - { - return Connections.Select(connection => connection.Socket).ToList(); - } - public bool IsEmpty() { return Connections.Count == 0; diff --git a/Room/RoomMessage.cs b/Room/RoomMessage.cs index cf5df13..f0c9309 100644 --- a/Room/RoomMessage.cs +++ b/Room/RoomMessage.cs @@ -1,39 +1,20 @@ -using MauMau_Server.Websockets; +using Newtonsoft.Json; namespace MauMau_Server.Room; -public class RoomMessage +public class RoomMessage { - public string CurrentRoomType; - public string? MessageType; - public List JoinedPlayers; - public string? Message; - - public RoomMessage(string currentRoomType, IEnumerable joinedPlayers, string? message) + public string Type { get; set; } + public string Data { get; set; } + + public RoomMessage(string type, T data) { - CurrentRoomType = currentRoomType; - JoinedPlayers = GetNamesFromConnections(joinedPlayers); - Message = message; + Type = type; + Data = JsonConvert.SerializeObject(data); } - public RoomMessage(string currentRoomType, MessageType messageType, IEnumerable joinedPlayers, string? message) + public RoomMessage() { - CurrentRoomType = currentRoomType; - MessageType = messageType.ToString(); - JoinedPlayers = GetNamesFromConnections(joinedPlayers); - Message = message; + } - - private static List GetNamesFromConnections(IEnumerable connections) - { - return connections.Select(player => player.Name).ToList(); - } -} - -public enum MessageType -{ - INFO, - SUCCES, - WARNING, - ERROR } \ No newline at end of file diff --git a/Room/RoomType.cs b/Room/RoomType.cs index 5999a57..c219066 100644 --- a/Room/RoomType.cs +++ b/Room/RoomType.cs @@ -18,7 +18,7 @@ public abstract class RoomType * The ConnectionInstance that sent the message * The message received */ - public abstract void OnMessage(ConnectionInstance sender, string message); + public abstract void OnMessage(ConnectionInstance sender, RoomMessage message); /** * diff --git a/Websockets/ConnectionInstance.cs b/Websockets/ConnectionInstance.cs index 58e034f..9e4fbc1 100644 --- a/Websockets/ConnectionInstance.cs +++ b/Websockets/ConnectionInstance.cs @@ -5,11 +5,11 @@ namespace MauMau_Server.Websockets; public class ConnectionInstance { + public Guid Id { get; set; } public string Name { get; set; } - public string Id { get; set; } public WebSocket Socket { get; set; } - public ConnectionInstance(string name, string id, WebSocket socket) + public ConnectionInstance(string name, Guid id, WebSocket socket) { Name = name; Id = id; diff --git a/Websockets/WebsocketManager.cs b/Websockets/WebsocketManager.cs index 8941c17..487a16c 100644 --- a/Websockets/WebsocketManager.cs +++ b/Websockets/WebsocketManager.cs @@ -5,21 +5,6 @@ namespace MauMau_Server.Websockets; public static class WebsocketManager { - public static void SendAsync(WebSocket socket, string message) - { - var bytes = Encoding.Default.GetBytes(message); - var arraySegment = new ArraySegment(bytes); - socket.SendAsync(arraySegment, WebSocketMessageType.Text, true, CancellationToken.None); - } - - public static void BroadcastAsync(List sockets, string message) - { - foreach (var socket in sockets) - { - SendAsync(socket, message); - } - } - public static async Task ReceiveAsync(WebSocket webSocket) { var buffer = new byte[4096]; -- 2.49.1 From f5c2b937b0c8c98dfeb7a769a7cfcbbc080c193a Mon Sep 17 00:00:00 2001 From: DTieman Date: Sat, 18 May 2024 14:16:30 +0200 Subject: [PATCH 3/4] Some renames --- Mau/Game.cs | 14 +++++++------- Mau/Managers/TurnManager.cs | 10 +++++----- Mau/PlayerState.cs | 3 ++- 3 files changed, 14 insertions(+), 13 deletions(-) diff --git a/Mau/Game.cs b/Mau/Game.cs index d22c0cf..fb2f41a 100644 --- a/Mau/Game.cs +++ b/Mau/Game.cs @@ -102,7 +102,7 @@ public class Game : RoomType // Change the turn if the player that left was the current player if (player == _turnManager.CurrentPlayer) { - _turnManager.ChangeTurn(); + _turnManager.ChangeTurnTo(); } // Remove the player from the game @@ -144,7 +144,7 @@ public class Game : RoomType _deck.CurrentCard = new Card(cardType, CardValue.JACK); // Change the turns - _turnManager.ChangeTurn(nextPlayer); + _turnManager.ChangeTurnTo(nextPlayer); // Broadcast new game state SendGameState(); @@ -172,7 +172,7 @@ public class Game : RoomType // Change the player if the drawn card cannot be played if (!drawnCard.CanBePlayedOn(_deck.CurrentCard)) { - _turnManager.ChangeTurn(); + _turnManager.ChangeTurnTo(); } // Broadcast new game state @@ -258,20 +258,20 @@ public class Game : RoomType case CardValue.TWO: { _turnManager.GetNextPlayer().GiveCards(_deck.DrawCards(2)); - _turnManager.ChangeTurn(_turnManager.GetNextPlayer(2)); + _turnManager.ChangeTurnTo(_turnManager.GetNextPlayer(2)); break; } case CardValue.SEVEN: case CardValue.KING: break; case CardValue.EIGHT: - _turnManager.ChangeTurn(_turnManager.GetNextPlayer(2)); + _turnManager.ChangeTurnTo(_turnManager.GetNextPlayer(2)); break; case CardValue.ACE: if (_turnManager.Players.Count > 2) { _turnManager.ChangeDirection(); - _turnManager.ChangeTurn(); + _turnManager.ChangeTurnTo(); } break; case CardValue.JACK: @@ -285,7 +285,7 @@ public class Game : RoomType case CardValue.TEN: case CardValue.QUEEN: default: - _turnManager.ChangeTurn(); + _turnManager.ChangeTurnTo(); break; } } diff --git a/Mau/Managers/TurnManager.cs b/Mau/Managers/TurnManager.cs index 14d4a23..ad03a57 100644 --- a/Mau/Managers/TurnManager.cs +++ b/Mau/Managers/TurnManager.cs @@ -39,14 +39,14 @@ public class TurnManager * Change the turn to another player. * If no player is given, the next player in the current direction is chosen. * - * The player to change the turn to. Defaults to the next player in the current direction. + * The player to change the turn to. Defaults to the next player in the current direction. */ - public void ChangeTurn(Player? nextPlayer = null) + public void ChangeTurnTo(Player? player = null, PlayerState nextPlayerState = PlayerState.TURN) { - var player = nextPlayer ?? GetNextPlayer(); + var nextPlayer = player ?? GetNextPlayer(); CurrentPlayer.State = PlayerState.WAIT; - player.State = PlayerState.TURN; - CurrentPlayer = player; + nextPlayer.State = nextPlayerState; + CurrentPlayer = nextPlayer; } /** diff --git a/Mau/PlayerState.cs b/Mau/PlayerState.cs index a52669c..aa015f1 100644 --- a/Mau/PlayerState.cs +++ b/Mau/PlayerState.cs @@ -4,5 +4,6 @@ public enum PlayerState { TURN, CHOOSE, - WAIT + WAIT, + POST_DRAW } \ No newline at end of file -- 2.49.1 From 3b765841b786c4fcff993aa5db251b7005ed73b6 Mon Sep 17 00:00:00 2001 From: DTieman Date: Sun, 19 May 2024 23:49:25 +0200 Subject: [PATCH 4/4] Better messaging and better rule implementation --- Mau/Deck.cs | 4 + Mau/Game.cs | 128 ++++++++++++++++++++++--------- Mau/GameState.cs | 4 +- Room/Lobby.cs | 4 +- Room/Messages/JoinMessage.cs | 22 ++---- Room/Messages/LeaveMessage.cs | 12 +-- Room/Room.cs | 66 ++++++++++++---- Websockets/ConnectionInstance.cs | 2 + Websockets/WebsocketManager.cs | 5 +- 9 files changed, 174 insertions(+), 73 deletions(-) diff --git a/Mau/Deck.cs b/Mau/Deck.cs index ad83aad..e5129e8 100644 --- a/Mau/Deck.cs +++ b/Mau/Deck.cs @@ -17,6 +17,10 @@ public class Deck ShuffleDeck(); CurrentCard = DrawCard(); _usedDeck.Add(CurrentCard); + + if (CurrentCard.CardType != CardType.JOKER) return; + CurrentCard = DrawCard(); + _usedDeck.Add(CurrentCard); } /** diff --git a/Mau/Game.cs b/Mau/Game.cs index fb2f41a..8bd9804 100644 --- a/Mau/Game.cs +++ b/Mau/Game.cs @@ -11,6 +11,8 @@ 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) { @@ -69,7 +71,7 @@ public class Game : RoomType public override void OnConnect(ConnectionInstance connection) { // Broadcast that a player joined - var joinMessage = new JoinMessage(connection.Id, connection.Name + " has joined the room."); + 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 @@ -88,7 +90,7 @@ public class Game : RoomType public override void OnDisconnect(ConnectionInstance connection) { // Broadcast that the player left - var leaveMessage = new LeaveMessage(connection.Id, connection.Name + " has left the room."); + var leaveMessage = new LeaveMessage(connection); _room.BroadCast(new RoomMessage("LEAVE", leaveMessage)); // Get the player that left @@ -127,24 +129,11 @@ public class Game : RoomType 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); + // Set the next card type that is allowed to be played + NextAllowedCardType = cardType; // Change the turns - _turnManager.ChangeTurnTo(nextPlayer); + _turnManager.ChangeTurnTo(); // Broadcast new game state SendGameState(); @@ -157,23 +146,43 @@ public class Game : RoomType * 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) { - // 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)) + // 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(); @@ -208,14 +217,34 @@ public class Game : RoomType { return; } - - // Check if the played card is compatible with the current card - // If not, ignore the play - if (!playerCard.CanBePlayedOn(_deck.CurrentCard)) + + // Check if there is a specific card type that is allowed to be played + if (NextAllowedCardType != null) { - return; + // 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); @@ -251,14 +280,14 @@ public class Game : RoomType case CardValue.RED: case CardValue.BLACK: { - _turnManager.GetNextPlayer().GiveCards(_deck.DrawCards(5)); + MauCardBuffer.Add(card); _turnManager.CurrentPlayer.State = PlayerState.CHOOSE; break; } case CardValue.TWO: { - _turnManager.GetNextPlayer().GiveCards(_deck.DrawCards(2)); - _turnManager.ChangeTurnTo(_turnManager.GetNextPlayer(2)); + MauCardBuffer.Add(card); + _turnManager.ChangeTurnTo(); break; } case CardValue.SEVEN: @@ -290,6 +319,33 @@ public class Game : RoomType } } + /** + * + * 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. @@ -300,7 +356,7 @@ public class Game : RoomType { foreach (var player in _turnManager.Players) { - var gameState = new GameState(player, _deck.CurrentCard, _turnManager.CurrentPlayer, _turnManager.Players); + var gameState = new GameState(player, _deck.CurrentCard, NextAllowedCardType, _turnManager.CurrentPlayer, _turnManager.Players); player.Connection.SendMessageAsync(JsonConvert.SerializeObject(new RoomMessage("GAME", gameState))); } } diff --git a/Mau/GameState.cs b/Mau/GameState.cs index d544afb..bf113db 100644 --- a/Mau/GameState.cs +++ b/Mau/GameState.cs @@ -6,10 +6,11 @@ public class GameState public string MyState { get; set; } public List Hand { get; set; } = new(); public string CurrentCard { get; set; } + public string? NextAllowedCardType { get; set; } public PlayerDTO CurrentPlayer { get; set; } public List Players { get; set; } = new(); - public GameState(Player me, Card currentCard, Player currentPlayer, List others) + public GameState(Player me, Card currentCard, CardType? nextAllowedCardType, Player currentPlayer, List others) { Me = new PlayerDTO(me); MyState = me.State.ToString(); @@ -23,6 +24,7 @@ public class GameState Players.Add(new PlayerDTO(player)); } + NextAllowedCardType = nextAllowedCardType?.ToString(); CurrentCard = currentCard.ToString(); CurrentPlayer = new PlayerDTO(currentPlayer); } diff --git a/Room/Lobby.cs b/Room/Lobby.cs index 9559eac..cd8d6ac 100644 --- a/Room/Lobby.cs +++ b/Room/Lobby.cs @@ -21,13 +21,13 @@ public class Lobby : RoomType public override void OnConnect(ConnectionInstance connection) { - var joinMessage = new JoinMessage(connection.Id, connection.Name + " has joined the room."); + var joinMessage = new JoinMessage(_room.Connections, connection); _room.BroadCast(new RoomMessage("JOIN", joinMessage)); } public override void OnDisconnect(ConnectionInstance connection) { - var leaveMessage = new LeaveMessage(connection.Id, connection.Name + " has left the room."); + var leaveMessage = new LeaveMessage(connection); _room.BroadCast(new RoomMessage("LEAVE", leaveMessage)); } } \ No newline at end of file diff --git a/Room/Messages/JoinMessage.cs b/Room/Messages/JoinMessage.cs index b2f492e..b838a8e 100644 --- a/Room/Messages/JoinMessage.cs +++ b/Room/Messages/JoinMessage.cs @@ -1,21 +1,15 @@ -namespace MauMau_Server.Room.Messages; +using MauMau_Server.Websockets; + +namespace MauMau_Server.Room.Messages; public class JoinMessage { - public Guid Id { get; set; } - public string ChatMessage { get; set; } - public string? Data { get; set; } + public List Connections { get; set; } + public ConnectionInstance NewConnection { get; set; } - public JoinMessage(Guid id, string chatMessage, string data) + public JoinMessage(List connections, ConnectionInstance newConnection) { - Id = id; - ChatMessage = chatMessage; - Data = data; - } - - public JoinMessage(Guid id, string chatMessage) - { - Id = id; - ChatMessage = chatMessage; + Connections = connections; + NewConnection = newConnection; } } \ No newline at end of file diff --git a/Room/Messages/LeaveMessage.cs b/Room/Messages/LeaveMessage.cs index 32bdd0f..9b181e1 100644 --- a/Room/Messages/LeaveMessage.cs +++ b/Room/Messages/LeaveMessage.cs @@ -1,13 +1,13 @@ -namespace MauMau_Server.Room.Messages; +using MauMau_Server.Websockets; + +namespace MauMau_Server.Room.Messages; public class LeaveMessage { - public Guid Id { get; set; } - public string ChatMessage { get; set; } + public ConnectionInstance Connection { get; set; } - public LeaveMessage(Guid id, string chatMessage) + public LeaveMessage(ConnectionInstance connection) { - Id = id; - ChatMessage = chatMessage; + Connection = connection; } } \ No newline at end of file diff --git a/Room/Room.cs b/Room/Room.cs index b5d6da3..2b5f338 100644 --- a/Room/Room.cs +++ b/Room/Room.cs @@ -3,6 +3,7 @@ using System.Text.Json; using System.Text.RegularExpressions; using MauMau_Server.Room.Messages; using MauMau_Server.Websockets; +using Microsoft.AspNet.SignalR.Messaging; namespace MauMau_Server.Room; @@ -24,10 +25,16 @@ public class Room public async Task InstantiateConnection(WebSocket socket, string name) { var connectionId = Guid.NewGuid(); - var connection = new ConnectionInstance(name, connectionId, socket); + + // If the name is empty, set it to "Mau" + the first part of the connection ID, otherwise strip potential HTML from the name and use it + var validatedName = !string.IsNullOrWhiteSpace(name) + ? StripHTML(name) + : "Mau" + connectionId.ToString().Split('-')[0]; + + var connection = new ConnectionInstance(validatedName, connectionId, socket); if (IsEmpty()) Host = connection; Connections.Add(connection); - + RoomType.OnConnect(connection); await HandleConnection(connection); } @@ -38,27 +45,60 @@ public class Room while (!webSocketResponse.Result!.CloseStatus.HasValue) { var message = JsonSerializer.Deserialize>(webSocketResponse.SlicedBuffer); - if (message.Type == "CHAT") + switch (message.Type) { - var cleanedMessage = StripHTML(message.Data); - if (string.IsNullOrWhiteSpace(cleanedMessage)) cleanedMessage = "Mau!"; - var chatMessage = new ChatMessage(connection.Name, cleanedMessage); - BroadCast(new RoomMessage("CHAT", chatMessage)); - } - else - { - RoomType.OnMessage(connection, message); + case "CHAT": + HandleChatMessage(connection, message.Data); + break; + case "KICK": + await HandleKick(Guid.Parse(message.Data)); + break; + default: + RoomType.OnMessage(connection, message); + break; } webSocketResponse = await WebsocketManager.ReceiveAsync(connection.Socket); } + WebsocketManager.CloseAsync(connection.Socket, webSocketResponse.Result); HandleDisconnect(connection); } + private void HandleChatMessage(ConnectionInstance sender, string chatMessage) + { + // Remove HTML from chat message to prevent HTML injection + var cleanedMessage = StripHTML(chatMessage); + + // If the message is empty, set it to "Mau!" + if (string.IsNullOrWhiteSpace(cleanedMessage)) cleanedMessage = "Mau!"; + + // Create a new chat message object with the sender and the message + var envelope = new ChatMessage(sender.Name, cleanedMessage); + + // Broadcast the chat message to all connections in the room + BroadCast(new RoomMessage("CHAT", envelope)); + } + + private async Task HandleKick(Guid connectionId) + { + // Search for the connection with the given ID + var connection = Connections.FirstOrDefault(connection => connection.Id == connectionId); + + // If the connection could not be found, return + if (connection == null) return; + + // Handle the disconnect of the connection + HandleDisconnect(connection); + + // Close the connection with the reason "You have been kicked" + await connection.Socket.CloseAsync(WebSocketCloseStatus.NormalClosure, "You have been kicked", + CancellationToken.None); + } + private void HandleDisconnect(ConnectionInstance connection) { - Connections.Remove(connection); + Connections.Remove(connection); RoomType.OnDisconnect(connection); if (IsEmpty()) { @@ -84,7 +124,7 @@ public class Room { return Connections.Count == 0; } - + private static string StripHTML(string input) { return Regex.Replace(input, "<.*?>", string.Empty); diff --git a/Websockets/ConnectionInstance.cs b/Websockets/ConnectionInstance.cs index 9e4fbc1..9a5c58c 100644 --- a/Websockets/ConnectionInstance.cs +++ b/Websockets/ConnectionInstance.cs @@ -1,5 +1,6 @@ using System.Net.WebSockets; using System.Text; +using Newtonsoft.Json; namespace MauMau_Server.Websockets; @@ -7,6 +8,7 @@ public class ConnectionInstance { public Guid Id { get; set; } public string Name { get; set; } + [JsonIgnore] public WebSocket Socket { get; set; } public ConnectionInstance(string name, Guid id, WebSocket socket) diff --git a/Websockets/WebsocketManager.cs b/Websockets/WebsocketManager.cs index 487a16c..6fefe05 100644 --- a/Websockets/WebsocketManager.cs +++ b/Websockets/WebsocketManager.cs @@ -16,6 +16,9 @@ public static class WebsocketManager public static async void CloseAsync(WebSocket webSocket, WebSocketReceiveResult result) { - await webSocket.CloseAsync(result.CloseStatus.Value, result.CloseStatusDescription, CancellationToken.None); + if (webSocket.State is WebSocketState.Open or WebSocketState.CloseReceived or WebSocketState.CloseSent) + { + await webSocket.CloseAsync(result.CloseStatus.Value, result.CloseStatusDescription, CancellationToken.None); + } } } \ No newline at end of file -- 2.49.1