namespace Faraon.Core.Engine; using Faraon.Core.Models; public class GameEngine { private readonly Deck _deckManager; public GameState State { get; } public event Action? OnGameEvent; public GameEngine(int? seed = null) { _deckManager = new Deck(seed); State = new GameState(); } public void StartGame(List 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.IsMatchOver = false; State.IsRoundOver = false; State.MatchWinner = null; State.RoundLoser = null; State.RoundNumber = 1; 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.OlympicCardsCount = State.Rules.InitialCardCount; player.Reset(); } // Vytvorenie a zamiešanie balíčka State.DrawDeck = _deckManager.CreateShuffledDeck(); State.DiscardPile.Clear(); // Rozdanie kariet každému aktívnemu hráčovi podľa jeho OlympicCardsCount foreach (var player in State.ActiveTournamentPlayers) { for (int i = 0; i < player.OlympicCardsCount; i++) { 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 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 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 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 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 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(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 { 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 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) { // Zostávajúci hráč, ktorému ostali karty (porazený tohto kola) var lastPlayer = State.ActiveTournamentPlayers.FirstOrDefault(p => !p.HasFinished); if (lastPlayer != null) { lastPlayer.FinishedPosition = State.ActiveTournamentPlayers.Count; State.RoundLoser = lastPlayer; } if (State.Rules.OlympicMode) { if (lastPlayer != null) { lastPlayer.OlympicCardsCount--; if (lastPlayer.IsEliminated) { EmitEvent(new GameEvent( GameEventType.PlayerEliminated, lastPlayer, Message: $"☠️ {lastPlayer.Name} stratil všetky karty a vypadáva z Olympijského turnaja!" )); } } var remainingTournamentPlayers = State.ActiveTournamentPlayers; if (remainingTournamentPlayers.Count <= 1) { // Koniec turnaja! Zostal iba 1 víťaz State.IsGameOver = true; State.IsMatchOver = true; State.MatchWinner = remainingTournamentPlayers.FirstOrDefault() ?? lastPlayer; State.TurnState = TurnState.GameOver; EmitEvent(new GameEvent( GameEventType.MatchWon, State.MatchWinner, Message: $"🥇 {State.MatchWinner?.Name} sa stal celkovým OLYMPIJSKÝM ŠAMPIÓNOM!" )); } else { // Koniec jedného kola, nasleduje ďalšie State.IsRoundOver = true; State.TurnState = TurnState.RoundOver; EmitEvent(new GameEvent( GameEventType.RoundOver, lastPlayer, ExtraData: State.RoundNumber, Message: $"🏅 Kolo #{State.RoundNumber} sa skončilo! {lastPlayer?.Name} bude mať v ďalšom kole {lastPlayer?.OlympicCardsCount} kariet." )); } } else { // Bežná jednoduchá hra State.IsGameOver = true; State.TurnState = TurnState.GameOver; EmitEvent(new GameEvent( GameEventType.GameOver, null, Message: "Koniec hry! Všetky pozície sú určené." )); } } } public void StartNextRound() { if (!State.Rules.OlympicMode || State.IsMatchOver) return; State.RoundNumber++; State.IsRoundOver = false; State.IsGameOver = false; State.RoundLoser = null; State.TurnState = TurnState.Playing; State.TurnCount = 1; State.PendingDrawCount = 0; State.PendingSkip = false; State.ActiveSuit = null; State.PendingHornikPlayer = null; foreach (var p in State.Players) { p.Reset(); } // Vytvorenie a zamiešanie nového balíčka State.DrawDeck = _deckManager.CreateShuffledDeck(); State.DiscardPile.Clear(); // Rozdanie kariet len aktívnym (nevyradeným) hráčom podľa ich OlympicCardsCount foreach (var player in State.ActiveTournamentPlayers) { for (int i = 0; i < player.OlympicCardsCount; i++) { 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 var startCard = State.DrawDeck[0]; State.DrawDeck.RemoveAt(0); State.DiscardPile.Add(startCard); // Nastavíme prvého hráča na prvého aktívneho var firstActiveIndex = State.Players.FindIndex(p => !p.IsEliminated); State.CurrentPlayerIndex = firstActiveIndex >= 0 ? firstActiveIndex : 0; EmitEvent(new GameEvent( GameEventType.GameStarted, State.CurrentPlayer, Card: startCard, Message: $"🏅 Začalo sa Kolo #{State.RoundNumber} Olympijského Faraóna!" )); } private void AdvanceTurn() { if (State.IsGameOver || State.IsRoundOver) return; int attempts = 0; do { State.CurrentPlayerIndex = (State.CurrentPlayerIndex + 1) % State.Players.Count; attempts++; } while ((State.CurrentPlayer.HasFinished || State.CurrentPlayer.IsEliminated) && attempts <= State.Players.Count * 2); 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.ActiveTournamentPlayers.Count(p => p.Type == PlayerType.Human) > 1) { State.TurnState = TurnState.PassScreen; } else { State.TurnState = TurnState.Playing; } } private void EmitEvent(GameEvent e) { OnGameEvent?.Invoke(e); } }