From 4f85f6f793e3d07bba8b26a26d4d6a986206188d Mon Sep 17 00:00:00 2001 From: DTieman Date: Thu, 16 Mar 2023 15:20:28 +0100 Subject: [PATCH 1/4] 1st json over websocket test --- Controllers/MauController.cs | 21 +++++++++++++++ Mau/Card.cs | 51 ++++++++++++++++++++++++++++++++++++ Mau/CardDTO.cs | 30 +++++++++++++++++++++ Mau/Deck.cs | 40 ++++++++++++++++++++++++++++ Mau/HandDTO.cs | 19 ++++++++++++++ Websockets/Room.cs | 24 +++++++++++++---- mau.json | 1 + 7 files changed, 181 insertions(+), 5 deletions(-) create mode 100644 Controllers/MauController.cs create mode 100644 Mau/Card.cs create mode 100644 Mau/CardDTO.cs create mode 100644 Mau/Deck.cs create mode 100644 Mau/HandDTO.cs create mode 100644 mau.json diff --git a/Controllers/MauController.cs b/Controllers/MauController.cs new file mode 100644 index 0000000..acfc789 --- /dev/null +++ b/Controllers/MauController.cs @@ -0,0 +1,21 @@ +using MauMau_Server.Mau; +using Microsoft.AspNetCore.Mvc; + +namespace MauMau_Server.Controllers; + +[ApiController] +[Route("[controller]")] +public class DeckController : ControllerBase +{ + [HttpGet("deck")] + public IActionResult GetDeck() + { + return Ok(new Deck().GetUnusedDeck().Select(card => card.ToString()).ToList()); + } + + [HttpGet("hand")] + public IActionResult GetHand() + { + return Ok(new Deck().DrawCards(8).Select(card => card.ToString()).ToList()); + } +} \ No newline at end of file diff --git a/Mau/Card.cs b/Mau/Card.cs new file mode 100644 index 0000000..7e5ad7e --- /dev/null +++ b/Mau/Card.cs @@ -0,0 +1,51 @@ +namespace MauMau_Server.Mau; + +public class Card +{ + public readonly CardType CardType; + public readonly CardValue CardValue; + + public Card(CardType cardType, CardValue cardValue) + { + CardType = cardType; + CardValue = cardValue; + } + + public override string ToString() + { + return $"{CardType} {CardValue}"; + } + + public Card parseCard(string card) + { + var cardType = card.Split(" ")[0]; + var cardValue = card.Split(" ")[1]; + return new Card((CardType)Enum.Parse(typeof(CardType), cardType), + (CardValue)Enum.Parse(typeof(CardValue), cardValue)); + } +} + +public enum CardType +{ + SPADES, + HEARTS, + DIAMONDS, + CLUBS +} + +public enum CardValue +{ + TWO, + THREE, + FOUR, + FIVE, + SIX, + SEVEN, + EIGHT, + NINE, + TEN, + JACK, + QUEEN, + KING, + ACE +} \ No newline at end of file diff --git a/Mau/CardDTO.cs b/Mau/CardDTO.cs new file mode 100644 index 0000000..91334b7 --- /dev/null +++ b/Mau/CardDTO.cs @@ -0,0 +1,30 @@ +namespace MauMau_Server.Mau; + +public class CardDTO +{ + public string CardType { get; set; } + public string CardValue { get; set; } + + public CardDTO(Card card) + { + CardType = card.CardType.ToString(); + CardValue = card.CardValue.ToString(); + } + + public CardDTO(string cardType, string cardValue) + { + CardType = cardType; + CardValue = cardValue; + } + + public CardDTO() + { + + } + + 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/Deck.cs b/Mau/Deck.cs new file mode 100644 index 0000000..13ddd16 --- /dev/null +++ b/Mau/Deck.cs @@ -0,0 +1,40 @@ +namespace MauMau_Server.Mau; + +public class Deck +{ + public List UnusedDeck = new(); + + public Deck() + { + foreach (CardType cardType in Enum.GetValues(typeof(CardType))) + { + foreach (CardValue cardValue in Enum.GetValues(typeof(CardValue))) + { + UnusedDeck.Add(new Card(cardType, cardValue)); + } + } + UnusedDeck = UnusedDeck.OrderBy(x => Guid.NewGuid()).ToList(); + } + + public List GetUnusedDeck() + { + return UnusedDeck; + } + + public Card DrawCard() + { + var card = UnusedDeck[0]; + UnusedDeck.RemoveAt(0); + return card; + } + + public List DrawCards(int amount) + { + var cards = new List(); + for (var i = 0; i < amount; i++) + { + cards.Add(DrawCard()); + } + return cards; + } +} \ No newline at end of file diff --git a/Mau/HandDTO.cs b/Mau/HandDTO.cs new file mode 100644 index 0000000..ca69185 --- /dev/null +++ b/Mau/HandDTO.cs @@ -0,0 +1,19 @@ +namespace MauMau_Server.Mau; + +public class HandDTO +{ + public List _cards { get; set; } = new(); + + public HandDTO(List cards) + { + foreach (var card in cards) + { + _cards.Add(card.ToString()); + } + } + + public HandDTO() + { + + } +} \ No newline at end of file diff --git a/Websockets/Room.cs b/Websockets/Room.cs index c511148..af12bde 100644 --- a/Websockets/Room.cs +++ b/Websockets/Room.cs @@ -1,11 +1,16 @@ using System.Net.WebSockets; using System.Text; +using System.Text.Json; +using MauMau_Server.Mau; namespace MauMau_Server.Websockets; public class Room { private readonly Dictionary _connections = new(); + private readonly Deck _deck = new(); + private Card currentCard; + private List hand = new(); public async Task InstantiateConnection(WebSocket socket) { @@ -15,12 +20,16 @@ public class Room private async Task HandleConnection(WebSocket socket, string socketId) { + hand = _deck.DrawCards(8); + SendAsync(socket, JsonSerializer.Serialize(new HandDTO(hand))); var buffer = EmptyBuffer(); var result = await ReceiveAsync(socket, buffer); while (!result.CloseStatus.HasValue) { - var message = $"{socketId}: {Encoding.Default.GetString(buffer)}"; - BroadcastAsync(message); + var slicedBuffer = buffer[0..result.Count]; + var playedCard = JsonSerializer.Deserialize(slicedBuffer).ToCard(); + BroadcastAsync(JsonSerializer.Serialize(playedCard.ToString())); + SendAsync(socket, JsonSerializer.Serialize(new HandDTO(hand))); buffer = EmptyBuffer(); result = await ReceiveAsync(socket, buffer); } @@ -59,13 +68,18 @@ public class Room private void BroadcastAsync(string message) { - var bytes = Encoding.Default.GetBytes(message); - var arraySegment = new ArraySegment(bytes); foreach (var (id, socket) in GetAllConnections()) { - socket.SendAsync(arraySegment, WebSocketMessageType.Text, true, CancellationToken.None); + SendAsync(socket, message); } } + + private 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); + } private static byte[] EmptyBuffer() { diff --git a/mau.json b/mau.json new file mode 100644 index 0000000..60c5d52 --- /dev/null +++ b/mau.json @@ -0,0 +1 @@ +{"CardType":"SPADES","CardValue":"THREE"} \ No newline at end of file From c902493e283f2bad9ff6538317f55c6339012e85 Mon Sep 17 00:00:00 2001 From: DTieman Date: Thu, 16 Mar 2023 17:11:15 +0100 Subject: [PATCH 2/4] basic mau --- Mau/Deck.cs | 6 ++++++ Mau/Game.cs | 48 ++++++++++++++++++++++++++++++++++++++++++++++ Mau/GameState.cs | 16 ++++++++++++++++ Mau/HandDTO.cs | 4 ++-- Websockets/Room.cs | 11 ++++------- 5 files changed, 76 insertions(+), 9 deletions(-) create mode 100644 Mau/Game.cs create mode 100644 Mau/GameState.cs diff --git a/Mau/Deck.cs b/Mau/Deck.cs index 13ddd16..b6b1d91 100644 --- a/Mau/Deck.cs +++ b/Mau/Deck.cs @@ -3,6 +3,7 @@ public class Deck { public List UnusedDeck = new(); + public List UsedDeck = new(); public Deck() { @@ -37,4 +38,9 @@ public class Deck } return cards; } + + public void AddCardToUsedDeck(Card card) + { + UsedDeck.Add(card); + } } \ No newline at end of file diff --git a/Mau/Game.cs b/Mau/Game.cs new file mode 100644 index 0000000..a8d29a4 --- /dev/null +++ b/Mau/Game.cs @@ -0,0 +1,48 @@ +namespace MauMau_Server.Mau; + +public class Game +{ + public readonly Deck Deck = new(); + public Card CurrentCard; + public List Hand; + + public Game() + { + CurrentCard = Deck.DrawCard(); + Deck.AddCardToUsedDeck(CurrentCard); + Hand = Deck.DrawCards(8); + } + + public void PlayCard(Card card) + { + if (!IsCardInHand(Hand, card) || !IsCardPlayable(CurrentCard, card)) return; + Deck.AddCardToUsedDeck(card); + Hand.Remove(GetSameCardFromHand(Hand, card)); + CurrentCard = card; + } + + private Card GetSameCardFromHand(IEnumerable hand, Card card) + { + return hand.FirstOrDefault(handCard => IsSameCardType(handCard, card) && IsSameCardValue(handCard, card)); + } + + private bool IsCardPlayable(Card currentCard, Card playedCard) + { + return IsSameCardType(currentCard, playedCard) || IsSameCardValue(currentCard, playedCard); + } + + private bool IsCardInHand(IEnumerable hand, Card card) + { + return hand.Any(handCard => IsSameCardType(handCard, card) && IsSameCardValue(handCard, card)); + } + + 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; + } +} \ No newline at end of file diff --git a/Mau/GameState.cs b/Mau/GameState.cs new file mode 100644 index 0000000..cdb7f1c --- /dev/null +++ b/Mau/GameState.cs @@ -0,0 +1,16 @@ +namespace MauMau_Server.Mau; + +public class GameState +{ + public List Hand { get; set; } = new(); + public string CurrentCard { get; set; } + + public GameState(Game game) + { + foreach (var card in game.Hand) + { + Hand.Add(card.ToString()); + } + CurrentCard = game.CurrentCard.ToString(); + } +} \ No newline at end of file diff --git a/Mau/HandDTO.cs b/Mau/HandDTO.cs index ca69185..a5b9417 100644 --- a/Mau/HandDTO.cs +++ b/Mau/HandDTO.cs @@ -2,13 +2,13 @@ public class HandDTO { - public List _cards { get; set; } = new(); + public List Cards { get; set; } = new(); public HandDTO(List cards) { foreach (var card in cards) { - _cards.Add(card.ToString()); + Cards.Add(card.ToString()); } } diff --git a/Websockets/Room.cs b/Websockets/Room.cs index af12bde..6cf4461 100644 --- a/Websockets/Room.cs +++ b/Websockets/Room.cs @@ -8,9 +8,7 @@ namespace MauMau_Server.Websockets; public class Room { private readonly Dictionary _connections = new(); - private readonly Deck _deck = new(); - private Card currentCard; - private List hand = new(); + private Game _game = new(); public async Task InstantiateConnection(WebSocket socket) { @@ -20,16 +18,15 @@ public class Room private async Task HandleConnection(WebSocket socket, string socketId) { - hand = _deck.DrawCards(8); - SendAsync(socket, JsonSerializer.Serialize(new HandDTO(hand))); + BroadcastAsync(JsonSerializer.Serialize(new GameState(_game))); var buffer = EmptyBuffer(); var result = await ReceiveAsync(socket, buffer); while (!result.CloseStatus.HasValue) { var slicedBuffer = buffer[0..result.Count]; var playedCard = JsonSerializer.Deserialize(slicedBuffer).ToCard(); - BroadcastAsync(JsonSerializer.Serialize(playedCard.ToString())); - SendAsync(socket, JsonSerializer.Serialize(new HandDTO(hand))); + _game.PlayCard(playedCard); + BroadcastAsync(JsonSerializer.Serialize(new GameState(_game))); buffer = EmptyBuffer(); result = await ReceiveAsync(socket, buffer); } From d05ece2efc56d3565d9997f27507d37daa2d5eef Mon Sep 17 00:00:00 2001 From: DTieman Date: Fri, 17 Mar 2023 09:53:15 +0100 Subject: [PATCH 3/4] deck shuffling and rooms reset --- Controllers/RoomController.cs | 7 +++++++ Mau/Deck.cs | 15 ++++++++++++++- Websockets/RoomManager.cs | 6 ++++++ 3 files changed, 27 insertions(+), 1 deletion(-) diff --git a/Controllers/RoomController.cs b/Controllers/RoomController.cs index d687512..ac24bd7 100644 --- a/Controllers/RoomController.cs +++ b/Controllers/RoomController.cs @@ -51,4 +51,11 @@ public class RoomController : ControllerBase var id = _roomManager.CreateRoom(); return Ok(id); } + + [HttpDelete] + public IActionResult Delete() + { + _roomManager.RemoveAllRooms(); + return NoContent(); + } } \ No newline at end of file diff --git a/Mau/Deck.cs b/Mau/Deck.cs index b6b1d91..b2b6ff5 100644 --- a/Mau/Deck.cs +++ b/Mau/Deck.cs @@ -14,7 +14,7 @@ public class Deck UnusedDeck.Add(new Card(cardType, cardValue)); } } - UnusedDeck = UnusedDeck.OrderBy(x => Guid.NewGuid()).ToList(); + ShuffleDeck(); } public List GetUnusedDeck() @@ -24,6 +24,7 @@ public class Deck public Card DrawCard() { + if (UnusedDeck.Count == 0) ReshuffleDeck(); var card = UnusedDeck[0]; UnusedDeck.RemoveAt(0); return card; @@ -43,4 +44,16 @@ public class Deck { UsedDeck.Add(card); } + + private void ReshuffleDeck() + { + UnusedDeck.AddRange(UsedDeck); + UsedDeck.Clear(); + ShuffleDeck(); + } + + private void ShuffleDeck() + { + UnusedDeck = UnusedDeck.OrderBy(x => Guid.NewGuid()).ToList(); + } } \ No newline at end of file diff --git a/Websockets/RoomManager.cs b/Websockets/RoomManager.cs index 5fb699a..280a9c7 100644 --- a/Websockets/RoomManager.cs +++ b/Websockets/RoomManager.cs @@ -31,6 +31,11 @@ public class RoomManager : IRoomManager { return Rooms.ContainsKey(roomId); } + + public void RemoveAllRooms() + { + Rooms.Clear(); + } } public interface IRoomManager @@ -40,4 +45,5 @@ public interface IRoomManager public List GetAllRooms(); public void RemoveRoom(string roomId); public bool RoomExists(string roomId); + public void RemoveAllRooms(); } \ No newline at end of file From 5d1e78094f629e78b8f8988a643834d3734d1a8e Mon Sep 17 00:00:00 2001 From: DTieman Date: Fri, 17 Mar 2023 11:42:53 +0100 Subject: [PATCH 4/4] basic logic + multiplayer --- Controllers/RoomController.cs | 2 +- Mau/Game.cs | 68 ++++++++++++++++++++++++++++------- Mau/GameState.cs | 16 +++++++-- Mau/HandDTO.cs | 19 ---------- Mau/Player.cs | 20 +++++++++++ Websockets/Room.cs | 20 ++++++++--- 6 files changed, 106 insertions(+), 39 deletions(-) delete mode 100644 Mau/HandDTO.cs create mode 100644 Mau/Player.cs diff --git a/Controllers/RoomController.cs b/Controllers/RoomController.cs index ac24bd7..f9c281b 100644 --- a/Controllers/RoomController.cs +++ b/Controllers/RoomController.cs @@ -51,7 +51,7 @@ public class RoomController : ControllerBase var id = _roomManager.CreateRoom(); return Ok(id); } - + [HttpDelete] public IActionResult Delete() { diff --git a/Mau/Game.cs b/Mau/Game.cs index a8d29a4..1ba09f0 100644 --- a/Mau/Game.cs +++ b/Mau/Game.cs @@ -1,39 +1,81 @@ -namespace MauMau_Server.Mau; +using System.Net.WebSockets; + +namespace MauMau_Server.Mau; public class Game { public readonly Deck Deck = new(); public Card CurrentCard; - public List Hand; + public List Players = new(); + public Player CurrentPlayer; + public int TurnDirection = 1; public Game() { CurrentCard = Deck.DrawCard(); Deck.AddCardToUsedDeck(CurrentCard); - Hand = Deck.DrawCards(8); } - public void PlayCard(Card card) + public void AddPlayerToGame(string playerId, WebSocket socket) { - if (!IsCardInHand(Hand, card) || !IsCardPlayable(CurrentCard, card)) return; - Deck.AddCardToUsedDeck(card); - Hand.Remove(GetSameCardFromHand(Hand, card)); - CurrentCard = card; + var player = new Player("Koetje " + playerId.Split('-')[0], playerId, socket) + { + Hand = Deck.DrawCards(8) + }; + Players.Add(player); + if (Players.Count == 1) CurrentPlayer = player; } - private Card GetSameCardFromHand(IEnumerable hand, Card card) + public void RemovePlayer(string playerId) { - return hand.FirstOrDefault(handCard => IsSameCardType(handCard, card) && IsSameCardValue(handCard, card)); + var player = GetPlayer(playerId); + Players.Remove(player); } - private bool IsCardPlayable(Card currentCard, Card playedCard) + public void PlayCard(string playerId, Card card) + { + var player = GetPlayer(playerId); + if (CurrentPlayer != player) return; + var hand = player.Hand; + if (!IsCardInHand(hand, card) || !IsCardPlayable(CurrentCard, card)) return; + Deck.AddCardToUsedDeck(card); + hand.Remove(GetSameCardFromHand(hand, card)); + CurrentCard = card; + CurrentPlayer = GetNextPlayer(); + } + + public Player GetNextPlayer() + { + var index = Players.IndexOf(CurrentPlayer); + 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); } - private bool IsCardInHand(IEnumerable hand, Card card) + private static bool IsCardInHand(IEnumerable hand, Card card) { - return hand.Any(handCard => IsSameCardType(handCard, card) && IsSameCardValue(handCard, 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) diff --git a/Mau/GameState.cs b/Mau/GameState.cs index cdb7f1c..2218875 100644 --- a/Mau/GameState.cs +++ b/Mau/GameState.cs @@ -2,15 +2,27 @@ public class GameState { + public string PlayerName { get; set; } public List Hand { get; set; } = new(); public string CurrentCard { get; set; } + public string CurrentPlayer { get; set; } + public List Players { get; set; } = new(); - public GameState(Game game) + public GameState(Game game, string playerId) { - foreach (var card in game.Hand) + var p = game.GetPlayer(playerId); + PlayerName = p.Name; + foreach (var card in p.Hand) { Hand.Add(card.ToString()); } + + foreach (var player in game.Players) + { + Players.Add(player.Name); + } + CurrentCard = game.CurrentCard.ToString(); + CurrentPlayer = game.CurrentPlayer.Name; } } \ No newline at end of file diff --git a/Mau/HandDTO.cs b/Mau/HandDTO.cs deleted file mode 100644 index a5b9417..0000000 --- a/Mau/HandDTO.cs +++ /dev/null @@ -1,19 +0,0 @@ -namespace MauMau_Server.Mau; - -public class HandDTO -{ - public List Cards { get; set; } = new(); - - public HandDTO(List cards) - { - foreach (var card in cards) - { - Cards.Add(card.ToString()); - } - } - - public HandDTO() - { - - } -} \ No newline at end of file diff --git a/Mau/Player.cs b/Mau/Player.cs new file mode 100644 index 0000000..0a91b20 --- /dev/null +++ b/Mau/Player.cs @@ -0,0 +1,20 @@ +using System.Net.WebSockets; + +namespace MauMau_Server.Mau; + +public class Player +{ + public string Name { get; set; } + public string PlayerId { get; set; } + public WebSocket Socket { get; set; } + public List Hand { get; set; } = new(); + + public Player(string name, string playerId, WebSocket socket) + { + Name = name; + PlayerId = playerId; + Socket = socket; + } + + public bool IsMe(string playerId) => PlayerId == playerId; +} \ No newline at end of file diff --git a/Websockets/Room.cs b/Websockets/Room.cs index 6cf4461..b3003fd 100644 --- a/Websockets/Room.cs +++ b/Websockets/Room.cs @@ -8,7 +8,7 @@ namespace MauMau_Server.Websockets; public class Room { private readonly Dictionary _connections = new(); - private Game _game = new(); + private readonly Game _game = new(); public async Task InstantiateConnection(WebSocket socket) { @@ -18,26 +18,28 @@ public class Room private async Task HandleConnection(WebSocket socket, string socketId) { - BroadcastAsync(JsonSerializer.Serialize(new GameState(_game))); + BroadcastGameState(); var buffer = EmptyBuffer(); var result = await ReceiveAsync(socket, buffer); while (!result.CloseStatus.HasValue) { var slicedBuffer = buffer[0..result.Count]; var playedCard = JsonSerializer.Deserialize(slicedBuffer).ToCard(); - _game.PlayCard(playedCard); - BroadcastAsync(JsonSerializer.Serialize(new GameState(_game))); + _game.PlayCard(socketId, playedCard); + BroadcastGameState(); buffer = EmptyBuffer(); result = await ReceiveAsync(socket, buffer); } await socket.CloseAsync(result.CloseStatus.Value, result.CloseStatusDescription, CancellationToken.None); RemoveConnection(socketId); + _game.RemovePlayer(socketId); } private string AddConnection(WebSocket socket) { var socketId = Guid.NewGuid().ToString(); _connections.Add(socketId, socket); + _game.AddPlayerToGame(socketId, socket); return socketId; } @@ -63,6 +65,16 @@ public class Room return result; } + private void BroadcastGameState() + { + foreach (var (id, socket) in GetAllConnections()) + { + var gameState = new GameState(_game, id); + var message = JsonSerializer.Serialize(gameState); + SendAsync(socket, message); + } + } + private void BroadcastAsync(string message) { foreach (var (id, socket) in GetAllConnections())