Files
zfxaction25/RobotAndDonkey.Game/GameState/CoreLoop.cs
2026-04-19 00:43:27 +02:00

348 lines
11 KiB
C#

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<Card> GlitchDeck, Board.Board Board, Robot Robot)
{
public static ImmutableArray<Card> 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<PatchCard>();
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<Card>(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<Card> CreateGlitchDeck(ref SRandom random)
{
var allCards = new List<GlitchCard>();
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<Card> GetHandCards()
{
return ProgramRow.Where(card => !m_TapeCardIds.Contains(card.CardId)).ToList();
}
public List<Card> 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<Card> orderedCards, IReadOnlyCollection<Guid> 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<Hex> CellsVisited { get; } = [];
public List<Card> Shop { get; } = [];
public List<Card> BoosterPack { get; set; } = [];
public List<Card> Discard { get; } = [];
public List<Card> ProgramRow { get; } = [];
public IReadOnlyList<Card> Hand => GetHandCards();
public List<Card> PatchDeck { get; } = [];
public IReadOnlyList<Card> Tape => GetTapeCards();
public IReadOnlyCollection<Guid> TapeCardIds => m_TapeCardIds;
public bool IsPreview { get; init; }
public EDifficulty Difficulty { get; }
private SRandom m_Random = random;
private readonly HashSet<Guid> m_TapeCardIds = [];
private static readonly Dictionary<ECard, Type> 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<ECard, Type> 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);
}