using SlaySharp.Core.Catalogs; using SlaySharp.Core.Combat; using SlaySharp.Core.Enums; using SlaySharp.Core.Models; using Xunit; namespace SlaySharp.Tests; public class CombatEngineTests { [Fact] public void CombatEngine_InitializesDeckAndDraws5Cards() { var player = new Player("Senior Dev", 70, maxEnergy: 3, seed: 42); player.MasterDeck.AddRange(StarterDecks.CreateCsharpArchitectDeck()); var engine = new CombatEngine(); var enemy = new NullReferenceExceptionEnemy(); engine.StartCombat(player, new List { enemy }); Assert.Equal(CombatPhase.PlayerTurn, engine.Phase); Assert.Equal(3, player.CurrentEnergy); Assert.Equal(5, player.Hand.Count); Assert.Equal(player.MasterDeck.Count - 5, player.DrawPile.Count); Assert.Empty(player.DiscardPile); } [Fact] public void PlayCard_DealsDamage_AndConsumesEnergy() { var player = new Player("Senior Dev", 70, maxEnergy: 3); var card = new ConsoleWriteLineCard(); // Cost 1, dmg 6 var enemy = new NullReferenceExceptionEnemy(); // 45 HP var engine = new CombatEngine(); engine.StartCombat(player, new List { enemy }); player.Hand.Clear(); player.Hand.Add(card); player.CurrentEnergy = 3; var success = engine.PlayCard(card, enemy); Assert.True(success); Assert.Equal(2, player.CurrentEnergy); Assert.True(enemy.CurrentHp <= 39); Assert.Contains(card, player.DiscardPile); } [Fact] public void CalculateDamagePreview_AccuratelyCalculatesHpLossAndShieldAbsorbed() { var player = new Player("Senior Dev", 70, maxEnergy: 3); var card = new ThrowExceptionCard(); // 16 dmg var enemy = new OffByOneBugEnemy(); // 22 HP var engine = new CombatEngine(); engine.StartCombat(player, new List { enemy }); enemy.GainShield(6); // Set shield AFTER StartCombat var preview = engine.CalculateDamagePreview(card, enemy); Assert.Equal(16, preview.MinRawDamage); Assert.Equal(6, preview.ShieldAbsorbed); Assert.Equal(10, preview.MinHpLoss); // 16 - 6 shield = 10 HP loss Assert.Equal(12, preview.ProjectedRemainingHp); // 22 - 10 = 12 Assert.False(preview.IsLethal); } [Fact] public void CalculateDamagePreview_IdentifiesLethalDamage() { var player = new Player("Senior Dev", 70, maxEnergy: 3); var card = new ThrowExceptionCard(); // 16 dmg var enemy = new OffByOneBugEnemy(); // 22 HP var engine = new CombatEngine(); engine.StartCombat(player, new List { enemy }); enemy.CurrentHp = 14; var preview = engine.CalculateDamagePreview(card, enemy); Assert.Equal(16, preview.MinHpLoss); Assert.True(preview.IsLethal); Assert.Equal(0, preview.ProjectedRemainingHp); } [Fact] public void SpanSliceCard_PiercesThroughShield() { var player = new Player("Senior Dev", 70, maxEnergy: 3); var card = new SpanSliceCard(); // 8 piercing dmg var enemy = new NullReferenceExceptionEnemy(); // 45 HP var engine = new CombatEngine(); engine.StartCombat(player, new List { enemy }); enemy.GainShield(10); player.Hand.Clear(); player.Hand.Add(card); player.CurrentEnergy = 3; var success = engine.PlayCard(card, enemy); Assert.True(success); Assert.Equal(10, enemy.Shield); Assert.True(enemy.CurrentHp <= 37); } [Fact] public void StatusEffects_StrengthAndVulnerable_MultiplyDamage() { var player = new Player("Senior Dev", 70, maxEnergy: 3); var enemy = new NullReferenceExceptionEnemy(); // 45 HP var card = new ConsoleWriteLineCard(); // 6 dmg var engine = new CombatEngine(); engine.StartCombat(player, new List { enemy }); player.Statuses.Add(StatusType.Strength, 2); enemy.Statuses.Add(StatusType.Vulnerable, 1); player.Hand.Clear(); player.Hand.Add(card); player.CurrentEnergy = 3; engine.PlayCard(card, enemy); Assert.True(enemy.CurrentHp <= 33); } [Fact] public void DiscardPile_Reshuffles_WhenDrawPileEmpty() { var player = new Player("Senior Dev", 70, maxEnergy: 3, seed: 123); player.MasterDeck.Add(new ConsoleWriteLineCard()); player.MasterDeck.Add(new TryCatchCard()); player.MasterDeck.Add(new LinqWhereCard()); var engine = new CombatEngine(); var enemy = new OffByOneBugEnemy(); engine.StartCombat(player, new List { enemy }); Assert.Equal(3, player.Hand.Count); Assert.Empty(player.DrawPile); player.DiscardPile.Add(player.Hand[0]); player.DiscardPile.Add(player.Hand[1]); player.Hand.RemoveRange(0, 2); player.DrawCards(2, engine.Log); Assert.Equal(3, player.Hand.Count); Assert.Empty(player.DiscardPile); } [Fact] public void KillingAllEnemies_TriggersVictory() { var player = new Player("Senior Dev", 70, maxEnergy: 3); var card = new ThrowExceptionCard(); // 16 dmg var enemy = new OffByOneBugEnemy(); // 22 HP var engine = new CombatEngine(); engine.StartCombat(player, new List { enemy }); enemy.CurrentHp = 10; player.Hand.Clear(); player.Hand.Add(card); player.CurrentEnergy = 2; engine.PlayCard(card, enemy); Assert.Equal(CombatPhase.Victory, engine.Phase); Assert.Empty(engine.Enemies); Assert.NotEmpty(engine.CardRewards); } [Fact] public void GameRun_StartNewRun_InitializesCorrectArchetypes() { var run = new SlaySharp.Core.Run.GameRun(); // Principal Architect run.StartNewRun(DeveloperArchetype.PrincipalArchitect); Assert.Equal("Principal Architect", run.Player.Name); Assert.Equal(70, run.Player.MaxHp); Assert.Equal(3, run.Player.MaxEnergy); Assert.Equal(3, run.Player.Relics.Count); // Async Concurrency Master run.StartNewRun(DeveloperArchetype.AsyncConcurrencyMaster); Assert.Equal("Async Master", run.Player.Name); Assert.Equal(65, run.Player.MaxHp); Assert.Equal(4, run.Player.MaxEnergy); Assert.Equal(2, run.Player.Relics.Count); // Unsafe Memory Hacker run.StartNewRun(DeveloperArchetype.UnsafeMemoryHacker); Assert.Equal("Unsafe Hacker", run.Player.Name); Assert.Equal(60, run.Player.MaxHp); Assert.Equal(3, run.Player.MaxEnergy); Assert.Equal(2, run.Player.Relics.Count); } [Fact] public void TaskWhenAllCard_DealsDamageToTargetEnemy() { var player = new Player("Async Master", 65, maxEnergy: 4); var card = new TaskWhenAllCard(); // 14 dmg single target var enemy = new NullReferenceExceptionEnemy(); // 45 HP var engine = new CombatEngine(); engine.StartCombat(player, new List { enemy }); player.Hand.Clear(); player.Hand.Add(card); player.CurrentEnergy = 2; var success = engine.PlayCard(card, enemy); Assert.True(success); Assert.Equal(0, player.CurrentEnergy); Assert.True(enemy.CurrentHp <= 31); } [Fact] public void FixedPointerCard_DealsPiercingDamageTwoTimes() { var player = new Player("Unsafe Hacker", 60, maxEnergy: 3); var card = new FixedPointerCard(); // 2 hits of 9 piercing dmg var enemy = new MemoryLeakSpectreEnemy(); // 35 HP var engine = new CombatEngine(); engine.StartCombat(player, new List { enemy }); enemy.GainShield(12); player.Hand.Clear(); player.Hand.Add(card); player.CurrentEnergy = 2; var success = engine.PlayCard(card, enemy); Assert.True(success); Assert.Equal(12, enemy.Shield); // Pierced Assert.True(enemy.CurrentHp <= 17); } [Fact] public void KubernetesBoss_ExecutesRoutines() { var boss = new KubernetesClusterBossEnemy(); Assert.Equal(110, boss.MaxHp); Assert.Equal(110, boss.CurrentHp); Assert.Equal("☸️", boss.Icon); var player = new Player("Senior Dev", 70, maxEnergy: 3); var engine = new CombatEngine(); engine.StartCombat(player, new List { boss }); // Verify intent is planned Assert.NotNull(boss.CurrentIntent); } [Fact] public void RunRecord_Create_GeneratesValidGitCommitRecord() { var record = RunRecord.Create( DeveloperArchetype.AsyncConcurrencyMaster, true, 55, 65, 120, 14, 7 ); Assert.Equal(7, record.CommitHash.Length); Assert.True(record.IsVictory); Assert.Equal(55, record.FinalHp); Assert.Equal(120, record.DevCredits); Assert.Contains("feat(prod)", record.Message); } }