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];