Files
FaraonCardGame/Faraon.Tests/GameEngineTests.cs
T
2026-09-03 17:54:36 +02:00

435 lines
16 KiB
C#

namespace Faraon.Tests;
using Faraon.Core.Models;
using Faraon.Core.Engine;
using Faraon.Core.AI;
public class GameEngineTests
{
[Fact]
public void StartGame_DealsCorrectNumberOfCards()
{
var engine = new GameEngine(seed: 42);
var players = new List<Player>
{
new Player("Hráč 1", PlayerType.Human),
new Player("AI 1", PlayerType.AI),
new Player("AI 2", PlayerType.AI),
new Player("AI 3", PlayerType.AI)
};
var rules = new RuleOptions { InitialCardCount = 5 };
engine.StartGame(players, rules);
Assert.Equal(4, engine.State.Players.Count);
Assert.All(engine.State.Players, p => Assert.Equal(5, p.Hand.Count));
Assert.Single(engine.State.DiscardPile);
Assert.Equal(11, engine.State.DrawDeck.Count);
}
[Fact]
public void Seven_IncreasesPendingDrawCount()
{
var engine = new GameEngine(seed: 42);
var p1 = new Player("Hráč 1", PlayerType.Human);
var p2 = new Player("Hráč 2", PlayerType.Human);
var rules = new RuleOptions { StackSevens = true };
engine.StartGame(new List<Player> { p1, p2 }, rules);
p1.Hand.Clear();
var sevenCard = new Card(Suit.Hearts, Rank.Seven);
p1.Hand.Add(sevenCard);
p1.Hand.Add(new Card(Suit.Hearts, Rank.Eight)); // extra card so p1 does not finish
engine.State.DiscardPile.Clear();
engine.State.DiscardPile.Add(new Card(Suit.Hearts, Rank.Ten));
var played = engine.PlayCard(p1, sevenCard);
Assert.True(played);
Assert.Equal(3, engine.State.PendingDrawCount);
Assert.Equal(p2, engine.State.CurrentPlayer);
}
[Fact]
public void Ace_SetsPendingSkip()
{
var engine = new GameEngine(seed: 42);
var p1 = new Player("Hráč 1", PlayerType.Human);
var p2 = new Player("Hráč 2", PlayerType.Human);
var rules = new RuleOptions { CounterAceWithAce = true };
engine.StartGame(new List<Player> { p1, p2 }, rules);
p1.Hand.Clear();
var aceCard = new Card(Suit.Bells, Rank.Ace);
p1.Hand.Add(aceCard);
p1.Hand.Add(new Card(Suit.Bells, Rank.Eight)); // extra card
engine.State.DiscardPile.Clear();
engine.State.DiscardPile.Add(new Card(Suit.Bells, Rank.Nine));
var played = engine.PlayCard(p1, aceCard);
Assert.True(played);
Assert.True(engine.State.PendingSkip);
Assert.Equal(p2, engine.State.CurrentPlayer);
// Hráč 2 akceptuje státie
engine.DrawOrAcknowledge(p2);
Assert.False(engine.State.PendingSkip);
Assert.Equal(p1, engine.State.CurrentPlayer);
}
[Fact]
public void Hornik_ChangesSuitSuccessfully()
{
var engine = new GameEngine(seed: 42);
var p1 = new Player("Hráč 1", PlayerType.Human);
var p2 = new Player("Hráč 2", PlayerType.Human);
var rules = new RuleOptions();
engine.StartGame(new List<Player> { p1, p2 }, rules);
p1.Hand.Clear();
var hornik = new Card(Suit.Acorns, Rank.OverKnave);
p1.Hand.Add(hornik);
p1.Hand.Add(new Card(Suit.Leaves, Rank.Eight)); // extra card
engine.State.DiscardPile.Clear();
engine.State.DiscardPile.Add(new Card(Suit.Hearts, Rank.Eight));
var played = engine.PlayCard(p1, hornik, Suit.Leaves);
Assert.True(played);
Assert.Equal(Suit.Leaves, engine.State.ActiveSuit);
}
[Fact]
public void RedSeven_RevivesFinishedPlayer()
{
var engine = new GameEngine(seed: 42);
var p1 = new Player("Hráč 1", PlayerType.Human);
var p2 = new Player("Hráč 2", PlayerType.Human);
var p3 = new Player("Hráč 3", PlayerType.Human);
var rules = new RuleOptions { RedSevenRevives = true };
engine.StartGame(new List<Player> { p1, p2, p3 }, rules);
p3.Hand.Clear();
p3.FinishedPosition = 1;
engine.State.LastFinishedPlayer = p3;
engine.State.ReviveTurnsRemaining = 4;
engine.State.ReviveRoundsRemaining = 2;
p1.Hand.Clear();
var redSeven = new Card(Suit.Hearts, Rank.Seven);
p1.Hand.Add(redSeven);
p1.Hand.Add(new Card(Suit.Hearts, Rank.Eight));
engine.State.DiscardPile.Clear();
engine.State.DiscardPile.Add(new Card(Suit.Hearts, Rank.Ten));
var played = engine.PlayCard(p1, redSeven);
Assert.True(played);
Assert.False(p3.HasFinished);
Assert.Null(p3.FinishedPosition);
Assert.Equal(3, p3.Hand.Count);
}
[Fact]
public void RedSeven_AnyCardOneRound_CanPlayOnAnyCard()
{
var rules = new RuleOptions { RedSevenMode = RedSevenMode.AnyCardOneRound };
var redSeven = new Card(Suit.Hearts, Rank.Seven);
var topCard = new Card(Suit.Acorns, Rank.King); // completely different suit and rank
var canPlay = ActionValidator.CanPlayCard(redSeven, topCard, null, 0, false, rules);
Assert.True(canPlay);
}
[Fact]
public void RedSeven_ColorOrSeven_OnlyOnMatchingSuitOrSeven()
{
var rules = new RuleOptions { RedSevenMode = RedSevenMode.ColorOrSevenThreeRounds };
var redSeven = new Card(Suit.Hearts, Rank.Seven);
var topAcornsKing = new Card(Suit.Acorns, Rank.King);
var topHeartsKing = new Card(Suit.Hearts, Rank.King);
var topBellsSeven = new Card(Suit.Bells, Rank.Seven);
Assert.False(ActionValidator.CanPlayCard(redSeven, topAcornsKing, null, 0, false, rules));
Assert.True(ActionValidator.CanPlayCard(redSeven, topHeartsKing, null, 0, false, rules));
Assert.True(ActionValidator.CanPlayCard(redSeven, topBellsSeven, null, 0, false, rules));
}
[Fact]
public void RedSeven_ReviveExpiresAfterTurns()
{
var engine = new GameEngine(seed: 42);
var p1 = new Player("Hráč 1", PlayerType.Human);
var p2 = new Player("Hráč 2", PlayerType.Human);
var p3 = new Player("Hráč 3", PlayerType.Human);
var rules = new RuleOptions { RedSevenMode = RedSevenMode.AnyCardOneRound };
engine.StartGame(new List<Player> { p1, p2, p3 }, rules);
p3.Hand.Clear();
p3.Hand.Add(new Card(Suit.Leaves, Rank.Eight));
engine.State.DiscardPile.Clear();
engine.State.DiscardPile.Add(new Card(Suit.Leaves, Rank.Seven));
// p3 finishes
engine.State.CurrentPlayerIndex = 2;
var played = engine.PlayCard(p3, p3.Hand[0]);
Assert.True(played);
Assert.True(p3.HasFinished);
Assert.Equal(p3, engine.State.LastFinishedPlayer);
Assert.True(engine.State.ReviveTurnsRemaining > 0);
// Advance turns until window expires
while (engine.State.ReviveTurnsRemaining > 0)
{
engine.DrawOrAcknowledge(engine.State.CurrentPlayer);
}
Assert.Null(engine.State.LastFinishedPlayer);
Assert.Equal(0, engine.State.ReviveTurnsRemaining);
}
[Fact]
public void FourOfAKind_BurnsAndAllowsAnotherMove()
{
var engine = new GameEngine(seed: 42);
var p1 = new Player("Hráč 1", PlayerType.Human);
var p2 = new Player("Hráč 2", PlayerType.Human);
var rules = new RuleOptions { FourOfAKindBurns = true };
engine.StartGame(new List<Player> { p1, p2 }, rules);
p1.Hand.Clear();
var fourKings = new List<Card>
{
new Card(Suit.Hearts, Rank.King),
new Card(Suit.Leaves, Rank.King),
new Card(Suit.Acorns, Rank.King),
new Card(Suit.Bells, Rank.King)
};
p1.Hand.AddRange(fourKings);
p1.Hand.Add(new Card(Suit.Hearts, Rank.Eight));
var burned = engine.BurnCards(p1, fourKings);
Assert.True(burned);
Assert.Equal(p1, engine.State.CurrentPlayer); // Hráč pokračuje
Assert.Single(p1.Hand);
}
[Fact]
public void MultiSeven_StacksDoublePenalty()
{
var engine = new GameEngine(seed: 42);
var p1 = new Player("Hráč 1", PlayerType.Human);
var p2 = new Player("Hráč 2", PlayerType.Human);
var rules = new RuleOptions { StackSevens = true };
engine.StartGame(new List<Player> { p1, p2 }, rules);
p1.Hand.Clear();
var sevens = new List<Card>
{
new Card(Suit.Hearts, Rank.Seven),
new Card(Suit.Bells, Rank.Seven)
};
p1.Hand.AddRange(sevens);
p1.Hand.Add(new Card(Suit.Hearts, Rank.Eight)); // extra card
engine.State.DiscardPile.Clear();
engine.State.DiscardPile.Add(new Card(Suit.Hearts, Rank.Ten));
var played = engine.PlayCards(p1, sevens);
Assert.True(played);
Assert.Equal(6, engine.State.PendingDrawCount); // 2x sedma = +6 kariet
Assert.Equal(p2, engine.State.CurrentPlayer);
}
[Fact]
public void MultiCards_PlaysBatchOfSameRank()
{
var engine = new GameEngine(seed: 42);
var p1 = new Player("Hráč 1", PlayerType.Human);
var p2 = new Player("Hráč 2", PlayerType.Human);
var rules = new RuleOptions();
engine.StartGame(new List<Player> { p1, p2 }, rules);
p1.Hand.Clear();
var dolniks = new List<Card>
{
new Card(Suit.Hearts, Rank.UnderKnave),
new Card(Suit.Leaves, Rank.UnderKnave),
new Card(Suit.Bells, Rank.UnderKnave)
};
p1.Hand.AddRange(dolniks);
p1.Hand.Add(new Card(Suit.Hearts, Rank.Eight)); // extra card
engine.State.DiscardPile.Clear();
engine.State.DiscardPile.Add(new Card(Suit.Hearts, Rank.Nine));
var played = engine.PlayCards(p1, dolniks);
Assert.True(played);
Assert.Single(p1.Hand); // Ostaná len 1 karta
Assert.Equal(4, engine.State.DiscardPile.Count);
Assert.Equal(p2, engine.State.CurrentPlayer);
}
[Fact]
public void MultiCards_PreservesPlayerChosenOrder_AndSetsTopCard()
{
var engine = new GameEngine(seed: 42);
var p1 = new Player("Hráč 1", PlayerType.Human);
var p2 = new Player("Hráč 2", PlayerType.Human);
var rules = new RuleOptions();
engine.StartGame(new List<Player> { p1, p2 }, rules);
p1.Hand.Clear();
var card1 = new Card(Suit.Hearts, Rank.Eight); // Matches table Hearts
var card2 = new Card(Suit.Bells, Rank.Eight); // Desired top card (Bells)
p1.Hand.Add(card1);
p1.Hand.Add(card2);
p1.Hand.Add(new Card(Suit.Acorns, Rank.Nine)); // extra
engine.State.DiscardPile.Clear();
engine.State.DiscardPile.Add(new Card(Suit.Hearts, Rank.Ten));
// Player explicitly chooses: card1 first, card2 second (so card2 becomes top of discard pile)
var sequence = new List<Card> { card1, card2 };
var played = engine.PlayCards(p1, sequence);
Assert.True(played);
Assert.Equal(card2, engine.State.TopCard); // Card2 is on top
Assert.NotNull(engine.State.TopCard);
Assert.Equal(Suit.Bells, engine.State.TopCard.Suit);
}
[Fact]
public void FaraonAI_MakesValidDecisions()
{
var engine = new GameEngine(seed: 42);
var p1 = new Player("AI Bot", PlayerType.AI, difficulty: AIDifficulty.Hard);
var p2 = new Player("AI Bot 2", PlayerType.AI, difficulty: AIDifficulty.Hard);
engine.StartGame(new List<Player> { p1, p2 }, new RuleOptions());
var ai = new FaraonAI();
var decision = ai.MakeDecision(engine.State, p1);
Assert.NotNull(decision);
if (decision.Type == AIDecisionType.PlayCard)
{
Assert.NotNull(decision.Card);
Assert.True(engine.CanPlayCard(p1, decision.Card));
}
}
[Fact]
public void OlympicMode_LoserLosesOneCard_AndStartsNextRound()
{
var engine = new GameEngine(seed: 42);
var p1 = new Player("Hráč 1", PlayerType.Human);
var p2 = new Player("Hráč 2", PlayerType.Human);
var p3 = new Player("Hráč 3", PlayerType.Human);
var rules = new RuleOptions { OlympicMode = true, InitialCardCount = 4 };
engine.StartGame(new List<Player> { p1, p2, p3 }, rules);
// Hráč 1 vyhrá (prvý skončí)
p1.Hand.Clear();
p1.Hand.Add(new Card(Suit.Hearts, Rank.Eight));
engine.State.DiscardPile.Clear();
engine.State.DiscardPile.Add(new Card(Suit.Hearts, Rank.Seven));
engine.PlayCard(p1, p1.Hand[0]); // p1 finished 1st
// Hráč 2 skončí druhý
p2.Hand.Clear();
p2.Hand.Add(new Card(Suit.Hearts, Rank.Nine));
engine.PlayCard(p2, p2.Hand[0]); // p2 finished 2nd
// Hráč 3 ostal posledný (porazený kola)
Assert.True(engine.State.IsRoundOver);
Assert.Equal(TurnState.RoundOver, engine.State.TurnState);
Assert.Equal(p3, engine.State.RoundLoser);
Assert.Equal(3, p3.OlympicCardsCount); // 4 - 1 = 3 karty
// Spustenie ďalšieho kola
engine.StartNextRound();
Assert.Equal(2, engine.State.RoundNumber);
Assert.Equal(4, p1.Hand.Count);
Assert.Equal(4, p2.Hand.Count);
Assert.Equal(3, p3.Hand.Count); // Hráč 3 má o 1 kartu menej
}
[Fact]
public void OlympicMode_EliminatesPlayer_AndDeclaresWinner()
{
var engine = new GameEngine(seed: 42);
var p1 = new Player("Hráč 1", PlayerType.Human);
var p2 = new Player("Hráč 2", PlayerType.Human);
var rules = new RuleOptions { OlympicMode = true, InitialCardCount = 1 };
engine.StartGame(new List<Player> { p1, p2 }, rules);
// Hráč 1 vyhrá kolo
p1.Hand.Clear();
p1.Hand.Add(new Card(Suit.Hearts, Rank.Eight));
engine.State.DiscardPile.Clear();
engine.State.DiscardPile.Add(new Card(Suit.Hearts, Rank.Seven));
engine.PlayCard(p1, p1.Hand[0]);
// Hráč 2 prehrá kolo -> klesne z 1 na 0 kariet -> eliminovaný!
Assert.True(p2.IsEliminated);
Assert.True(engine.State.IsMatchOver);
Assert.Equal(p1, engine.State.MatchWinner);
Assert.Equal(TurnState.GameOver, engine.State.TurnState);
}
[Fact]
public void GreenUnderKnave_CancelsSevenPenalty()
{
var engine = new GameEngine(seed: 42);
var p1 = new Player("Hráč 1", PlayerType.Human);
var p2 = new Player("Hráč 2", PlayerType.Human);
var rules = new RuleOptions { GreenUnderKnaveMode = GreenUnderKnaveMode.OnlyOnSevenOrUnderKnave };
engine.StartGame(new List<Player> { p1, p2 }, rules);
// p1 plays 7 -> penalty 3
p1.Hand.Clear();
p1.Hand.Add(new Card(Suit.Bells, Rank.Seven));
p1.Hand.Add(new Card(Suit.Leaves, Rank.Eight));
engine.State.DiscardPile.Clear();
engine.State.DiscardPile.Add(new Card(Suit.Bells, Rank.King));
engine.PlayCard(p1, p1.Hand[0]);
Assert.Equal(3, engine.State.PendingDrawCount);
// p2 plays Green Under Knave -> penalty becomes 0
p2.Hand.Clear();
var greenUnderKnave = new Card(Suit.Leaves, Rank.UnderKnave);
p2.Hand.Add(greenUnderKnave);
p2.Hand.Add(new Card(Suit.Hearts, Rank.Nine));
var played = engine.PlayCard(p2, greenUnderKnave);
Assert.True(played);
Assert.Equal(0, engine.State.PendingDrawCount);
Assert.Equal(greenUnderKnave, engine.State.TopCard);
}
[Fact]
public void GreenUnderKnave_UniversalWildcard_AllowsAnyPlay()
{
var rules = new RuleOptions { GreenUnderKnaveMode = GreenUnderKnaveMode.UniversalWildcard };
var greenUnderKnave = new Card(Suit.Leaves, Rank.UnderKnave);
var topAcornsKing = new Card(Suit.Acorns, Rank.King);
var arbitraryCard = new Card(Suit.Hearts, Rank.Ten);
// Green Under Knave can be played on Acorns King
Assert.True(ActionValidator.CanPlayCard(greenUnderKnave, topAcornsKing, null, 0, false, rules));
// Arbitrary card (Hearts Ten) can be played on Green Under Knave
Assert.True(ActionValidator.CanPlayCard(arbitraryCard, greenUnderKnave, null, 0, false, rules));
}
[Fact]
public void GreenUnderKnave_Disabled_DoesNotCancelPenalty()
{
var rules = new RuleOptions { GreenUnderKnaveMode = GreenUnderKnaveMode.Disabled };
var greenUnderKnave = new Card(Suit.Leaves, Rank.UnderKnave);
var topSeven = new Card(Suit.Hearts, Rank.Seven);
// When facing penalty and disabled, Green Under Knave cannot be played
Assert.False(ActionValidator.CanPlayCard(greenUnderKnave, topSeven, null, 3, false, rules));
}
}