Merge pull request #1 from MauMauStudios/mautiplayer

Basic (and First!) Multiplayer Mau Back-End
This commit is contained in:
Jordan Geurtsen
2023-03-17 13:25:32 +01:00
committed by GitHub
11 changed files with 342 additions and 6 deletions

View File

@@ -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());
}
}

View File

@@ -51,4 +51,11 @@ public class RoomController : ControllerBase
var id = _roomManager.CreateRoom();
return Ok(id);
}
[HttpDelete]
public IActionResult Delete()
{
_roomManager.RemoveAllRooms();
return NoContent();
}
}

51
Mau/Card.cs Normal file
View File

@@ -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
}

30
Mau/CardDTO.cs Normal file
View File

@@ -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));
}
}

59
Mau/Deck.cs Normal file
View File

@@ -0,0 +1,59 @@
namespace MauMau_Server.Mau;
public class Deck
{
public List<Card> UnusedDeck = new();
public List<Card> UsedDeck = 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));
}
}
ShuffleDeck();
}
public List<Card> GetUnusedDeck()
{
return UnusedDeck;
}
public Card DrawCard()
{
if (UnusedDeck.Count == 0) ReshuffleDeck();
var card = UnusedDeck[0];
UnusedDeck.RemoveAt(0);
return card;
}
public List<Card> DrawCards(int amount)
{
var cards = new List<Card>();
for (var i = 0; i < amount; i++)
{
cards.Add(DrawCard());
}
return cards;
}
public void AddCardToUsedDeck(Card card)
{
UsedDeck.Add(card);
}
private void ReshuffleDeck()
{
UnusedDeck.AddRange(UsedDeck);
UsedDeck.Clear();
ShuffleDeck();
}
private void ShuffleDeck()
{
UnusedDeck = UnusedDeck.OrderBy(x => Guid.NewGuid()).ToList();
}
}

90
Mau/Game.cs Normal file
View File

@@ -0,0 +1,90 @@
using System.Net.WebSockets;
namespace MauMau_Server.Mau;
public class Game
{
public readonly Deck Deck = new();
public Card CurrentCard;
public List<Player> Players = new();
public Player CurrentPlayer;
public int TurnDirection = 1;
public Game()
{
CurrentCard = Deck.DrawCard();
Deck.AddCardToUsedDeck(CurrentCard);
}
public void AddPlayerToGame(string playerId, WebSocket socket)
{
var player = new Player("Koetje " + playerId.Split('-')[0], playerId, socket)
{
Hand = Deck.DrawCards(8)
};
Players.Add(player);
if (Players.Count == 1) CurrentPlayer = player;
}
public void RemovePlayer(string playerId)
{
var player = GetPlayer(playerId);
Players.Remove(player);
}
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<Card> 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 static bool IsCardInHand(IEnumerable<Card> 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;
}
}

28
Mau/GameState.cs Normal file
View File

@@ -0,0 +1,28 @@
namespace MauMau_Server.Mau;
public class GameState
{
public string PlayerName { get; set; }
public List<string> Hand { get; set; } = new();
public string CurrentCard { get; set; }
public string CurrentPlayer { get; set; }
public List<string> Players { get; set; } = new();
public GameState(Game game, string playerId)
{
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;
}
}

20
Mau/Player.cs Normal file
View File

@@ -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<Card> 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;
}

View File

@@ -1,11 +1,14 @@
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<string, WebSocket> _connections = new();
private readonly Game _game = new();
public async Task InstantiateConnection(WebSocket socket)
{
@@ -15,23 +18,28 @@ public class Room
private async Task HandleConnection(WebSocket socket, string socketId)
{
BroadcastGameState();
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<CardDTO>(slicedBuffer).ToCard();
_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;
}
@@ -57,14 +65,29 @@ 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())
{
SendAsync(socket, message);
}
}
private void SendAsync(WebSocket socket, string message)
{
var bytes = Encoding.Default.GetBytes(message);
var arraySegment = new ArraySegment<byte>(bytes);
foreach (var (id, socket) in GetAllConnections())
{
socket.SendAsync(arraySegment, WebSocketMessageType.Text, true, CancellationToken.None);
}
socket.SendAsync(arraySegment, WebSocketMessageType.Text, true, CancellationToken.None);
}
private static byte[] EmptyBuffer()

View File

@@ -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<string> GetAllRooms();
public void RemoveRoom(string roomId);
public bool RoomExists(string roomId);
public void RemoveAllRooms();
}

1
mau.json Normal file
View File

@@ -0,0 +1 @@
{"CardType":"SPADES","CardValue":"THREE"}