ported from perforce
This commit is contained in:
348
RobotAndDonkey.Game/GameState/CoreLoop.cs
Normal file
348
RobotAndDonkey.Game/GameState/CoreLoop.cs
Normal file
@@ -0,0 +1,348 @@
|
||||
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);
|
||||
}
|
||||
Reference in New Issue
Block a user