@page "/" @inject FxService Fx @inject IFaraonAI AI @inject IJSRuntime JS @if (IsInLobby || Engine == null) { } else {

FARAÓN

@if (Engine.State.Rules.OlympicMode) { 🏅 KOLO #@Engine.State.RoundNumber }
Ťah #@Engine.State.TurnCount • Na rade: @Engine.State.CurrentPlayer.Avatar @Engine.State.CurrentPlayer.Name
@foreach (var opp in GetOpponents()) { var isOppFrozen = Engine.State.PendingSkip && Engine.State.CurrentPlayer == opp.Player; var canOppBeRevived = Engine.State.Rules.RedSevenRevives && Engine.State.LastFinishedPlayer == opp.Player && Engine.State.ReviveTurnsRemaining > 0; }
@Engine.State.DrawDeck.Count
@if (Engine.State.TopCard != null) { } @if (Engine.State.PendingSkip) {
❄️ ESO: ZMRAZENIE!
} @if (Engine.State.PendingDrawCount > 0) {
🔥 +@Engine.State.PendingDrawCount Kariet!
} @if (Engine.State.ActiveSuit.HasValue) { var dummy = new Card(Engine.State.ActiveSuit.Value, Rank.OverKnave);
Farba:
@dummy.SuitName
} @if (Engine.State.TopCard != null && Engine.State.TopCard.IsGreenUnderKnave && Engine.State.Rules.GreenUnderKnaveMode == GreenUnderKnaveMode.UniversalWildcard) {
🍃 ZELENÝ DOLNÍK: Daj čokoľvek!
}
@{ var isBottomFrozen = Engine.State.PendingSkip && IsViewingPlayerActiveTurn; var canBottomBeRevived = Engine.State.Rules.RedSevenRevives && Engine.State.LastFinishedPlayer == ViewingPlayer && Engine.State.ReviveTurnsRemaining > 0; }
@if (isBottomFrozen && !ViewingPlayer.HasFinished && !ViewingPlayer.IsEliminated) {
@(ViewingPlayer.Type == PlayerType.Human ? "❄️ STOJÍŠ TOTO KOLO" : "❄️ ZMRAZENÝ")
} else if (IsViewingPlayerActiveTurn && !ViewingPlayer.HasFinished && !ViewingPlayer.IsEliminated) {
@(ViewingPlayer.Type == PlayerType.Human ? "SI NA ŤAHU" : "NA ŤAHU")
} @if (ViewingPlayer.HasFinished) {
@ViewingPlayer.FinishedPosition
}
@ViewingPlayer.Avatar @ViewingPlayer.Name @if (ViewingPlayer.Type == PlayerType.AI) { (AI) } (@ViewingPlayer.Hand.Count @(ViewingPlayer.Hand.Count == 1 ? "karta" : (ViewingPlayer.Hand.Count < 5 ? "karty" : "kariet"))) @if (Engine.State.Rules.OlympicMode) { 🏅 @ViewingPlayer.OlympicCardsCount @(ViewingPlayer.OlympicCardsCount == 1 ? "karta" : (ViewingPlayer.OlympicCardsCount < 5 ? "karty" : "kariet")) }
@if (isBottomFrozen && !ViewingPlayer.HasFinished && !ViewingPlayer.IsEliminated) {
} @if (canBottomBeRevived && ViewingPlayer.HasFinished) {
❤️ @(Engine.State.ReviveRoundsRemaining > 1 ? $"Oživenie: {Engine.State.ReviveRoundsRemaining} kolá" : (Engine.State.ReviveTurnsRemaining <= 1 ? "Oživenie: 1 ťah" : "Oživenie: 1 kolo"))
}
@if (!ViewingPlayer.IsEliminated) { } else {
☠️ Bol si vyradený z Olympijského turnaja. Sleduj zápas až do konca!
}
@if (IsViewingPlayerTurn && Engine.State.TurnState == TurnState.Playing && !ViewingPlayer.IsEliminated) {
@if (CheckFourOfAKindRank(ViewingPlayer) is Rank burnRank) { }
} @if (!string.IsNullOrEmpty(LastEventMessage)) {
@LastEventMessage
} @if (Engine.State.TurnState == TurnState.SelectingSuit && Engine.State.PendingHornikPlayer == ViewingPlayer) { } @if (Engine.State.TurnState == TurnState.PassScreen) { } @if (Engine.State.TurnState == TurnState.RoundOver) { } @if (Engine.State.IsGameOver) { }
} @code { private bool IsInLobby = true; private bool IsMuted = false; private GameEngine? Engine; private string LastEventMessage = ""; private System.Threading.CancellationTokenSource? _aiCts; private List ConfiguredPlayers = new(); private RuleOptions ConfiguredRules = new(); private Player? _previousTurnPlayer; private Player ViewingPlayer { get { if (Engine == null || Engine.State.Players.Count == 0) return new Player(); // Ak je Pass & Play a aktuálny hráč je človek, zobrazujeme jeho ruku if (Engine.State.CurrentPlayer.Type == PlayerType.Human) { return Engine.State.CurrentPlayer; } // Inak primárne prvého živého človeka v hre var firstHuman = Engine.State.Players.FirstOrDefault(p => p.Type == PlayerType.Human); if (firstHuman != null) return firstHuman; // Ak sú všetci hráči AI, spodný hráč je PEVNE prvý hráč (Players[0]), aby sa stôl a rozmiestnenie nemenili return Engine.State.Players[0]; } } private bool IsViewingPlayerActiveTurn => Engine != null && Engine.State.TurnState == TurnState.Playing && Engine.State.CurrentPlayer == ViewingPlayer; private bool IsViewingPlayerTurn => IsViewingPlayerActiveTurn && ViewingPlayer.Type == PlayerType.Human; protected override async Task OnAfterRenderAsync(bool firstRender) { if (firstRender) { await Fx.InitParticlesAsync(); } } private void HandleStartGame((List players, RuleOptions rules) setup) { ConfiguredPlayers = setup.players; ConfiguredRules = setup.rules; StartNewGame(); } private void StartNewGame() { // Klony hráčov pre čistú hru var gamePlayers = ConfiguredPlayers.Select(p => new Player(p.Name, p.Type, p.Avatar, p.Difficulty)).ToList(); Engine = new GameEngine(); Engine.OnGameEvent += HandleGameEvent; Engine.StartGame(gamePlayers, ConfiguredRules); _previousTurnPlayer = Engine.State.CurrentPlayer; _ = Fx.SetSpeedLevelAsync(Engine.State.Rules.SpeedLevel); IsInLobby = false; LastEventMessage = "Hra začala! Veľa šťastia."; StateHasChanged(); CheckTriggerAI(); } private void HandleGameEvent(GameEvent e) { LastEventMessage = e.Message; _ = InvokeAsync(async () => { switch (e.Type) { case GameEventType.CardPlayed: await Fx.PlayCardSnapAsync(); break; case GameEventType.CardDrawn: await Fx.PlayCardDrawAsync(); break; case GameEventType.SevenStacked: await Fx.PlaySevenStackAsync(Math.Max(1, e.ExtraData / 3)); await Fx.TriggerDopamineBannerAsync($"🔥 +{e.ExtraData} KARIET!", "Trestná reťaz sediem", "triple"); break; case GameEventType.SevenCanceled: await Fx.PlayLeafShieldAsync(); await Fx.TriggerDopamineBannerAsync("🍃 TREST ZRUŠENÝ!", $"{e.SourcePlayer?.Name ?? "Hráč"} zrušil trest Zeleným dolníkom!", "triple"); break; case GameEventType.AceSkipped: if (e.Message.Contains("stojí toto kolo")) { if (e.SourcePlayer != null) { await Fx.TriggerIceShatterAsync(GetPlayerPosition(e.SourcePlayer)); } } else { await Fx.TriggerFreezeEffectAsync(); } break; case GameEventType.AceCountered: if (e.SourcePlayer != null) { await Fx.TriggerIceShatterAsync(GetPlayerPosition(e.SourcePlayer)); await Fx.TriggerDopamineBannerAsync("⚡ ROZMRAZENÝ!", $"{e.SourcePlayer.Name} prebil Eso vlastným Esom!", "revive"); } break; case GameEventType.PlayerRevived: await Fx.TriggerReviveEffectAsync(e.TargetPlayer?.Name ?? e.SourcePlayer?.Name ?? "Hráč"); break; case GameEventType.SuitChanged: await Fx.PlayHornikChimeAsync(); break; case GameEventType.CardsBurned: await Fx.PlayBurnExplosionAsync(); break; case GameEventType.PlayerFinished: case GameEventType.MatchWon: case GameEventType.GameOver: await Fx.TriggerJackpotWinAsync(e.SourcePlayer?.Name ?? "Víťaz"); break; } if (Engine != null && Engine.State.CurrentPlayer.Hand.Count == 1 && !Engine.State.CurrentPlayer.HasFinished) { await Fx.PlayHeartbeatAsync(); } await CheckTurnTransitionAsync(); StateHasChanged(); }); } private async Task CheckTurnTransitionAsync() { if (Engine == null || Engine.State.Players.Count == 0 || Engine.State.IsGameOver) return; var current = Engine.State.CurrentPlayer; if (_previousTurnPlayer != null && _previousTurnPlayer != current) { var prev = _previousTurnPlayer; _previousTurnPlayer = current; await Fx.AnimateTurnTransitionAsync(GetPlayerPosition(prev), GetPlayerPosition(current)); } else { _previousTurnPlayer = current; } } private string GetPlayerPosition(Player player) { if (player == ViewingPlayer) return "bottom"; var opp = GetOpponents().FirstOrDefault(o => o.Player == player); if (!string.IsNullOrEmpty(opp.PositionClass)) { return opp.PositionClass.Replace("slot-", ""); } return "top"; } private static readonly string[] AvailableDecks = new[] { "original", "janosik", "spongebob", "cyberpunk", "fantasy", "egypt", "pets", "pixel" }; private int MultiCardDelayMs => Engine?.State.Rules.SpeedLevel switch { 1 => 130, 2 => 105, 4 => 50, 5 => 25, _ => 80 }; private async Task CycleSpeed() { if (Engine == null) return; int next = (Engine.State.Rules.SpeedLevel % 5) + 1; Engine.State.Rules.SpeedLevel = next; await Fx.SetSpeedLevelAsync(next); StateHasChanged(); } private string GetSpeedButtonText(int level) => level switch { 1 => "🐢 0.5x", 2 => "🚶 0.75x", 3 => "⚡ 1x", 4 => "🔥 1.5x", 5 => "🚀 2.5x", _ => "⚡ 1x" }; private string GetSpeedDescription(int level) => level switch { 1 => "0.5x (Pomalá)", 2 => "0.75x (Pokojná)", 3 => "1x (Normálna - stredná)", 4 => "1.5x (Rýchla)", 5 => "2.5x (Turbo / Blesková)", _ => "1x (Normálna)" }; private void ToggleDeck() { if (Engine == null) return; int currentIdx = Array.IndexOf(AvailableDecks, Engine.State.Rules.SelectedDeck); int nextIdx = (currentIdx + 1) % AvailableDecks.Length; Engine.State.Rules.SelectedDeck = AvailableDecks[nextIdx]; StateHasChanged(); } private string GetDeckIcon(string deck) => deck switch { "janosik" => "🪓", "spongebob" => "🧽", "cyberpunk" => "⚡", "fantasy" => "🐉", "egypt" => "⚔️", "pets" => "🐱", "pixel" => "🕹️", _ => "🎴" }; private async Task HandlePlayCards(List cards) { if (Engine == null || cards.Count == 0) return; string pos = GetPlayerPosition(ViewingPlayer); string deck = Engine.State.Rules.SelectedDeck; if (cards.Count == 2) { _ = Fx.TriggerComboSlamAsync(2, "⚡ DVOJIČKA!"); } else if (cards.Count == 3) { _ = Fx.TriggerComboSlamAsync(3, "🔥 TROJITÝ ÚDER!"); } else if (cards.Count >= 4) { _ = Fx.TriggerComboSlamAsync(4, "👑 KVADRA / MEGA COMBO!"); } for (int i = 0; i < cards.Count; i++) { if (i == cards.Count - 1) { await Fx.AnimateCardPlayAsync(pos, cards[i], deck); } else { _ = Fx.AnimateCardPlayAsync(pos, cards[i], deck); await Task.Delay(MultiCardDelayMs); } } Engine.PlayCards(ViewingPlayer, cards); StateHasChanged(); CheckTriggerAI(); } private async Task HandleDeckClick() { if (Engine == null || !IsViewingPlayerTurn) return; string pos = GetPlayerPosition(ViewingPlayer); string deck = Engine.State.Rules.SelectedDeck; if (Engine.State.PendingDrawCount > 0) { int drawCount = Engine.State.PendingDrawCount; int staggerDelay = Math.Max(25, (int)(MultiCardDelayMs * 0.75)); for (int i = 0; i < drawCount; i++) { if (i == drawCount - 1) { await Fx.AnimateCardDrawAsync(pos, deck); } else { _ = Fx.AnimateCardDrawAsync(pos, deck); await Task.Delay(staggerDelay); } } } else if (!Engine.State.PendingSkip) { await Fx.AnimateCardDrawAsync(pos, deck); } Engine.DrawOrAcknowledge(ViewingPlayer); StateHasChanged(); CheckTriggerAI(); } private void HandleSuitSelected(Suit suit) { if (Engine == null) return; Engine.ChooseSuit(ViewingPlayer, suit); StateHasChanged(); CheckTriggerAI(); } private void HandlePassContinue() { if (Engine == null) return; Engine.ContinueFromPassScreen(); StateHasChanged(); CheckTriggerAI(); } private void HandleStartNextRound() { if (Engine == null) return; Engine.StartNextRound(); StateHasChanged(); CheckTriggerAI(); } private async Task HandleBurn(Rank rank) { if (Engine == null) return; var cards = ViewingPlayer.Hand.Where(c => c.Rank == rank).ToList(); if (cards.Count == 4) { string pos = GetPlayerPosition(ViewingPlayer); string deck = Engine.State.Rules.SelectedDeck; for (int i = 0; i < cards.Count; i++) { if (i == cards.Count - 1) { await Fx.AnimateCardPlayAsync(pos, cards[i], deck); } else { _ = Fx.AnimateCardPlayAsync(pos, cards[i], deck); await Task.Delay(MultiCardDelayMs); } } Engine.BurnCards(ViewingPlayer, cards); StateHasChanged(); CheckTriggerAI(); } } private Rank? CheckFourOfAKindRank(Player player) { if (Engine == null || !Engine.State.Rules.FourOfAKindBurns) return null; var group = player.Hand.GroupBy(c => c.Rank).FirstOrDefault(g => g.Count() == 4); return group?.Key; } private void CheckTriggerAI() { if (Engine == null || Engine.State.IsGameOver) return; if (Engine.State.TurnState == TurnState.Playing && Engine.State.CurrentPlayer.Type == PlayerType.AI) { _aiCts?.Cancel(); _aiCts = new System.Threading.CancellationTokenSource(); var token = _aiCts.Token; _ = Task.Run(async () => { try { await Task.Delay(Engine.State.Rules.AIDelayMs, token); if (token.IsCancellationRequested || Engine == null) return; var aiPlayer = Engine.State.CurrentPlayer; if (aiPlayer.Type != PlayerType.AI) return; var decision = AI.MakeDecision(Engine.State, aiPlayer); await InvokeAsync(async () => { string pos = GetPlayerPosition(aiPlayer); string deck = Engine.State.Rules.SelectedDeck; if (decision.Type == AIDecisionType.PlayCard) { var cardsToPlay = decision.Cards ?? (decision.Card != null ? new List { decision.Card } : new List()); if (cardsToPlay.Count > 0) { if (cardsToPlay.Count == 2) { _ = Fx.TriggerComboSlamAsync(2, $"⚡ {aiPlayer.Name}: DVOJIČKA!"); } else if (cardsToPlay.Count == 3) { _ = Fx.TriggerComboSlamAsync(3, $"🔥 {aiPlayer.Name}: TROJITÝ ÚDER!"); } else if (cardsToPlay.Count >= 4) { _ = Fx.TriggerComboSlamAsync(4, $"👑 {aiPlayer.Name}: KVADRA!"); } for (int i = 0; i < cardsToPlay.Count; i++) { if (i == cardsToPlay.Count - 1) { await Fx.AnimateCardPlayAsync(pos, cardsToPlay[i], deck); } else { _ = Fx.AnimateCardPlayAsync(pos, cardsToPlay[i], deck); await Task.Delay(MultiCardDelayMs); } } Engine.PlayCards(aiPlayer, cardsToPlay, decision.ChosenSuit); } } else if (decision.Type == AIDecisionType.BurnCards && decision.Cards != null) { for (int i = 0; i < decision.Cards.Count; i++) { if (i == decision.Cards.Count - 1) { await Fx.AnimateCardPlayAsync(pos, decision.Cards[i], deck); } else { _ = Fx.AnimateCardPlayAsync(pos, decision.Cards[i], deck); await Task.Delay(MultiCardDelayMs); } } Engine.BurnCards(aiPlayer, decision.Cards); } else { if (Engine.State.PendingDrawCount > 0) { int drawCount = Engine.State.PendingDrawCount; int staggerDelay = Math.Max(25, (int)(MultiCardDelayMs * 0.75)); for (int i = 0; i < drawCount; i++) { if (i == drawCount - 1) { await Fx.AnimateCardDrawAsync(pos, deck); } else { _ = Fx.AnimateCardDrawAsync(pos, deck); await Task.Delay(staggerDelay); } } } else if (!Engine.State.PendingSkip) { await Fx.AnimateCardDrawAsync(pos, deck); } Engine.DrawOrAcknowledge(aiPlayer); } StateHasChanged(); CheckTriggerAI(); }); } catch (TaskCanceledException) { } }, token); } } private IEnumerable<(Player Player, string PositionClass)> GetOpponents() { if (Engine == null) return Enumerable.Empty<(Player, string)>(); var opponents = Engine.State.Players.Where(p => p != ViewingPlayer).ToList(); var result = new List<(Player, string)>(); if (opponents.Count == 1) { result.Add((opponents[0], "slot-top")); } else if (opponents.Count == 2) { result.Add((opponents[0], "slot-left")); result.Add((opponents[1], "slot-right")); } else if (opponents.Count >= 3) { result.Add((opponents[0], "slot-left")); result.Add((opponents[1], "slot-top")); result.Add((opponents[2], "slot-right")); } return result; } private async Task ToggleMute() { IsMuted = await Fx.ToggleMuteAsync(); } private void HandlePlayAgain() { StartNewGame(); } private void ReturnToLobby() { _aiCts?.Cancel(); IsInLobby = true; Engine = null; } }