320 lines
10 KiB
C#
320 lines
10 KiB
C#
using MauMau_Server.Mau.GameMessages;
|
|
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;
|
|
|
|
public class Game : RoomType
|
|
{
|
|
private readonly Deck _deck = new();
|
|
private readonly TurnManager _turnManager = new();
|
|
|
|
public Game(Room.Room room, IEnumerable<ConnectionInstance> connections) : base(room)
|
|
{
|
|
// Convert all the connections to players
|
|
List<Player> 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();
|
|
}
|
|
|
|
/**
|
|
* <inheritdoc cref="RoomType.OnMessage"/>
|
|
*/
|
|
public override void OnMessage(ConnectionInstance sender, RoomMessage<string> 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;
|
|
|
|
// Based on the message intent, handle the message
|
|
switch (message.Type)
|
|
{
|
|
case "CHOOSE":
|
|
Choose(player, message.Data);
|
|
break;
|
|
case "PLAY":
|
|
Play(player, message.Data);
|
|
break;
|
|
default:
|
|
Draw(player);
|
|
break;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* <inheritdoc cref="RoomType.OnConnect"/>
|
|
*/
|
|
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<JoinMessage>("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();
|
|
}
|
|
|
|
/**
|
|
* <inheritdoc cref="RoomType.OnDisconnect"/>
|
|
*/
|
|
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<LeaveMessage>("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();
|
|
}
|
|
|
|
/**
|
|
* <summary>
|
|
* The player had to choose a new card type
|
|
* </summary>
|
|
* <param name="player">The player that chose a new card type</param>
|
|
* <param name="data">A string that represents a CardType</param>
|
|
*/
|
|
private void Choose(Player player, string data)
|
|
{
|
|
// Convert the data to a CardType, if it fails, ignore the message
|
|
if (!Enum.TryParse(data, out CardType cardType))
|
|
{
|
|
return;
|
|
}
|
|
|
|
// Does the current card require the next player to draw cards?
|
|
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(_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
|
|
_turnManager.ChangeTurn(nextPlayer);
|
|
|
|
// Broadcast new game state
|
|
SendGameState();
|
|
}
|
|
|
|
/**
|
|
* <summary>
|
|
* The player either:
|
|
* <list type="bullet">
|
|
* <item>Could not play a card and had to draw a card</item>
|
|
* <item>Chose to draw a card as a strategic move</item>
|
|
* </list>
|
|
* </summary>
|
|
* <param name="player">The player that drew a card</param>
|
|
* <param name="data">A string that can be serialized to a drawcard instance</param>
|
|
*/
|
|
private void Draw(Player player)
|
|
{
|
|
// Draw a card from the deck
|
|
var drawnCard = _deck.DrawCard();
|
|
|
|
// Give the card to the player
|
|
player.GiveCard(drawnCard);
|
|
|
|
// Change the player if the drawn card cannot be played
|
|
if (!drawnCard.CanBePlayedOn(_deck.CurrentCard))
|
|
{
|
|
_turnManager.ChangeTurn();
|
|
}
|
|
|
|
// Broadcast new game state
|
|
SendGameState();
|
|
}
|
|
|
|
/**
|
|
* <summary>
|
|
* The player plays a card, this will only be possible if:
|
|
* <list type="bullet">
|
|
* <item>The player has the correct state</item>
|
|
* <item>The player has the card in their hand</item>
|
|
* <item>The card is playable on the current card</item>
|
|
* </list>
|
|
* </summary>
|
|
* <param name="player">The player that played</param>
|
|
* <param name="data">A string that can be serialized to a playcard instance</param>
|
|
*/
|
|
private void Play(Player player, string data)
|
|
{
|
|
// Check if the player has the correct state to play a card
|
|
if (player.State != PlayerState.TURN)
|
|
{
|
|
return;
|
|
}
|
|
|
|
// Convert the data to a Card instance
|
|
var cardData = JsonConvert.DeserializeObject<PlayCard>(data).ToCard();
|
|
|
|
// Check if the player indeed has the card they claim to have
|
|
var playerCard = player.TakeCardFromHand(cardData);
|
|
if (playerCard is null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
// Check if the played card is compatible with the current card
|
|
// If not, ignore the play
|
|
if (!playerCard.CanBePlayedOn(_deck.CurrentCard))
|
|
{
|
|
return;
|
|
}
|
|
|
|
// Remove the card from the player's hand
|
|
player.Hand.Remove(playerCard);
|
|
|
|
// Add the card to the used deck
|
|
_deck.AddCardToUsedDeck(playerCard);
|
|
|
|
// Set the new current card
|
|
_deck.CurrentCard = playerCard;
|
|
|
|
if (player.Hand.Count == 0)
|
|
{
|
|
EndGame(player);
|
|
return;
|
|
}
|
|
|
|
// Change the turn based on the played card
|
|
HandleNextPlayer(playerCard);
|
|
|
|
// Broadcast new game state
|
|
SendGameState();
|
|
}
|
|
|
|
/**
|
|
* <summary>
|
|
* Based on the played card, change the turn to the next player.
|
|
* </summary>
|
|
* <param name="card">The card that was played</param>
|
|
*/
|
|
private void HandleNextPlayer(Card card)
|
|
{
|
|
switch (card.CardValue)
|
|
{
|
|
case CardValue.RED:
|
|
case CardValue.BLACK:
|
|
{
|
|
_turnManager.GetNextPlayer().GiveCards(_deck.DrawCards(5));
|
|
_turnManager.CurrentPlayer.State = PlayerState.CHOOSE;
|
|
break;
|
|
}
|
|
case CardValue.TWO:
|
|
{
|
|
_turnManager.GetNextPlayer().GiveCards(_deck.DrawCards(2));
|
|
_turnManager.ChangeTurn(_turnManager.GetNextPlayer(2));
|
|
break;
|
|
}
|
|
case CardValue.SEVEN:
|
|
case CardValue.KING:
|
|
break;
|
|
case CardValue.EIGHT:
|
|
_turnManager.ChangeTurn(_turnManager.GetNextPlayer(2));
|
|
break;
|
|
case CardValue.ACE:
|
|
if (_turnManager.Players.Count > 2)
|
|
{
|
|
_turnManager.ChangeDirection();
|
|
_turnManager.ChangeTurn();
|
|
}
|
|
break;
|
|
case CardValue.JACK:
|
|
_turnManager.CurrentPlayer.State = PlayerState.CHOOSE;
|
|
break;
|
|
case CardValue.THREE:
|
|
case CardValue.FOUR:
|
|
case CardValue.FIVE:
|
|
case CardValue.SIX:
|
|
case CardValue.NINE:
|
|
case CardValue.TEN:
|
|
case CardValue.QUEEN:
|
|
default:
|
|
_turnManager.ChangeTurn();
|
|
break;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* <summary>
|
|
* 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).
|
|
* </summary>
|
|
*/
|
|
private void SendGameState()
|
|
{
|
|
foreach (var player in _turnManager.Players)
|
|
{
|
|
var gameState = new GameState(player, _deck.CurrentCard, _turnManager.CurrentPlayer, _turnManager.Players);
|
|
player.Connection.SendMessageAsync(JsonConvert.SerializeObject(new RoomMessage<GameState>("GAME", gameState)));
|
|
}
|
|
}
|
|
|
|
/**
|
|
* <summary>
|
|
* Broadcast the winner of the game and change the room's type to a lobby.
|
|
* </summary>
|
|
* <param name="winner">The player that won the game</param>
|
|
*/
|
|
private void EndGame(Player winner)
|
|
{
|
|
var winMessage = new EndMessage(winner.Connection.Id, winner.Connection.Name);
|
|
_room.BroadCast(new RoomMessage<EndMessage>("END", winMessage));
|
|
_room.RoomType = new Lobby(_room);
|
|
}
|
|
} |