435 lines
16 KiB
C#
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));
|
|
}
|
|
}
|