Large refactor and added new rules #2
@@ -17,6 +17,10 @@ public class Deck
|
|||||||
ShuffleDeck();
|
ShuffleDeck();
|
||||||
CurrentCard = DrawCard();
|
CurrentCard = DrawCard();
|
||||||
_usedDeck.Add(CurrentCard);
|
_usedDeck.Add(CurrentCard);
|
||||||
|
|
||||||
|
if (CurrentCard.CardType != CardType.JOKER) return;
|
||||||
|
CurrentCard = DrawCard();
|
||||||
|
_usedDeck.Add(CurrentCard);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
104
Mau/Game.cs
104
Mau/Game.cs
@@ -11,6 +11,8 @@ public class Game : RoomType
|
|||||||
{
|
{
|
||||||
private readonly Deck _deck = new();
|
private readonly Deck _deck = new();
|
||||||
private readonly TurnManager _turnManager = new();
|
private readonly TurnManager _turnManager = new();
|
||||||
|
public readonly List<Card> MauCardBuffer = new();
|
||||||
|
public CardType? NextAllowedCardType { get; set; }
|
||||||
|
|
||||||
public Game(Room.Room room, IEnumerable<ConnectionInstance> connections) : base(room)
|
public Game(Room.Room room, IEnumerable<ConnectionInstance> connections) : base(room)
|
||||||
{
|
{
|
||||||
@@ -69,7 +71,7 @@ public class Game : RoomType
|
|||||||
public override void OnConnect(ConnectionInstance connection)
|
public override void OnConnect(ConnectionInstance connection)
|
||||||
{
|
{
|
||||||
// Broadcast that a player joined
|
// 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<JoinMessage>("JOIN", joinMessage));
|
_room.BroadCast(new RoomMessage<JoinMessage>("JOIN", joinMessage));
|
||||||
|
|
||||||
// Create a new player, give them a new hand and add them to the game
|
// 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)
|
public override void OnDisconnect(ConnectionInstance connection)
|
||||||
{
|
{
|
||||||
// Broadcast that the player left
|
// 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<LeaveMessage>("LEAVE", leaveMessage));
|
_room.BroadCast(new RoomMessage<LeaveMessage>("LEAVE", leaveMessage));
|
||||||
|
|
||||||
// Get the player that left
|
// Get the player that left
|
||||||
@@ -127,24 +129,11 @@ public class Game : RoomType
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Does the current card require the next player to draw cards?
|
// Set the next card type that is allowed to be played
|
||||||
var isCardGivingCard = _deck.CurrentCard.CardType == CardType.JOKER || _deck.CurrentCard.CardValue == CardValue.TWO;
|
NextAllowedCardType = cardType;
|
||||||
|
|
||||||
// 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);
|
|
||||||
|
|
||||||
// Change the turns
|
// Change the turns
|
||||||
_turnManager.ChangeTurnTo(nextPlayer);
|
_turnManager.ChangeTurnTo();
|
||||||
|
|
||||||
// Broadcast new game state
|
// Broadcast new game state
|
||||||
SendGameState();
|
SendGameState();
|
||||||
@@ -157,11 +146,30 @@ public class Game : RoomType
|
|||||||
* <item>Could not play a card and had to draw a card</item>
|
* <item>Could not play a card and had to draw a card</item>
|
||||||
* <item>Chose to draw a card as a strategic move</item>
|
* <item>Chose to draw a card as a strategic move</item>
|
||||||
* </list>
|
* </list>
|
||||||
|
* When there are multiple mau cards played, the player has to draw the combined amount of mau cards played.
|
||||||
* </summary>
|
* </summary>
|
||||||
* <param name="player">The player that drew a card</param>
|
* <param name="player">The player that drew a card</param>
|
||||||
* <param name="data">A string that can be serialized to a drawcard instance</param>
|
* <param name="data">A string that can be serialized to a drawcard instance</param>
|
||||||
*/
|
*/
|
||||||
private void Draw(Player player)
|
private void Draw(Player player)
|
||||||
|
{
|
||||||
|
// 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
|
// Draw a card from the deck
|
||||||
var drawnCard = _deck.DrawCard();
|
var drawnCard = _deck.DrawCard();
|
||||||
@@ -174,6 +182,7 @@ public class Game : RoomType
|
|||||||
{
|
{
|
||||||
_turnManager.ChangeTurnTo();
|
_turnManager.ChangeTurnTo();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Broadcast new game state
|
// Broadcast new game state
|
||||||
SendGameState();
|
SendGameState();
|
||||||
@@ -209,12 +218,32 @@ public class Game : RoomType
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if the played card is compatible with the current card
|
// Check if there is a specific card type that is allowed to be played
|
||||||
// If not, ignore the play
|
if (NextAllowedCardType != null)
|
||||||
|
{
|
||||||
|
// 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))
|
if (!playerCard.CanBePlayedOn(_deck.CurrentCard))
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Remove the card from the player's hand
|
// Remove the card from the player's hand
|
||||||
player.Hand.Remove(playerCard);
|
player.Hand.Remove(playerCard);
|
||||||
@@ -251,14 +280,14 @@ public class Game : RoomType
|
|||||||
case CardValue.RED:
|
case CardValue.RED:
|
||||||
case CardValue.BLACK:
|
case CardValue.BLACK:
|
||||||
{
|
{
|
||||||
_turnManager.GetNextPlayer().GiveCards(_deck.DrawCards(5));
|
MauCardBuffer.Add(card);
|
||||||
_turnManager.CurrentPlayer.State = PlayerState.CHOOSE;
|
_turnManager.CurrentPlayer.State = PlayerState.CHOOSE;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case CardValue.TWO:
|
case CardValue.TWO:
|
||||||
{
|
{
|
||||||
_turnManager.GetNextPlayer().GiveCards(_deck.DrawCards(2));
|
MauCardBuffer.Add(card);
|
||||||
_turnManager.ChangeTurnTo(_turnManager.GetNextPlayer(2));
|
_turnManager.ChangeTurnTo();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case CardValue.SEVEN:
|
case CardValue.SEVEN:
|
||||||
@@ -290,6 +319,33 @@ public class Game : RoomType
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <summary>
|
||||||
|
* Count the amount of cards that need to be drawn from the MauCardBuffer. This method also clears the buffer.
|
||||||
|
* </summary>
|
||||||
|
* <returns>The amount of cards that need to be drawn</returns>
|
||||||
|
*/
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* <summary>
|
* <summary>
|
||||||
* Create a game state for each player and send it to them.
|
* 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)
|
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<GameState>("GAME", gameState)));
|
player.Connection.SendMessageAsync(JsonConvert.SerializeObject(new RoomMessage<GameState>("GAME", gameState)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,10 +6,11 @@ public class GameState
|
|||||||
public string MyState { get; set; }
|
public string MyState { get; set; }
|
||||||
public List<string> Hand { get; set; } = new();
|
public List<string> Hand { get; set; } = new();
|
||||||
public string CurrentCard { get; set; }
|
public string CurrentCard { get; set; }
|
||||||
|
public string? NextAllowedCardType { get; set; }
|
||||||
public PlayerDTO CurrentPlayer { get; set; }
|
public PlayerDTO CurrentPlayer { get; set; }
|
||||||
public List<PlayerDTO> Players { get; set; } = new();
|
public List<PlayerDTO> Players { get; set; } = new();
|
||||||
|
|
||||||
public GameState(Player me, Card currentCard, Player currentPlayer, List<Player> others)
|
public GameState(Player me, Card currentCard, CardType? nextAllowedCardType, Player currentPlayer, List<Player> others)
|
||||||
{
|
{
|
||||||
Me = new PlayerDTO(me);
|
Me = new PlayerDTO(me);
|
||||||
MyState = me.State.ToString();
|
MyState = me.State.ToString();
|
||||||
@@ -23,6 +24,7 @@ public class GameState
|
|||||||
Players.Add(new PlayerDTO(player));
|
Players.Add(new PlayerDTO(player));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
NextAllowedCardType = nextAllowedCardType?.ToString();
|
||||||
CurrentCard = currentCard.ToString();
|
CurrentCard = currentCard.ToString();
|
||||||
CurrentPlayer = new PlayerDTO(currentPlayer);
|
CurrentPlayer = new PlayerDTO(currentPlayer);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,13 +21,13 @@ public class Lobby : RoomType
|
|||||||
|
|
||||||
public override void OnConnect(ConnectionInstance connection)
|
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<JoinMessage>("JOIN", joinMessage));
|
_room.BroadCast(new RoomMessage<JoinMessage>("JOIN", joinMessage));
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void OnDisconnect(ConnectionInstance connection)
|
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<LeaveMessage>("LEAVE", leaveMessage));
|
_room.BroadCast(new RoomMessage<LeaveMessage>("LEAVE", leaveMessage));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,21 +1,15 @@
|
|||||||
namespace MauMau_Server.Room.Messages;
|
using MauMau_Server.Websockets;
|
||||||
|
|
||||||
|
namespace MauMau_Server.Room.Messages;
|
||||||
|
|
||||||
public class JoinMessage
|
public class JoinMessage
|
||||||
{
|
{
|
||||||
public Guid Id { get; set; }
|
public List<ConnectionInstance> Connections { get; set; }
|
||||||
public string ChatMessage { get; set; }
|
public ConnectionInstance NewConnection { get; set; }
|
||||||
public string? Data { get; set; }
|
|
||||||
|
|
||||||
public JoinMessage(Guid id, string chatMessage, string data)
|
public JoinMessage(List<ConnectionInstance> connections, ConnectionInstance newConnection)
|
||||||
{
|
{
|
||||||
Id = id;
|
Connections = connections;
|
||||||
ChatMessage = chatMessage;
|
NewConnection = newConnection;
|
||||||
Data = data;
|
|
||||||
}
|
|
||||||
|
|
||||||
public JoinMessage(Guid id, string chatMessage)
|
|
||||||
{
|
|
||||||
Id = id;
|
|
||||||
ChatMessage = chatMessage;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,13 +1,13 @@
|
|||||||
namespace MauMau_Server.Room.Messages;
|
using MauMau_Server.Websockets;
|
||||||
|
|
||||||
|
namespace MauMau_Server.Room.Messages;
|
||||||
|
|
||||||
public class LeaveMessage
|
public class LeaveMessage
|
||||||
{
|
{
|
||||||
public Guid Id { get; set; }
|
public ConnectionInstance Connection { get; set; }
|
||||||
public string ChatMessage { get; set; }
|
|
||||||
|
|
||||||
public LeaveMessage(Guid id, string chatMessage)
|
public LeaveMessage(ConnectionInstance connection)
|
||||||
{
|
{
|
||||||
Id = id;
|
Connection = connection;
|
||||||
ChatMessage = chatMessage;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
58
Room/Room.cs
58
Room/Room.cs
@@ -3,6 +3,7 @@ using System.Text.Json;
|
|||||||
using System.Text.RegularExpressions;
|
using System.Text.RegularExpressions;
|
||||||
using MauMau_Server.Room.Messages;
|
using MauMau_Server.Room.Messages;
|
||||||
using MauMau_Server.Websockets;
|
using MauMau_Server.Websockets;
|
||||||
|
using Microsoft.AspNet.SignalR.Messaging;
|
||||||
|
|
||||||
namespace MauMau_Server.Room;
|
namespace MauMau_Server.Room;
|
||||||
|
|
||||||
@@ -24,7 +25,13 @@ public class Room
|
|||||||
public async Task InstantiateConnection(WebSocket socket, string name)
|
public async Task InstantiateConnection(WebSocket socket, string name)
|
||||||
{
|
{
|
||||||
var connectionId = Guid.NewGuid();
|
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;
|
if (IsEmpty()) Host = connection;
|
||||||
Connections.Add(connection);
|
Connections.Add(connection);
|
||||||
|
|
||||||
@@ -38,24 +45,57 @@ public class Room
|
|||||||
while (!webSocketResponse.Result!.CloseStatus.HasValue)
|
while (!webSocketResponse.Result!.CloseStatus.HasValue)
|
||||||
{
|
{
|
||||||
var message = JsonSerializer.Deserialize<RoomMessage<string>>(webSocketResponse.SlicedBuffer);
|
var message = JsonSerializer.Deserialize<RoomMessage<string>>(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<ChatMessage>("CHAT", chatMessage));
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
{
|
||||||
|
case "CHAT":
|
||||||
|
HandleChatMessage(connection, message.Data);
|
||||||
|
break;
|
||||||
|
case "KICK":
|
||||||
|
await HandleKick(Guid.Parse(message.Data));
|
||||||
|
break;
|
||||||
|
default:
|
||||||
RoomType.OnMessage(connection, message);
|
RoomType.OnMessage(connection, message);
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
webSocketResponse = await WebsocketManager.ReceiveAsync(connection.Socket);
|
webSocketResponse = await WebsocketManager.ReceiveAsync(connection.Socket);
|
||||||
}
|
}
|
||||||
|
|
||||||
WebsocketManager.CloseAsync(connection.Socket, webSocketResponse.Result);
|
WebsocketManager.CloseAsync(connection.Socket, webSocketResponse.Result);
|
||||||
HandleDisconnect(connection);
|
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<ChatMessage>("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)
|
private void HandleDisconnect(ConnectionInstance connection)
|
||||||
{
|
{
|
||||||
Connections.Remove(connection);
|
Connections.Remove(connection);
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
using System.Net.WebSockets;
|
using System.Net.WebSockets;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
using Newtonsoft.Json;
|
||||||
|
|
||||||
namespace MauMau_Server.Websockets;
|
namespace MauMau_Server.Websockets;
|
||||||
|
|
||||||
@@ -7,6 +8,7 @@ public class ConnectionInstance
|
|||||||
{
|
{
|
||||||
public Guid Id { get; set; }
|
public Guid Id { get; set; }
|
||||||
public string Name { get; set; }
|
public string Name { get; set; }
|
||||||
|
[JsonIgnore]
|
||||||
public WebSocket Socket { get; set; }
|
public WebSocket Socket { get; set; }
|
||||||
|
|
||||||
public ConnectionInstance(string name, Guid id, WebSocket socket)
|
public ConnectionInstance(string name, Guid id, WebSocket socket)
|
||||||
|
|||||||
@@ -15,7 +15,10 @@ public static class WebsocketManager
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static async void CloseAsync(WebSocket webSocket, WebSocketReceiveResult result)
|
public static async void CloseAsync(WebSocket webSocket, WebSocketReceiveResult result)
|
||||||
|
{
|
||||||
|
if (webSocket.State is WebSocketState.Open or WebSocketState.CloseReceived or WebSocketState.CloseSent)
|
||||||
{
|
{
|
||||||
await webSocket.CloseAsync(result.CloseStatus.Value, result.CloseStatusDescription, CancellationToken.None);
|
await webSocket.CloseAsync(result.CloseStatus.Value, result.CloseStatusDescription, CancellationToken.None);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user