inial impl
This commit is contained in:
@@ -0,0 +1,601 @@
|
||||
namespace Faraon.Core.Engine;
|
||||
|
||||
using Faraon.Core.Models;
|
||||
|
||||
public class GameEngine
|
||||
{
|
||||
private readonly Deck _deckManager;
|
||||
public GameState State { get; }
|
||||
|
||||
public event Action<GameEvent>? OnGameEvent;
|
||||
|
||||
public GameEngine(int? seed = null)
|
||||
{
|
||||
_deckManager = new Deck(seed);
|
||||
State = new GameState();
|
||||
}
|
||||
|
||||
public void StartGame(List<Player> players, RuleOptions rules)
|
||||
{
|
||||
if (players.Count < 2 || players.Count > 4)
|
||||
{
|
||||
throw new ArgumentException("Hra Faraón vyžaduje 2 až 4 hráčov.", nameof(players));
|
||||
}
|
||||
|
||||
State.Players = players;
|
||||
State.Rules = rules;
|
||||
State.IsGameOver = false;
|
||||
State.LastFinishedPlayer = null;
|
||||
State.TurnCount = 1;
|
||||
State.PendingDrawCount = 0;
|
||||
State.PendingSkip = false;
|
||||
State.ActiveSuit = null;
|
||||
State.PendingHornikPlayer = null;
|
||||
State.TurnState = TurnState.Playing;
|
||||
|
||||
foreach (var player in State.Players)
|
||||
{
|
||||
player.Reset();
|
||||
}
|
||||
|
||||
// Vytvorenie a zamiešanie balíčka
|
||||
State.DrawDeck = _deckManager.CreateShuffledDeck();
|
||||
State.DiscardPile.Clear();
|
||||
|
||||
// Rozdanie kariet každému hráčovi
|
||||
for (int i = 0; i < State.Rules.InitialCardCount; i++)
|
||||
{
|
||||
foreach (var player in State.Players)
|
||||
{
|
||||
if (State.DrawDeck.Count > 0)
|
||||
{
|
||||
var card = State.DrawDeck[0];
|
||||
State.DrawDeck.RemoveAt(0);
|
||||
player.Hand.Add(card);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Otočenie prvej karty na odhadzovaciu kôpku
|
||||
// Ak je prvá karta sedma, eso alebo horník, v základnej hre ju berieme ako štartovaciu bez okamžitého trestu
|
||||
var startCard = State.DrawDeck[0];
|
||||
State.DrawDeck.RemoveAt(0);
|
||||
State.DiscardPile.Add(startCard);
|
||||
|
||||
State.CurrentPlayerIndex = 0;
|
||||
|
||||
EmitEvent(new GameEvent(
|
||||
GameEventType.GameStarted,
|
||||
State.CurrentPlayer,
|
||||
Card: startCard,
|
||||
Message: "Hra sa začala!"
|
||||
));
|
||||
}
|
||||
|
||||
public bool CanPlayCard(Player player, Card card)
|
||||
{
|
||||
if (State.IsGameOver || State.TurnState != TurnState.Playing) return false;
|
||||
if (player != State.CurrentPlayer) return false;
|
||||
if (!player.Hand.Contains(card)) return false;
|
||||
|
||||
return ActionValidator.CanPlayCard(
|
||||
card,
|
||||
State.TopCard,
|
||||
State.ActiveSuit,
|
||||
State.PendingDrawCount,
|
||||
State.PendingSkip,
|
||||
State.Rules
|
||||
);
|
||||
}
|
||||
|
||||
public bool CanBurnCards(Player player, List<Card> cards)
|
||||
{
|
||||
if (State.IsGameOver || State.TurnState != TurnState.Playing) return false;
|
||||
if (player != State.CurrentPlayer) return false;
|
||||
if (cards.Any(c => !player.Hand.Contains(c))) return false;
|
||||
|
||||
return ActionValidator.CanBurnCards(
|
||||
cards,
|
||||
State.PendingDrawCount,
|
||||
State.PendingSkip,
|
||||
State.Rules
|
||||
);
|
||||
}
|
||||
|
||||
public bool CanPlayCards(Player player, List<Card> cards)
|
||||
{
|
||||
if (State.IsGameOver || State.TurnState != TurnState.Playing) return false;
|
||||
if (player != State.CurrentPlayer) return false;
|
||||
if (cards == null || cards.Count == 0) return false;
|
||||
if (cards.Any(c => !player.Hand.Contains(c))) return false;
|
||||
|
||||
return ActionValidator.CanPlayCards(
|
||||
cards,
|
||||
State.TopCard,
|
||||
State.ActiveSuit,
|
||||
State.PendingDrawCount,
|
||||
State.PendingSkip,
|
||||
State.Rules
|
||||
);
|
||||
}
|
||||
|
||||
public bool PlayCards(Player player, List<Card> cards, Suit? chosenSuit = null)
|
||||
{
|
||||
if (!CanPlayCards(player, cards)) return false;
|
||||
|
||||
if (cards.Count == 1)
|
||||
{
|
||||
return PlayCard(player, cards[0], chosenSuit);
|
||||
}
|
||||
|
||||
// 4 rovnaké karty pri pravidle spálenia
|
||||
if (cards.Count == 4 && State.Rules.FourOfAKindBurns && State.PendingDrawCount == 0 && !State.PendingSkip)
|
||||
{
|
||||
return BurnCards(player, cards);
|
||||
}
|
||||
|
||||
// Zoradíme karty tak, aby prvá vyložená karta legálne sadla na stôl a posledná ostala na vrchu
|
||||
List<Card> orderedCards;
|
||||
if (ActionValidator.CanPlayCard(cards[0], State.TopCard, State.ActiveSuit, State.PendingDrawCount, State.PendingSkip, State.Rules))
|
||||
{
|
||||
// Prvá karta zvolená hráčom priamo sedí na stôl -> zachováme presné poradie hráča
|
||||
orderedCards = new List<Card>(cards);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Nájdeme kartu, ktorá legálne viaže combo na stôl, a dáme ju na začiatok
|
||||
var matching = cards.FirstOrDefault(c => ActionValidator.CanPlayCard(
|
||||
c, State.TopCard, State.ActiveSuit, State.PendingDrawCount, State.PendingSkip, State.Rules)) ?? cards[0];
|
||||
|
||||
orderedCards = new List<Card> { matching };
|
||||
foreach (var c in cards)
|
||||
{
|
||||
if (c != matching)
|
||||
{
|
||||
orderedCards.Add(c);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var c in orderedCards)
|
||||
{
|
||||
player.Hand.Remove(c);
|
||||
State.DiscardPile.Add(c);
|
||||
}
|
||||
|
||||
var firstRank = cards[0].Rank;
|
||||
State.ActiveSuit = null;
|
||||
|
||||
// Efekt Horníkov
|
||||
if (firstRank == Rank.OverKnave)
|
||||
{
|
||||
if (chosenSuit.HasValue)
|
||||
{
|
||||
State.ActiveSuit = chosenSuit.Value;
|
||||
EmitEvent(new GameEvent(
|
||||
GameEventType.SuitChanged,
|
||||
player,
|
||||
Cards: cards,
|
||||
ChosenSuit: chosenSuit.Value,
|
||||
Message: $"{player.Name} zahral {cards.Count}x Horníka a zmenil farbu!"
|
||||
));
|
||||
}
|
||||
else
|
||||
{
|
||||
State.TurnState = TurnState.SelectingSuit;
|
||||
State.PendingHornikPlayer = player;
|
||||
EmitEvent(new GameEvent(
|
||||
GameEventType.CardPlayed,
|
||||
player,
|
||||
Cards: cards,
|
||||
Message: $"{player.Name} zahral {cards.Count}x Horníka a vyberá farbu..."
|
||||
));
|
||||
return true;
|
||||
}
|
||||
}
|
||||
// Efekt Sediem
|
||||
else if (firstRank == Rank.Seven)
|
||||
{
|
||||
int addedPenalty = cards.Count * 2;
|
||||
State.PendingDrawCount += addedPenalty;
|
||||
|
||||
var redSeven = cards.FirstOrDefault(c => c.IsRedSeven);
|
||||
if (redSeven != null && State.Rules.RedSevenRevives && State.LastFinishedPlayer != null && State.LastFinishedPlayer != player)
|
||||
{
|
||||
var revived = State.LastFinishedPlayer;
|
||||
revived.FinishedPosition = null;
|
||||
State.LastFinishedPlayer = null;
|
||||
DrawCardsForPlayer(revived, 3);
|
||||
|
||||
EmitEvent(new GameEvent(
|
||||
GameEventType.PlayerRevived,
|
||||
player,
|
||||
TargetPlayer: revived,
|
||||
Cards: cards,
|
||||
ExtraData: 3,
|
||||
Message: $"🔥 {player.Name} zahral combo {cards.Count}x Sedma a vrátil hráča {revived.Name} späť do hry!"
|
||||
));
|
||||
}
|
||||
else
|
||||
{
|
||||
EmitEvent(new GameEvent(
|
||||
GameEventType.SevenStacked,
|
||||
player,
|
||||
Cards: cards,
|
||||
ExtraData: State.PendingDrawCount,
|
||||
Message: $"⚡ COMBO {cards.Count}x Sedma! Trest stúpol na {State.PendingDrawCount} kariet!"
|
||||
));
|
||||
}
|
||||
}
|
||||
// Efekt Es
|
||||
else if (firstRank == Rank.Ace)
|
||||
{
|
||||
bool wasPending = State.PendingSkip;
|
||||
State.PendingSkip = true;
|
||||
|
||||
if (wasPending)
|
||||
{
|
||||
EmitEvent(new GameEvent(
|
||||
GameEventType.AceCountered,
|
||||
player,
|
||||
Cards: cards,
|
||||
Message: $"❄️ {player.Name} prebil Eso {cards.Count}x Esom!"
|
||||
));
|
||||
}
|
||||
else
|
||||
{
|
||||
EmitEvent(new GameEvent(
|
||||
GameEventType.AceSkipped,
|
||||
player,
|
||||
Cards: cards,
|
||||
Message: $"❄️ COMBO {cards.Count}x Eso! Ďalší hráč stojí."
|
||||
));
|
||||
}
|
||||
}
|
||||
// Ostatné karty
|
||||
else
|
||||
{
|
||||
EmitEvent(new GameEvent(
|
||||
GameEventType.CardPlayed,
|
||||
player,
|
||||
Cards: cards,
|
||||
Message: $"{player.Name} zahral combo {cards.Count}x {cards[0].RankName}!"
|
||||
));
|
||||
}
|
||||
|
||||
CheckPlayerFinish(player);
|
||||
AdvanceTurn();
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool PlayCard(Player player, Card card, Suit? chosenSuit = null)
|
||||
{
|
||||
if (!CanPlayCard(player, card)) return false;
|
||||
|
||||
// Odstránenie z ruky
|
||||
player.Hand.Remove(card);
|
||||
State.DiscardPile.Add(card);
|
||||
|
||||
// Ak bola predtým zmenená farba, zrušíme ju (alebo prepíšeme novým horníkom)
|
||||
State.ActiveSuit = null;
|
||||
|
||||
// Efekt Horníka
|
||||
if (card.IsHornik)
|
||||
{
|
||||
if (chosenSuit.HasValue)
|
||||
{
|
||||
State.ActiveSuit = chosenSuit.Value;
|
||||
EmitEvent(new GameEvent(
|
||||
GameEventType.SuitChanged,
|
||||
player,
|
||||
Card: card,
|
||||
ChosenSuit: chosenSuit.Value,
|
||||
Message: $"{player.Name} zahral Horníka a zmenil farbu na {card.SuitName}!"
|
||||
));
|
||||
}
|
||||
else
|
||||
{
|
||||
// Čakáme na výber farby
|
||||
State.TurnState = TurnState.SelectingSuit;
|
||||
State.PendingHornikPlayer = player;
|
||||
EmitEvent(new GameEvent(
|
||||
GameEventType.CardPlayed,
|
||||
player,
|
||||
Card: card,
|
||||
Message: $"{player.Name} zahral Horníka a vyberá farbu..."
|
||||
));
|
||||
return true;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
EmitEvent(new GameEvent(
|
||||
GameEventType.CardPlayed,
|
||||
player,
|
||||
Card: card,
|
||||
Message: $"{player.Name} zahral {card.Name}."
|
||||
));
|
||||
}
|
||||
|
||||
// Efekt Sedmy
|
||||
if (card.IsSeven)
|
||||
{
|
||||
State.PendingDrawCount += 2;
|
||||
|
||||
// Červená sedma vracia do hry
|
||||
if (card.IsRedSeven && State.Rules.RedSevenRevives && State.LastFinishedPlayer != null && State.LastFinishedPlayer != player)
|
||||
{
|
||||
var revived = State.LastFinishedPlayer;
|
||||
revived.FinishedPosition = null;
|
||||
State.LastFinishedPlayer = null;
|
||||
|
||||
// Doberie 3 karty
|
||||
DrawCardsForPlayer(revived, 3);
|
||||
|
||||
EmitEvent(new GameEvent(
|
||||
GameEventType.PlayerRevived,
|
||||
player,
|
||||
TargetPlayer: revived,
|
||||
Card: card,
|
||||
ExtraData: 3,
|
||||
Message: $"🔥 {player.Name} zahral Červenú sedmu a vrátil hráča {revived.Name} späť do hry!"
|
||||
));
|
||||
}
|
||||
else
|
||||
{
|
||||
EmitEvent(new GameEvent(
|
||||
GameEventType.SevenStacked,
|
||||
player,
|
||||
Card: card,
|
||||
ExtraData: State.PendingDrawCount,
|
||||
Message: $"⚡ Sedma! Ďalší hráč berie {State.PendingDrawCount} karty."
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
// Efekt Esa
|
||||
if (card.IsAce)
|
||||
{
|
||||
if (State.PendingSkip)
|
||||
{
|
||||
EmitEvent(new GameEvent(
|
||||
GameEventType.AceCountered,
|
||||
player,
|
||||
Card: card,
|
||||
Message: $"❄️ {player.Name} prebil Eso vlastným Esom!"
|
||||
));
|
||||
}
|
||||
else
|
||||
{
|
||||
EmitEvent(new GameEvent(
|
||||
GameEventType.AceSkipped,
|
||||
player,
|
||||
Card: card,
|
||||
Message: $"❄️ Eso! Ďalší hráč jedno kolo stojí."
|
||||
));
|
||||
}
|
||||
State.PendingSkip = true;
|
||||
}
|
||||
|
||||
// Kontrola, či hráč vyhral / skončil
|
||||
CheckPlayerFinish(player);
|
||||
|
||||
// Posun na ďalšieho hráča
|
||||
AdvanceTurn();
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool ChooseSuit(Player player, Suit suit)
|
||||
{
|
||||
if (State.TurnState != TurnState.SelectingSuit || State.PendingHornikPlayer != player)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
State.ActiveSuit = suit;
|
||||
State.TurnState = TurnState.Playing;
|
||||
State.PendingHornikPlayer = null;
|
||||
|
||||
var dummyCard = new Card(suit, Rank.OverKnave);
|
||||
EmitEvent(new GameEvent(
|
||||
GameEventType.SuitChanged,
|
||||
player,
|
||||
ChosenSuit: suit,
|
||||
Message: $"{player.Name} zmenil farbu na {dummyCard.SuitName} ({dummyCard.SuitSymbol})!"
|
||||
));
|
||||
|
||||
// Kontrola, či hráč vyhodením Horníka skončil
|
||||
CheckPlayerFinish(player);
|
||||
|
||||
AdvanceTurn();
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool BurnCards(Player player, List<Card> cards)
|
||||
{
|
||||
if (!CanBurnCards(player, cards)) return false;
|
||||
|
||||
var rank = cards[0].Rank;
|
||||
foreach (var c in cards)
|
||||
{
|
||||
player.Hand.Remove(c);
|
||||
State.DiscardPile.Add(c);
|
||||
}
|
||||
|
||||
State.ActiveSuit = null;
|
||||
|
||||
EmitEvent(new GameEvent(
|
||||
GameEventType.CardsBurned,
|
||||
player,
|
||||
Cards: cards,
|
||||
Message: $"💥 SPÁLENIE! {player.Name} spálil 4x {cards[0].RankName} a hrá znova!"
|
||||
));
|
||||
|
||||
CheckPlayerFinish(player);
|
||||
|
||||
// Pri spálení hráč pokračuje vo svojom ťahu (nevolá sa AdvanceTurn)
|
||||
return true;
|
||||
}
|
||||
|
||||
public void DrawOrAcknowledge(Player player)
|
||||
{
|
||||
if (State.IsGameOver || State.TurnState != TurnState.Playing) return;
|
||||
if (player != State.CurrentPlayer) return;
|
||||
|
||||
if (State.PendingDrawCount > 0)
|
||||
{
|
||||
int count = State.PendingDrawCount;
|
||||
State.PendingDrawCount = 0;
|
||||
DrawCardsForPlayer(player, count);
|
||||
|
||||
EmitEvent(new GameEvent(
|
||||
GameEventType.CardDrawn,
|
||||
player,
|
||||
ExtraData: count,
|
||||
Message: $"{player.Name} si potiahol {count} trestné karty zo sedmy."
|
||||
));
|
||||
AdvanceTurn();
|
||||
}
|
||||
else if (State.PendingSkip)
|
||||
{
|
||||
State.PendingSkip = false;
|
||||
|
||||
EmitEvent(new GameEvent(
|
||||
GameEventType.AceSkipped,
|
||||
player,
|
||||
Message: $"{player.Name} stojí toto kolo kvôli Esu."
|
||||
));
|
||||
AdvanceTurn();
|
||||
}
|
||||
else
|
||||
{
|
||||
// Bežné potiahnutie 1 karty
|
||||
DrawCardsForPlayer(player, 1);
|
||||
|
||||
EmitEvent(new GameEvent(
|
||||
GameEventType.CardDrawn,
|
||||
player,
|
||||
ExtraData: 1,
|
||||
Message: $"{player.Name} si potiahol kartu z balíčka."
|
||||
));
|
||||
AdvanceTurn();
|
||||
}
|
||||
}
|
||||
|
||||
public void ContinueFromPassScreen()
|
||||
{
|
||||
if (State.TurnState == TurnState.PassScreen)
|
||||
{
|
||||
State.TurnState = TurnState.Playing;
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawCardsForPlayer(Player player, int count)
|
||||
{
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
EnsureDrawDeckHasCards();
|
||||
if (State.DrawDeck.Count > 0)
|
||||
{
|
||||
var card = State.DrawDeck[0];
|
||||
State.DrawDeck.RemoveAt(0);
|
||||
player.Hand.Add(card);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void EnsureDrawDeckHasCards()
|
||||
{
|
||||
if (State.DrawDeck.Count > 0) return;
|
||||
|
||||
// Ak je balíček prázdny, vezmeme odhadzovaciu kôpku okrem vrchnej karty
|
||||
if (State.DiscardPile.Count > 1)
|
||||
{
|
||||
var topCard = State.DiscardPile[^1];
|
||||
var cardsToRecycle = State.DiscardPile.Take(State.DiscardPile.Count - 1).ToList();
|
||||
State.DiscardPile.Clear();
|
||||
State.DiscardPile.Add(topCard);
|
||||
|
||||
_deckManager.Shuffle(cardsToRecycle);
|
||||
State.DrawDeck.AddRange(cardsToRecycle);
|
||||
|
||||
EmitEvent(new GameEvent(
|
||||
GameEventType.DeckReshuffled,
|
||||
null,
|
||||
Message: "Lízací balíček bol premiešaný z odhadzovacej kôpky."
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
private void CheckPlayerFinish(Player player)
|
||||
{
|
||||
if (player.Hand.Count == 0 && !player.HasFinished)
|
||||
{
|
||||
int nextPos = State.Players.Count(p => p.HasFinished) + 1;
|
||||
player.FinishedPosition = nextPos;
|
||||
State.LastFinishedPlayer = player;
|
||||
|
||||
EmitEvent(new GameEvent(
|
||||
GameEventType.PlayerFinished,
|
||||
player,
|
||||
ExtraData: nextPos,
|
||||
Message: $"🏆 {player.Name} sa zbavil všetkých kariet a obsadil {nextPos}. miesto!"
|
||||
));
|
||||
|
||||
CheckGameOver();
|
||||
}
|
||||
}
|
||||
|
||||
private void CheckGameOver()
|
||||
{
|
||||
int remaining = State.ActivePlayersCount;
|
||||
if (remaining <= 1)
|
||||
{
|
||||
State.IsGameOver = true;
|
||||
State.TurnState = TurnState.GameOver;
|
||||
|
||||
// Zostávajúcemu hráčovi priradíme posledné miesto
|
||||
var lastPlayer = State.Players.FirstOrDefault(p => !p.HasFinished);
|
||||
if (lastPlayer != null)
|
||||
{
|
||||
lastPlayer.FinishedPosition = State.Players.Count;
|
||||
}
|
||||
|
||||
EmitEvent(new GameEvent(
|
||||
GameEventType.GameOver,
|
||||
null,
|
||||
Message: "Koniec hry! Všetky pozície sú určené."
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
private void AdvanceTurn()
|
||||
{
|
||||
if (State.IsGameOver) return;
|
||||
|
||||
int attempts = 0;
|
||||
do
|
||||
{
|
||||
State.CurrentPlayerIndex = (State.CurrentPlayerIndex + 1) % State.Players.Count;
|
||||
attempts++;
|
||||
}
|
||||
while (State.CurrentPlayer.HasFinished && attempts <= State.Players.Count);
|
||||
|
||||
State.TurnCount++;
|
||||
|
||||
// Ak hráme Pass & Play a nasledujúci hráč je človek a je zapnutá súkromná clona
|
||||
if (State.Rules.PassAndPlayPrivacyScreen && State.CurrentPlayer.Type == PlayerType.Human && State.Players.Count(p => p.Type == PlayerType.Human) > 1)
|
||||
{
|
||||
State.TurnState = TurnState.PassScreen;
|
||||
}
|
||||
else
|
||||
{
|
||||
State.TurnState = TurnState.Playing;
|
||||
}
|
||||
}
|
||||
|
||||
private void EmitEvent(GameEvent e)
|
||||
{
|
||||
OnGameEvent?.Invoke(e);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user