Merge pull request #1 from MauMauStudios/mautiplayer
Basic (and First!) Multiplayer Mau Back-End
This commit is contained in:
21
Controllers/MauController.cs
Normal file
21
Controllers/MauController.cs
Normal 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());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -51,4 +51,11 @@ public class RoomController : ControllerBase
|
|||||||
var id = _roomManager.CreateRoom();
|
var id = _roomManager.CreateRoom();
|
||||||
return Ok(id);
|
return Ok(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[HttpDelete]
|
||||||
|
public IActionResult Delete()
|
||||||
|
{
|
||||||
|
_roomManager.RemoveAllRooms();
|
||||||
|
return NoContent();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
51
Mau/Card.cs
Normal file
51
Mau/Card.cs
Normal 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
30
Mau/CardDTO.cs
Normal 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
59
Mau/Deck.cs
Normal 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
90
Mau/Game.cs
Normal 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
28
Mau/GameState.cs
Normal 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
20
Mau/Player.cs
Normal 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;
|
||||||
|
}
|
||||||
@@ -1,11 +1,14 @@
|
|||||||
using System.Net.WebSockets;
|
using System.Net.WebSockets;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
using System.Text.Json;
|
||||||
|
using MauMau_Server.Mau;
|
||||||
|
|
||||||
namespace MauMau_Server.Websockets;
|
namespace MauMau_Server.Websockets;
|
||||||
|
|
||||||
public class Room
|
public class Room
|
||||||
{
|
{
|
||||||
private readonly Dictionary<string, WebSocket> _connections = new();
|
private readonly Dictionary<string, WebSocket> _connections = new();
|
||||||
|
private readonly Game _game = new();
|
||||||
|
|
||||||
public async Task InstantiateConnection(WebSocket socket)
|
public async Task InstantiateConnection(WebSocket socket)
|
||||||
{
|
{
|
||||||
@@ -15,23 +18,28 @@ public class Room
|
|||||||
|
|
||||||
private async Task HandleConnection(WebSocket socket, string socketId)
|
private async Task HandleConnection(WebSocket socket, string socketId)
|
||||||
{
|
{
|
||||||
|
BroadcastGameState();
|
||||||
var buffer = EmptyBuffer();
|
var buffer = EmptyBuffer();
|
||||||
var result = await ReceiveAsync(socket, buffer);
|
var result = await ReceiveAsync(socket, buffer);
|
||||||
while (!result.CloseStatus.HasValue)
|
while (!result.CloseStatus.HasValue)
|
||||||
{
|
{
|
||||||
var message = $"{socketId}: {Encoding.Default.GetString(buffer)}";
|
var slicedBuffer = buffer[0..result.Count];
|
||||||
BroadcastAsync(message);
|
var playedCard = JsonSerializer.Deserialize<CardDTO>(slicedBuffer).ToCard();
|
||||||
|
_game.PlayCard(socketId, playedCard);
|
||||||
|
BroadcastGameState();
|
||||||
buffer = EmptyBuffer();
|
buffer = EmptyBuffer();
|
||||||
result = await ReceiveAsync(socket, buffer);
|
result = await ReceiveAsync(socket, buffer);
|
||||||
}
|
}
|
||||||
await socket.CloseAsync(result.CloseStatus.Value, result.CloseStatusDescription, CancellationToken.None);
|
await socket.CloseAsync(result.CloseStatus.Value, result.CloseStatusDescription, CancellationToken.None);
|
||||||
RemoveConnection(socketId);
|
RemoveConnection(socketId);
|
||||||
|
_game.RemovePlayer(socketId);
|
||||||
}
|
}
|
||||||
|
|
||||||
private string AddConnection(WebSocket socket)
|
private string AddConnection(WebSocket socket)
|
||||||
{
|
{
|
||||||
var socketId = Guid.NewGuid().ToString();
|
var socketId = Guid.NewGuid().ToString();
|
||||||
_connections.Add(socketId, socket);
|
_connections.Add(socketId, socket);
|
||||||
|
_game.AddPlayerToGame(socketId, socket);
|
||||||
return socketId;
|
return socketId;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -57,14 +65,29 @@ public class Room
|
|||||||
return result;
|
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)
|
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 bytes = Encoding.Default.GetBytes(message);
|
||||||
var arraySegment = new ArraySegment<byte>(bytes);
|
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()
|
private static byte[] EmptyBuffer()
|
||||||
|
|||||||
@@ -31,6 +31,11 @@ public class RoomManager : IRoomManager
|
|||||||
{
|
{
|
||||||
return Rooms.ContainsKey(roomId);
|
return Rooms.ContainsKey(roomId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void RemoveAllRooms()
|
||||||
|
{
|
||||||
|
Rooms.Clear();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public interface IRoomManager
|
public interface IRoomManager
|
||||||
@@ -40,4 +45,5 @@ public interface IRoomManager
|
|||||||
public List<string> GetAllRooms();
|
public List<string> GetAllRooms();
|
||||||
public void RemoveRoom(string roomId);
|
public void RemoveRoom(string roomId);
|
||||||
public bool RoomExists(string roomId);
|
public bool RoomExists(string roomId);
|
||||||
|
public void RemoveAllRooms();
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user