using System.Collections.Immutable; using System.Runtime.InteropServices; using RobotAndDonkey.Game.Cards; using RobotAndDonkey.Game.Cards.Glitches; using RobotAndDonkey.Game.Cards.Patches; using RobotAndDonkey.Game.Data; using RobotAndDonkey.Game.Execution; using RobotAndDonkey.Game.Intents; using RobotAndDonkey.Game.Modifiers; using RobotAndDonkey.Game.Robots; using RobotAndDonkey.Game.Utils; namespace RobotAndDonkey.Game.GameState; public record CoreLoop(SRandom random, ImmutableArray GlitchDeck, Board.Board Board, Robot Robot) { public static ImmutableArray CreatePatchDeck(ref SRandom random, EDifficulty difficulty, int amount, bool includeModifiers) { int GetRarityWeight(ERarity rarity) { return rarity switch { ERarity.Common => Balancing.Instance.CommonWeight, ERarity.Magic => Balancing.Instance.MagicWeight, ERarity.Uncommon => Balancing.Instance.UncommonWeight, ERarity.Rare => Balancing.Instance.RareWeight, ERarity.Legendary => Balancing.Instance.LegendaryWeight, _ => 0 }; } var allCards = new List(); foreach (var type in s_PatchCardTypes.Values) allCards.Add((PatchCard)Activator.CreateInstance(type)!); var byRarity = allCards.GroupBy(c => c.Rarity).ToDictionary(g => g.Key, g => g.ToList()); var result = new List(Math.Max(0, amount)); ERarity PickRarity(ref SRandom random) { var candidates = byRarity.Where(kvp => kvp.Value.Count > 0).Select(kvp => (kvp.Key, Value: GetRarityWeight(kvp.Key))).ToArray(); if (candidates.Length == 0) return ERarity.Common; var total = candidates.Sum(kv => kv.Value); var roll = random.Next(total + 1); var accum = 0; foreach (var (rarity, weight) in candidates) { accum += weight; if (roll <= accum) return rarity; } return candidates[^1].Key; } while (result.Count < amount) { if (!byRarity.Values.Any(list => list.Count > 0)) break; var rarity = PickRarity(ref random); var bucket = byRarity[rarity]; var idx = random.Next(bucket.Count); var chosen = bucket[idx]; result.Add(chosen.DeepClone()); bucket.RemoveAt(idx); } if (!includeModifiers) return [..result]; var fDifficulty = 0.5f + (int)difficulty / 4.0f; var buffChance = Balancing.Instance.ShopBuffChance * (1 - fDifficulty); var debuffChance = Balancing.Instance.ShopDebuffChance * fDifficulty; for (int i = 0; i < result.Count; ++i) { var buff = random.NextSingle() <= buffChance; var debuff = random.NextSingle() <= debuffChance; if (debuff) { result[i].AddModifier(ModifyCard.Create(random.Next(4) switch { 0 => EModifierId.Corrupt, 1 => EModifierId.Unreliable, 2 => EModifierId.RaceCondition, 3 => EModifierId.Throttled}, EModifierDuration.Permanent), []); } if (buff) { result[i].AddModifier(ModifyCard.Create(random.Next(4) switch { 0 => EModifierId.Effective, 1 => EModifierId.Optimized, 2 => EModifierId.Efficient, 3 => EModifierId.Persistent}, EModifierDuration.Permanent), []); } } return [..result]; } public CoreLoop(SRandom random, MatchParameters parameters) : this(random, CreateGlitchDeck(ref random), RobotAndDonkey.Game.Board.Board.Generate(ref random, parameters.Difficulty), CreateRobot(parameters)) { Difficulty = parameters.Difficulty; m_Random = random; Currency = Robot.Currency; PatchDeck = Robot.Deck.Select(c => (Card)Activator.CreateInstance(s_PatchCardTypes[c])!).ToList(); ResetShop(); } public CoreLoop(CoreLoop clone) { GlitchDeck = [..clone.GlitchDeck.Select(c => c.DeepClone())]; Board = new(clone.Board); Robot = clone.Robot.DeepClone(); NextGlitch = clone.NextGlitch; DeferGlitchCount = clone.DeferGlitchCount; RunPhase = clone.RunPhase; ProgramCount = clone.ProgramCount; RerollCount = clone.RerollCount; EnergyWasted = clone.EnergyWasted; ProgramsExecuted = clone.ProgramsExecuted; InstructionsUsed = clone.InstructionsUsed; Overspill = clone.Overspill; PathLength = clone.PathLength; Random = clone.Random; Currency = clone.Currency; CanGamble = clone.CanGamble; CanBufferOverflow = clone.CanBufferOverflow; HasDonkey = clone.HasDonkey; Difficulty = clone.Difficulty; CellsVisited = clone.CellsVisited.ToHashSet(); Shop = clone.Shop.Select(c => c.DeepClone()).ToList(); BoosterPack = clone.BoosterPack.Select(c => c.DeepClone()).ToList(); Discard = clone.Discard.Select(c => c.DeepClone()).ToList(); ProgramRow = clone.ProgramRow.Select(c => c.DeepClone()).ToList(); m_TapeCardIds = clone.m_TapeCardIds.ToHashSet(); PatchDeck = clone.PatchDeck.Select(c => c.DeepClone()).ToList(); } private static ImmutableArray CreateGlitchDeck(ref SRandom random) { var allCards = new List(); foreach (var type in s_GlitchCardTypes.Values) allCards.Add((GlitchCard)Activator.CreateInstance(type)!); random.Shuffle(CollectionsMarshal.AsSpan(allCards)); return [..allCards]; } private static Robot CreateRobot(MatchParameters parameters) { return parameters.RobotType switch { ERobotType.Vintage => new Vintage(parameters), ERobotType.Analyst => new Analyst(parameters), _ => throw new NotImplementedException() }; } public void ResetShop() { PatchDeck.AddRange(ProgramRow); PatchDeck.AddRange(Discard); ProgramRow.Clear(); m_TapeCardIds.Clear(); Discard.Clear(); ShuffleDeck(); RerollPatchDeck(); ProgramCount = 0; RerollCount = 0; CanGamble = true; CanBufferOverflow = true; } public void RerollPatchDeck() { Shop.Clear(); Shop.AddRange(CreatePatchDeck(ref m_Random, Difficulty, Balancing.Instance.ShopSize, false).ToList()); RerollCount += 1; } public void DrawHand() { var handCount = GetHandCards().Count; var toDraw = Math.Min(PatchDeck.Count, Math.Max(0, Currency.HandSize - handCount)); var drawnCards = PatchDeck.Take(toDraw).ToList(); ProgramRow.AddRange(drawnCards); PatchDeck.RemoveRange(0, toDraw); } public List GetHandCards() { return ProgramRow.Where(card => !m_TapeCardIds.Contains(card.CardId)).ToList(); } public List GetTapeCards() { return ProgramRow.Where(card => m_TapeCardIds.Contains(card.CardId)).ToList(); } public bool IsTapeCard(Card card) { return m_TapeCardIds.Contains(card.CardId); } public void SetProgramRow(IReadOnlyList orderedCards, IReadOnlyCollection tapeCardIds) { ProgramRow.Clear(); ProgramRow.AddRange(orderedCards); m_TapeCardIds.Clear(); if (tapeCardIds.Count == 0) return; var validIds = ProgramRow.Select(card => card.CardId).ToHashSet(); foreach (var cardId in tapeCardIds) { if (validIds.Contains(cardId)) m_TapeCardIds.Add(cardId); } } public void ClearTapeSelection() { m_TapeCardIds.Clear(); } public void RemoveProgramCard(Card card) { m_TapeCardIds.Remove(card.CardId); ProgramRow.Remove(card); } public void InsertProgramCard(int index, Card card, bool select) { ProgramRow.Insert(index, card); if (select) m_TapeCardIds.Add(card.CardId); else m_TapeCardIds.Remove(card.CardId); } public void AddProgramCard(Card card, bool select) { ProgramRow.Add(card); if (select) m_TapeCardIds.Add(card.CardId); else m_TapeCardIds.Remove(card.CardId); } public int GetProgramIndexForTapeIndex(int tapeIndex) { if (tapeIndex < 0) return -1; var current = -1; for (var i = 0; i < ProgramRow.Count; i++) { if (!m_TapeCardIds.Contains(ProgramRow[i].CardId)) continue; current += 1; if (current == tapeIndex) return i; } return -1; } public bool SwapTapeCards(int tapeIndexA, int tapeIndexB) { var indexA = GetProgramIndexForTapeIndex(tapeIndexA); var indexB = GetProgramIndexForTapeIndex(tapeIndexB); if (indexA < 0 || indexB < 0 || indexA == indexB) return false; (ProgramRow[indexA], ProgramRow[indexB]) = (ProgramRow[indexB], ProgramRow[indexA]); return true; } public void ShuffleDeck() { m_Random.Shuffle(CollectionsMarshal.AsSpan(PatchDeck)); } public int NextGlitch { get; set; } public int DeferGlitchCount { get; set; } public ERunPhase RunPhase { get; set; } public int ProgramCount { get; set; } public int RerollCount { get; set; } public int EnergyWasted { get; set; } public int ProgramsExecuted { get; set; } public int InstructionsUsed { get; set; } public int Overspill { get; set; } public int PathLength { get; set; } public ref SRandom Random => ref m_Random; public ref Currency Currency => ref Robot.Currency; public bool CanGamble { get; set; } = true; public bool CanBufferOverflow { get; set; } = true; public bool HasDonkey { get; set; } public HashSet CellsVisited { get; } = []; public List Shop { get; } = []; public List BoosterPack { get; set; } = []; public List Discard { get; } = []; public List ProgramRow { get; } = []; public IReadOnlyList Hand => GetHandCards(); public List PatchDeck { get; } = []; public IReadOnlyList Tape => GetTapeCards(); public IReadOnlyCollection TapeCardIds => m_TapeCardIds; public bool IsPreview { get; init; } public EDifficulty Difficulty { get; } private SRandom m_Random = random; private readonly HashSet m_TapeCardIds = []; private static readonly Dictionary s_PatchCardTypes = typeof(CoreLoop).Assembly.GetTypes().Where(t => !t.IsAbstract && t.BaseType == typeof(PatchCard) || t.BaseType?.BaseType == typeof(PatchCard)).ToDictionary(t => ((PatchCard)Activator.CreateInstance(t)!).Id); private static readonly Dictionary s_GlitchCardTypes = typeof(CoreLoop).Assembly.GetTypes().Where(t => !t.IsAbstract && t.BaseType == typeof(GlitchCard) || t.BaseType?.BaseType == typeof(GlitchCard)).ToDictionary(t => ((GlitchCard)Activator.CreateInstance(t)!).Id); }