inial impl

This commit is contained in:
Michal Vrba
2026-08-28 13:59:36 +02:00
parent efec4e62c5
commit 6abf0f91a0
180 changed files with 64651 additions and 0 deletions
+186
View File
@@ -0,0 +1,186 @@
namespace Faraon.Core.AI;
using Faraon.Core.Models;
using Faraon.Core.Engine;
public enum AIDecisionType
{
PlayCard,
BurnCards,
DrawOrAcknowledge
}
public record AIDecision(
AIDecisionType Type,
Card? Card = null,
List<Card>? Cards = null,
Suit? ChosenSuit = null
);
public interface IFaraonAI
{
AIDecision MakeDecision(GameState state, Player player);
}
public class FaraonAI : IFaraonAI
{
private readonly Random _random = new();
public AIDecision MakeDecision(GameState state, Player player)
{
// 1. Kontrola Spálenia (4 rovnaké karty)
if (state.Rules.FourOfAKindBurns && state.PendingDrawCount == 0 && !state.PendingSkip)
{
var fourOfAKindGroup = player.Hand
.GroupBy(c => c.Rank)
.FirstOrDefault(g => g.Count() == 4);
if (fourOfAKindGroup != null)
{
var burnList = fourOfAKindGroup.ToList();
if (ActionValidator.CanBurnCards(burnList, state.PendingDrawCount, state.PendingSkip, state.Rules))
{
return new AIDecision(AIDecisionType.BurnCards, Cards: burnList);
}
}
}
// 2. Nájdi všetky zahrateľné karty
var playableCards = player.Hand
.Where(c => ActionValidator.CanPlayCard(
c,
state.TopCard,
state.ActiveSuit,
state.PendingDrawCount,
state.PendingSkip,
state.Rules))
.ToList();
// Ak nemá čo zahrať -> musí si potiahnuť alebo akceptovať trest
if (playableCards.Count == 0)
{
return new AIDecision(AIDecisionType.DrawOrAcknowledge);
}
// 3. Rozhodovanie podľa obtiažnosti
Card selectedCard;
if (player.Difficulty == AIDifficulty.Easy)
{
selectedCard = playableCards[_random.Next(playableCards.Count)];
}
else if (player.Difficulty == AIDifficulty.Hard)
{
selectedCard = ChooseBestCardSmart(playableCards, player, state);
}
else // Medium
{
selectedCard = ChooseBestCardMedium(playableCards, player, state);
}
// Zozbierame všetky karty rovnakej hodnoty v ruke (combo vyloženie)
var multiCards = player.Hand.Where(c => c.Rank == selectedCard.Rank).ToList();
// 4. Ak je vybraná karta Horník, vyber najvýhodnejšiu farbu
Suit? chosenSuit = null;
if (selectedCard.IsHornik)
{
chosenSuit = PickBestSuit(player.Hand.Where(c => !multiCards.Contains(c)).ToList());
}
return new AIDecision(AIDecisionType.PlayCard, Card: selectedCard, Cards: multiCards, ChosenSuit: chosenSuit);
}
private Card ChooseBestCardMedium(List<Card> playable, Player player, GameState state)
{
// Ak sa musí brániť proti sedme, zahrá sedmu
if (state.PendingDrawCount > 0)
{
var seven = playable.FirstOrDefault(c => c.IsSeven);
if (seven != null) return seven;
}
// Ak sa musí brániť proti esu, zahrá eso
if (state.PendingSkip)
{
var ace = playable.FirstOrDefault(c => c.IsAce);
if (ace != null) return ace;
}
// Ak má v ruke len 1 kartu navyše a môže vyhrať
if (player.Hand.Count <= 2)
{
return playable[0];
}
// Radšej šetrí Horníka ako žolíka, ak má inú normálnu kartu
var nonHornik = playable.Where(c => !c.IsHornik).ToList();
if (nonHornik.Count > 0)
{
return nonHornik[_random.Next(nonHornik.Count)];
}
return playable[0];
}
private Card ChooseBestCardSmart(List<Card> playable, Player player, GameState state)
{
// 1. Ak čelíme sedme, prioritne zahráme obyčajnú sedmu pred Červenou sedmou (ak je niekto vonku, šetríme ju)
if (state.PendingDrawCount > 0)
{
var regularSeven = playable.FirstOrDefault(c => c.IsSeven && !c.IsRedSeven);
if (regularSeven != null) return regularSeven;
var redSeven = playable.FirstOrDefault(c => c.IsRedSeven);
if (redSeven != null) return redSeven;
}
// 2. Ak čelíme esu, prebijeme esom
if (state.PendingSkip)
{
var ace = playable.FirstOrDefault(c => c.IsAce);
if (ace != null) return ace;
}
// 3. Ak môžeme touto kartou hru ukončiť (posledná karta v ruke)
if (player.Hand.Count == 1)
{
return playable[0];
}
// 4. Ak má Červenú sedmu a na stole je niekto na oživenie (LastFinishedPlayer), oživíme ho!
if (state.Rules.RedSevenRevives && state.LastFinishedPlayer != null)
{
var redSeven = playable.FirstOrDefault(c => c.IsRedSeven);
if (redSeven != null) return redSeven;
}
// 5. Ak máme iné možnosti, šetríme si Horníka na núdzu
var nonHornikCards = playable.Where(c => !c.IsHornik).ToList();
if (nonHornikCards.Count > 0)
{
// Zvoľ kartu farby, z ktorej máme najviac kariet
var suitCounts = player.Hand
.GroupBy(c => c.Suit)
.ToDictionary(g => g.Key, g => g.Count());
return nonHornikCards
.OrderByDescending(c => suitCounts.GetValueOrDefault(c.Suit, 0))
.First();
}
return playable[0];
}
public static Suit PickBestSuit(List<Card> remainingHand)
{
if (remainingHand.Count == 0) return Suit.Hearts;
// Nájdi farbu, ktorej máme najviac v ruke
var mostFrequentSuit = remainingHand
.GroupBy(c => c.Suit)
.OrderByDescending(g => g.Count())
.First()
.Key;
return mostFrequentSuit;
}
}
+115
View File
@@ -0,0 +1,115 @@
namespace Faraon.Core.Engine;
using Faraon.Core.Models;
public static class ActionValidator
{
public static bool CanPlayCard(
Card card,
Card? topCard,
Suit? activeSuit,
int pendingDrawCount,
bool pendingSkip,
RuleOptions rules)
{
// 1. Ak je na stole trest zo sedmy (napr. +2, +4, +6)
if (pendingDrawCount > 0)
{
// Ak je povolené prebíjanie sediem, musí zahrať sedmu
return rules.StackSevens && card.IsSeven;
}
// 2. Ak je na stole eso (státie)
if (pendingSkip)
{
// Ak je povolené prebíjanie esa esom, musí zahrať eso
return rules.CounterAceWithAce && card.IsAce;
}
// 3. Ak je kôpka prázdna (začiatok hry)
if (topCard == null)
{
return true;
}
// 4. Horník (Menič) sa môže zahrať na čokoľvek (keď nie je aktívny trest/eso)
if (card.IsHornik)
{
return true;
}
// 5. Ak bola zmenená farba Horníkom
if (activeSuit.HasValue)
{
// Musí sedieť zvolená farba (alebo zahrať ďalšieho Horníka, čo rieši bod 4)
return card.Suit == activeSuit.Value;
}
// 6. Bežné pravidlo: rovnaká farba ALEBO rovnaká hodnota
return card.Suit == topCard.Suit || card.Rank == topCard.Rank;
}
public static bool CanPlayCards(
List<Card> cards,
Card? topCard,
Suit? activeSuit,
int pendingDrawCount,
bool pendingSkip,
RuleOptions rules)
{
if (cards == null || cards.Count == 0) return false;
// Všetky karty musia mať rovnakú hodnotu
var firstRank = cards[0].Rank;
if (!cards.All(c => c.Rank == firstRank)) return false;
// 1. Ak je na stole trest zo sedmy
if (pendingDrawCount > 0)
{
return rules.StackSevens && cards.All(c => c.IsSeven);
}
// 2. Ak je na stole eso
if (pendingSkip)
{
return rules.CounterAceWithAce && cards.All(c => c.IsAce);
}
// 3. Ak je kôpka prázdna
if (topCard == null)
{
return true;
}
// 4. Horníci sa dajú vyložiť kedykoľvek
if (firstRank == Rank.OverKnave)
{
return true;
}
// 5. Ak bola zmenená farba Horníkom
if (activeSuit.HasValue)
{
// Aspoň jedna karta musí mať požadovanú farbu (prvá vyložená)
return cards.Any(c => c.Suit == activeSuit.Value);
}
// 6. Bežné pravidlo: aspoň jedna karta musí sedieť farbou na topCard, ALEBO hodnota kariet sedí na topCard
return firstRank == topCard.Rank || cards.Any(c => c.Suit == topCard.Suit);
}
public static bool CanBurnCards(
List<Card> cards,
int pendingDrawCount,
bool pendingSkip,
RuleOptions rules)
{
if (!rules.FourOfAKindBurns) return false;
if (pendingDrawCount > 0 || pendingSkip) return false;
if (cards.Count != 4) return false;
// Všetky 4 karty musia mať rovnakú hodnotu
var firstRank = cards[0].Rank;
return cards.All(c => c.Rank == firstRank);
}
}
+601
View File
@@ -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);
}
}
+9
View File
@@ -0,0 +1,9 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
</Project>
+129
View File
@@ -0,0 +1,129 @@
namespace Faraon.Core.Models;
public enum Suit
{
Hearts = 0, // Červeň / Srdce ❤️
Leaves = 1, // Zeleň / List 🍃
Acorns = 2, // Žaluď 🌰
Bells = 3 // Guľa 🔔
}
public enum Rank
{
Seven = 7,
Eight = 8,
Nine = 9,
Ten = 10,
UnderKnave = 11, // Dolník / Spodok
OverKnave = 12, // Horník / Filak / Menič
King = 13, // Kráľ
Ace = 14 // Eso
}
public record Card(Suit Suit, Rank Rank)
{
public string Id => $"{Suit}_{Rank}";
public string Name => $"{RankName} ({SuitName})";
public string ShortName => Rank switch
{
Rank.Seven => "VII",
Rank.Eight => "VIII",
Rank.Nine => "IX",
Rank.Ten => "X",
Rank.UnderKnave => "D",
Rank.OverKnave => "H",
Rank.King => "K",
Rank.Ace => "E",
_ => Rank.ToString()
};
public string NumericShortName => Rank switch
{
Rank.Seven => "7",
Rank.Eight => "8",
Rank.Nine => "9",
Rank.Ten => "10",
Rank.UnderKnave => "D",
Rank.OverKnave => "H",
Rank.King => "K",
Rank.Ace => "E",
_ => Rank.ToString()
};
public string RankName => Rank switch
{
Rank.Seven => "Sedma",
Rank.Eight => "Osma",
Rank.Nine => "Deviatka",
Rank.Ten => "Desiatka",
Rank.UnderKnave => "Dolník",
Rank.OverKnave => "Horník",
Rank.King => "Kráľ",
Rank.Ace => "Eso",
_ => Rank.ToString()
};
public string SuitName => Suit switch
{
Suit.Hearts => "Červeň",
Suit.Leaves => "Zeleň",
Suit.Acorns => "Žaluď",
Suit.Bells => "Guľa",
_ => Suit.ToString()
};
public string SuitSymbol => Suit switch
{
Suit.Hearts => "♥",
Suit.Leaves => "🍃",
Suit.Acorns => "🌰",
Suit.Bells => "🔔",
_ => "?"
};
public string SuitColor => Suit switch
{
Suit.Hearts => "#d62828", // Tradičná sýta červeň
Suit.Leaves => "#2d6a4f", // Tradičná lesná zeleň
Suit.Acorns => "#8b5a2b", // Hnedo-zlatý žaluď
Suit.Bells => "#d48b1a", // Zlatistá rolničková guľa
_ => "#ffffff"
};
public string SeasonName => (Rank == Rank.Ace) ? Suit switch
{
Suit.Hearts => "Jar",
Suit.Leaves => "Leto",
Suit.Acorns => "Jeseň",
Suit.Bells => "Zima",
_ => ""
} : "";
public string CharacterName => Rank switch
{
Rank.OverKnave => Suit switch
{
Suit.Hearts => "Wilhelm Tell",
Suit.Leaves => "Walter Fürst",
Suit.Acorns => "Ulrich Rudenz",
Suit.Bells => "Stauffacher",
_ => "Horník"
},
Rank.UnderKnave => Suit switch
{
Suit.Hearts => "Kuoni",
Suit.Leaves => "Itel Reding",
Suit.Acorns => "Stüssi",
Suit.Bells => "Harrass",
_ => "Dolník"
},
_ => ""
};
public bool IsSeven => Rank == Rank.Seven;
public bool IsAce => Rank == Rank.Ace;
public bool IsHornik => Rank == Rank.OverKnave;
public bool IsRedSeven => Rank == Rank.Seven && Suit == Suit.Hearts;
}
+42
View File
@@ -0,0 +1,42 @@
namespace Faraon.Core.Models;
public class Deck
{
private readonly Random _random;
public Deck(int? seed = null)
{
_random = seed.HasValue ? new Random(seed.Value) : new Random();
}
public static List<Card> CreateStandardDeck()
{
var cards = new List<Card>(32);
foreach (Suit suit in Enum.GetValues<Suit>())
{
foreach (Rank rank in Enum.GetValues<Rank>())
{
cards.Add(new Card(suit, rank));
}
}
return cards;
}
public List<Card> CreateShuffledDeck()
{
var deck = CreateStandardDeck();
Shuffle(deck);
return deck;
}
public void Shuffle<T>(List<T> list)
{
int n = list.Count;
while (n > 1)
{
n--;
int k = _random.Next(n + 1);
(list[k], list[n]) = (list[n], list[k]);
}
}
}
+28
View File
@@ -0,0 +1,28 @@
namespace Faraon.Core.Models;
public enum GameEventType
{
GameStarted,
CardPlayed,
CardsBurned,
CardDrawn,
SevenStacked,
AceSkipped,
AceCountered,
SuitChanged,
PlayerRevived,
PlayerFinished,
GameOver,
DeckReshuffled
}
public record GameEvent(
GameEventType Type,
Player? SourcePlayer,
Player? TargetPlayer = null,
Card? Card = null,
List<Card>? Cards = null,
Suit? ChosenSuit = null,
int ExtraData = 0,
string Message = ""
);
+31
View File
@@ -0,0 +1,31 @@
namespace Faraon.Core.Models;
public enum TurnState
{
Playing,
SelectingSuit,
PassScreen,
GameOver
}
public class GameState
{
public List<Player> Players { get; set; } = new();
public int CurrentPlayerIndex { get; set; } = 0;
public List<Card> DrawDeck { get; set; } = new();
public List<Card> DiscardPile { get; set; } = new();
public Suit? ActiveSuit { get; set; }
public int PendingDrawCount { get; set; } = 0;
public bool PendingSkip { get; set; } = false;
public RuleOptions Rules { get; set; } = new();
public bool IsGameOver { get; set; } = false;
public Player? LastFinishedPlayer { get; set; }
public int TurnCount { get; set; } = 0;
public TurnState TurnState { get; set; } = TurnState.Playing;
public Player? PendingHornikPlayer { get; set; }
public Card? TopCard => DiscardPile.Count > 0 ? DiscardPile[^1] : null;
public Player CurrentPlayer => Players.Count > 0 ? Players[CurrentPlayerIndex % Players.Count] : new Player();
public int ActivePlayersCount => Players.Count(p => !p.HasFinished);
}
+46
View File
@@ -0,0 +1,46 @@
namespace Faraon.Core.Models;
public enum PlayerType
{
Human = 0,
AI = 1
}
public enum AIDifficulty
{
Easy = 0,
Medium = 1,
Hard = 2
}
public class Player
{
public string Id { get; init; } = Guid.NewGuid().ToString("N");
public string Name { get; set; } = "Hráč";
public PlayerType Type { get; set; } = PlayerType.Human;
public AIDifficulty Difficulty { get; set; } = AIDifficulty.Medium;
public string Avatar { get; set; } = "👑";
public List<Card> Hand { get; } = new();
public int? FinishedPosition { get; set; }
public bool HasFinished => FinishedPosition.HasValue;
public bool IsSkipped { get; set; }
public Player() { }
public Player(string name, PlayerType type, string avatar = "👤", AIDifficulty difficulty = AIDifficulty.Medium)
{
Name = name;
Type = type;
Avatar = avatar;
Difficulty = difficulty;
}
public void Reset()
{
Hand.Clear();
FinishedPosition = null;
IsSkipped = false;
}
}
+44
View File
@@ -0,0 +1,44 @@
namespace Faraon.Core.Models;
public class RuleOptions
{
/// <summary>
/// Počet kariet rozdaných každému hráčovi na začiatku (zvyčajne 4 alebo 5).
/// </summary>
public int InitialCardCount { get; set; } = 5;
/// <summary>
/// Ak je zapnuté, sedma na sedmu pridáva ďalšie +2 karty (kumulácia trestu).
/// </summary>
public bool StackSevens { get; set; } = true;
/// <summary>
/// Ak je zapnuté, eso možno prebiť vlastným esom a preniesť státie na ďalšieho hráča.
/// </summary>
public bool CounterAceWithAce { get; set; } = true;
/// <summary>
/// Červená sedma vracia do hry hráča, ktorý sa už zbavil všetkých kariet (posledného víťaza).
/// </summary>
public bool RedSevenRevives { get; set; } = true;
/// <summary>
/// Zahranie 4 rovnakých kariet naraz ("Spálenie"). Spáli kôpku a hráč hrá znova.
/// </summary>
public bool FourOfAKindBurns { get; set; } = true;
/// <summary>
/// Režim Pass & Play so súkromnou clonou medzi ťahmi viacerých živých hráčov.
/// </summary>
public bool PassAndPlayPrivacyScreen { get; set; } = false;
/// <summary>
/// Rýchlosť AI ťahov v milisekundách (pre plynulé sledovanie animácií).
/// </summary>
public int AIDelayMs { get; set; } = 900;
/// <summary>
/// Zvolený vizuálny štýl balíčka ("original" alebo "spongebob").
/// </summary>
public string SelectedDeck { get; set; } = "original";
}