ported from perforce

This commit is contained in:
2026-04-19 00:43:27 +02:00
commit 6c0c33f5d4
700 changed files with 19735 additions and 0 deletions

View File

@@ -0,0 +1,27 @@
using RobotAndDonkey.Game.Cards;
using RobotAndDonkey.Game.Execution.Results;
using RobotAndDonkey.Game.GameState;
namespace RobotAndDonkey.Game.Intents;
public class AcceptGlitch(Card card) : CardIntent(card)
{
public override void Run(Guid requestId, CoreLoop coreLoop, List<Intent> newIntents, List<Result> results)
{
Card.CreateIntents(null, coreLoop, requestId, newIntents, results);
results.Add(new RunCardResult(requestId, Card));
base.Run(requestId, coreLoop, newIntents, results);
}
public override bool IsValid(CoreLoop coreLoop)
{
if (Card != coreLoop.GlitchDeck[coreLoop.NextGlitch])
return false;
return base.IsValid(coreLoop);
}
public override bool Immune => true;
public override string ToString() => $"Accept {Card.Name}, " + base.ToString();
}

View File

@@ -0,0 +1,34 @@
using RobotAndDonkey.Game.Cards;
using RobotAndDonkey.Game.Execution.Results;
using RobotAndDonkey.Game.GameState;
namespace RobotAndDonkey.Game.Intents;
public class BuyPatch(Card card, bool free) : CardIntent(card, free ? 0 : card.ShopCost)
{
public override void Run(Guid requestId, CoreLoop coreLoop, List<Intent> newIntents, List<Result> results)
{
coreLoop.Shop.Remove(Card);
coreLoop.PatchDeck.Add(Card);
results.Add(new ShopResult(requestId, coreLoop.Shop.ToArray()));
results.Add(new DeckResult(requestId, coreLoop.PatchDeck.ToArray()));
base.Run(requestId, coreLoop, newIntents, results);
}
public override bool IsValid(CoreLoop coreLoop)
{
if (!Free && !coreLoop.Shop.Contains(Card))
return false;
if (Free && !coreLoop.BoosterPack.Contains(Card))
return false;
return base.IsValid(coreLoop);
}
public bool Free { get; } = free;
public override bool Immune => true;
public override string ToString() => $"Buy {Card.Name}, " + base.ToString();
}

View File

@@ -0,0 +1,8 @@
using RobotAndDonkey.Game.Cards;
namespace RobotAndDonkey.Game.Intents;
public class CardCostIntent(Card card, int energyCost) : CardIntent(card, energyCost)
{
public override string ToString() => $"Card cost for {Card.Name}, " + base.ToString();
}

View File

@@ -0,0 +1,8 @@
using RobotAndDonkey.Game.Cards;
namespace RobotAndDonkey.Game.Intents;
public abstract class CardIntent(Card card, int energyCost = 0) : Intent(energyCost)
{
public Card Card { get; } = card;
}

View File

@@ -0,0 +1,21 @@
using RobotAndDonkey.Game.Cards;
using RobotAndDonkey.Game.Execution.Results;
using RobotAndDonkey.Game.GameState;
using System;
using System.Collections.Generic;
namespace RobotAndDonkey.Game.Intents;
public class DeferGlitch(int energyCost) : Intent(energyCost)
{
public override void Run(Guid requestId, CoreLoop coreLoop, List<Intent> newIntents, List<Result> results)
{
coreLoop.DeferGlitchCount += 1;
results.Add(new DeferGlitchCountResult(requestId, coreLoop.DeferGlitchCount));
base.Run(requestId, coreLoop, newIntents, results);
}
public override bool Immune => true;
public override string ToString() => "Defer glitch, " + base.ToString();
}

View File

@@ -0,0 +1,29 @@
using RobotAndDonkey.Game.Cards;
using RobotAndDonkey.Game.Execution.Results;
using RobotAndDonkey.Game.GameState;
namespace RobotAndDonkey.Game.Intents;
public class DestroyCard(Card card) : CardIntent(card)
{
public override void Run(Guid requestId, CoreLoop coreLoop, List<Intent> newIntents, List<Result> results)
{
coreLoop.RemoveProgramCard(Card);
var handCards = coreLoop.GetHandCards();
coreLoop.PatchDeck.AddRange(handCards);
foreach (var handCard in handCards)
{
coreLoop.RemoveProgramCard(handCard);
}
coreLoop.ClearTapeSelection();
results.Add(new ProgramRowResult(requestId, coreLoop.ProgramRow.ToArray(), coreLoop.TapeCardIds));
results.Add(new DeckResult(requestId, coreLoop.PatchDeck.ToArray()));
results.Add(new HandResult(requestId, coreLoop.Hand.ToArray()));
base.Run(requestId, coreLoop, newIntents, results);
}
public override bool Immune => true;
public override string ToString() => $"Destroy {Card.Name}, " + base.ToString();
}

View File

@@ -0,0 +1,64 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using RobotAndDonkey.Game.Board;
using RobotAndDonkey.Game.Cards;
using RobotAndDonkey.Game.Execution.Results;
using RobotAndDonkey.Game.GameState;
using RobotAndDonkey.Game.Pois;
namespace RobotAndDonkey.Game.Intents;
public class DetoxiumPrime(Cell cell, Card card, EDirection direction) : Intent
{
public override void Run(Guid requestId, CoreLoop coreLoop, List<Intent> newIntents, List<Result> results)
{
var neighbour = cell.Hex.GetNeighbour(Direction);
var neighbourIndex = coreLoop.Board.FindCellIndex(neighbour);
if (neighbourIndex < 0)
{
results.Add(new InvalidInstructionResult(requestId, EInvalidReason.OutOfBounds, card, cell));
}
else
{
var neighbourCell = coreLoop.Board.Cells[neighbourIndex];
var newType = (neighbourCell.Type, Heal) switch
{
(ECellType.Blocked, true) => ECellType.Mud,
(ECellType.Rocky, true) => ECellType.Grass,
(ECellType.Mud, true) => ECellType.Grass,
(ECellType.Dry, true) => ECellType.Grass,
(ECellType.Grass, true) => ECellType.Fertile,
(ECellType.Grass, false) => coreLoop.Random.Next(3) switch
{
0 => ECellType.Dry,
1 => ECellType.Mud,
2 => ECellType.Rocky,
_ => throw new UnreachableException()
},
(ECellType.Fertile, false) => ECellType.Grass,
(ECellType.Mud, false) => ECellType.Blocked,
_ => (ECellType)(-1)
};
if (newType < 0)
{
results.Add(new InvalidInstructionResult(requestId, EInvalidReason.NoTarget, card, neighbourCell));
}
else
{
neighbourCell.Type = newType;
results.Add(new CellTypeResult(requestId, new(coreLoop.Board), neighbourCell.Hex));
}
}
base.Run(requestId, coreLoop, newIntents, results);
}
public EDirection Direction { get; set; } = direction;
public bool Heal { get; set; } = true;
public override bool IsValid(CoreLoop coreLoop) => base.IsValid(coreLoop) && cell.Poi is Avatar;
public override string ToString() => $"Detox {Direction}, Heal={Heal}, " + base.ToString();
}

View File

@@ -0,0 +1,75 @@
using RobotAndDonkey.Game.Cards;
using RobotAndDonkey.Game.Execution;
using RobotAndDonkey.Game.Execution.Results;
using RobotAndDonkey.Game.GameState;
namespace RobotAndDonkey.Game.Intents;
public class Discard(Guid[] cardIds, int energyCost) : Intent(energyCost)
{
public override void Run(Guid requestId, CoreLoop coreLoop, List<Intent> newIntents, List<Result> results)
{
var pool = coreLoop.ProgramRow.ToDictionary(card => card.CardId);
var discardCards = new List<Card>(cardIds.Length);
foreach (var cardId in cardIds)
{
if (pool.TryGetValue(cardId, out var card))
discardCards.Add(card);
}
var discardSet = discardCards.ToHashSet();
var newProgramRow = new List<Card>(coreLoop.ProgramRow.Count);
foreach (var card in coreLoop.ProgramRow)
{
if (!discardSet.Contains(card))
{
newProgramRow.Add(card);
continue;
}
coreLoop.Discard.Add(card);
if (coreLoop.PatchDeck.Count > 0)
{
var newCard = coreLoop.PatchDeck[0];
coreLoop.PatchDeck.RemoveAt(0);
newProgramRow.Add(newCard);
}
}
var remainingTapeIds = coreLoop.TapeCardIds.Where(id => !cardIds.Contains(id)).ToHashSet();
coreLoop.SetProgramRow(newProgramRow, remainingTapeIds);
results.Add(new CurrencyResult(requestId, coreLoop.Currency));
results.Add(new ProgramRowResult(requestId, coreLoop.ProgramRow.ToArray(), coreLoop.TapeCardIds));
results.Add(new HandResult(requestId, coreLoop.Hand.ToArray()));
results.Add(new TapeResult(requestId, coreLoop.Tape.ToArray()));
results.Add(new DeckResult(requestId, coreLoop.PatchDeck.ToArray()));
base.Run(requestId, coreLoop, newIntents, results);
}
public override bool IsValid(CoreLoop coreLoop)
{
if (!base.IsValid(coreLoop))
return false;
if (cardIds.Length == 0)
return false;
var uniqueIds = cardIds.ToHashSet();
if (uniqueIds.Count != cardIds.Length)
return false;
foreach (var cardId in uniqueIds)
{
if (!coreLoop.TapeCardIds.Contains(cardId))
return false;
}
return coreLoop.RunPhase == ERunPhase.ExecuteProgram;
}
public override bool Immune => true;
public override string ToString() => $"Discard {string.Join(", ", cardIds.Select(id => id.ToString()))}, " + base.ToString();
}

View File

@@ -0,0 +1,37 @@
using System;
using System.Collections.Generic;
using RobotAndDonkey.Game.Board;
using RobotAndDonkey.Game.Cards;
using RobotAndDonkey.Game.Data;
using RobotAndDonkey.Game.Execution.Results;
using RobotAndDonkey.Game.GameState;
using RobotAndDonkey.Game.Pois;
namespace RobotAndDonkey.Game.Intents;
public class DonkeyIntent(Card card, Cell donkeyCell, int energyCost) : Intent(energyCost)
{
public override void Run(Guid requestId, CoreLoop coreLoop, List<Intent> newIntents, List<Result> results)
{
if (coreLoop.HasDonkey && donkeyCell.Poi == null)
{
coreLoop.HasDonkey = false;
donkeyCell.Poi = new Donkey();
results.Add(new PoiResult(requestId, new(coreLoop.Board), donkeyCell.Poi, donkeyCell.Hex));
newIntents.Add(new ModifyCurrency(new(0, -Balancing.Instance.DonkeyMaxCarryBonus, 0, 0, 0, 0)));
}
else if (!coreLoop.HasDonkey && donkeyCell.Poi is Donkey donkey)
{
coreLoop.HasDonkey = true;
donkeyCell.Poi = null;
results.Add(new PoiResult(requestId, new(coreLoop.Board), donkey, donkeyCell.Hex));
newIntents.Add(new ModifyCurrency(new(0, Balancing.Instance.DonkeyMaxCarryBonus, 0, 0, 0, 0)));
}
else
{
results.Add(new InvalidInstructionResult(requestId, EInvalidReason.NoTarget, card, donkeyCell));
}
}
public override string ToString() => "Donkey, " + base.ToString();
}

View File

@@ -0,0 +1,33 @@
using RobotAndDonkey.Game.Cards;
using RobotAndDonkey.Game.Execution;
using RobotAndDonkey.Game.Execution.Results;
using RobotAndDonkey.Game.GameState;
using System;
using System.Collections.Generic;
using System.Numerics;
namespace RobotAndDonkey.Game.Intents;
public class EnterBufferOverflow(int energyCost) : Intent(energyCost)
{
public override void Run(Guid requestId, CoreLoop coreLoop, List<Intent> newIntents, List<Result> results)
{
coreLoop.CanBufferOverflow = false;
coreLoop.RunPhase = ERunPhase.BufferOverflow;
results.Add(new NoMoreBufferOverflowResult(requestId));
results.Add(new RunPhaseResult(requestId, coreLoop.RunPhase));
base.Run(requestId, coreLoop, newIntents, results);
}
public override bool IsValid(CoreLoop coreLoop)
{
if (!base.IsValid(coreLoop))
return false;
return coreLoop is { CanBufferOverflow: true, RunPhase: ERunPhase.Improve };
}
public override bool Immune => true;
public override string ToString() => "Enter buffer overflow, " + base.ToString();
}

View File

@@ -0,0 +1,32 @@
using RobotAndDonkey.Game.Data;
using RobotAndDonkey.Game.Execution;
using RobotAndDonkey.Game.Execution.Results;
using RobotAndDonkey.Game.GameState;
using System.Numerics;
namespace RobotAndDonkey.Game.Intents;
public class EnterGamble(int energyCost) : Intent(energyCost)
{
public override void Run(Guid requestId, CoreLoop coreLoop, List<Intent> newIntents, List<Result> results)
{
coreLoop.CanGamble = false;
coreLoop.BoosterPack = [..CoreLoop.CreatePatchDeck(ref coreLoop.Random, coreLoop.Difficulty, Balancing.Instance.GambleBoosterSize, true)];
coreLoop.RunPhase = ERunPhase.Gamble;
results.Add(new NoMoreGamblingResult(requestId));
results.Add(new RunPhaseResult(requestId, coreLoop.RunPhase));
base.Run(requestId, coreLoop, newIntents, results);
}
public override bool IsValid(CoreLoop coreLoop)
{
if (!base.IsValid(coreLoop))
return false;
return coreLoop is { CanGamble: true, RunPhase: ERunPhase.Improve };
}
public override bool Immune => true;
public override string ToString() => "Enter gamble, " + base.ToString();
}

View File

@@ -0,0 +1,32 @@
using System;
using System.Collections.Generic;
using RobotAndDonkey.Game.Execution.Results;
using RobotAndDonkey.Game.Utils;
using RobotAndDonkey.Game.GameState;
namespace RobotAndDonkey.Game.Intents;
public class EnterPreview : Intent
{
public override void Run(Guid requestId, CoreLoop coreLoop, List<Intent> newIntents, List<Result> results)
{
coreLoop.ShuffleDeck();
coreLoop.DrawHand();
coreLoop.ProgramCount = coreLoop.Robot.ProgramCount;
coreLoop.ClearTapeSelection();
var sortedHand = CardExtensions.SortForHand(coreLoop.GetHandCards());
coreLoop.SetProgramRow(sortedHand, Array.Empty<Guid>());
results.Add(new ProgramRowResult(requestId, coreLoop.ProgramRow.ToArray(), coreLoop.TapeCardIds));
results.Add(new HandResult(requestId, coreLoop.Hand.ToArray()));
results.Add(new TapeResult(requestId, coreLoop.Tape.ToArray()));
results.Add(new DeckResult(requestId, coreLoop.PatchDeck.ToArray()));
base.Run(requestId, coreLoop, newIntents, results);
}
public override bool Immune => true;
public override string ToString() => "Enter preview, " + base.ToString();
}

View File

@@ -0,0 +1,78 @@
using RobotAndDonkey.Game.Cards;
using RobotAndDonkey.Game.Execution.Results;
using RobotAndDonkey.Game.GameState;
using RobotAndDonkey.Game.Modifiers;
namespace RobotAndDonkey.Game.Intents;
public class ImmunizeCard(Card source, Card target, EModifierId modifierId, EModifierDuration duration, bool canCorrupt = true) : CardIntent(target)
{
public override void Run(Guid requestId, CoreLoop coreLoop, List<Intent> newIntents, List<Result> results)
{
if (DebuffState)
{
if (Card.Modifiers.OfType<GlobalImmunityModifier>().FirstOrDefault() is {} existingImmunity)
{
if (!existingImmunity.Modifiers.Any(m => m.Modifier == ModifierId && m.Source == source))
existingImmunity.Modifiers.Add((source, ModifierId));
}
else
{
var newImmunity = new GlobalImmunityModifier(Duration);
newImmunity.Modifiers.Add((source, ModifierId));
Card.InsertModifier(newImmunity, newIntents);
}
}
else
{
foreach (var modifier in Card.Modifiers.ToArray())
{
if (modifier is GlobalImmunityModifier giModifier)
{
for (var i = giModifier.Modifiers.Count - 1; i >= 0; i--)
{
var immunity = giModifier.Modifiers[i];
if (immunity.Modifier == ModifierId)
{
giModifier.Modifiers.RemoveAt(i);
break;
}
}
if (giModifier.Modifiers.Count == 0)
Card.RemoveModifier(modifier, newIntents);
}
}
}
results.Add(new ModifyCardResult(requestId, Card.DeepClone(), EModifierId.GlobalImmunity, ECardLocation.Tape));
base.Run(requestId, coreLoop, newIntents, results);
}
public override bool IsValid(CoreLoop coreLoop)
{
return base.IsValid(coreLoop) &&
ModifierId is
EModifierId.Corrupt or
EModifierId.Unreliable or
EModifierId.RaceCondition or
EModifierId.Throttled or
EModifierId.Effective or
EModifierId.Optimized or
EModifierId.Efficient or
EModifierId.Persistent or
EModifierId._Invalid;
}
public override string ToString()
{
return $"Immunize {Card.Name} from {source.Name} against {ModifierId}, {Duration}, DebuffState={DebuffState}, CanCorrupt={CanCorrupt}, " + base.ToString();
}
public EModifierId ModifierId { get; set; } = modifierId;
public bool DebuffState { get; set; } = true;
public bool CanCorrupt { get; set; } = canCorrupt;
public EModifierDuration Duration { get; set; } = duration;
}

View File

@@ -0,0 +1,33 @@
using RobotAndDonkey.Game.Cards;
using RobotAndDonkey.Game.Execution.Results;
using RobotAndDonkey.Game.GameState;
using RobotAndDonkey.Game.Modifiers;
using System;
using System.Collections.Generic;
namespace RobotAndDonkey.Game.Intents;
public abstract class Intent(int energyCost = 0)
{
public virtual void Run(Guid requestId, CoreLoop coreLoop, List<Intent> newIntents, List<Result> results)
{
if (EnergyCost != 0)
{
coreLoop.Currency.Energy -= EnergyCost;
results.Add(new CurrencyResult(requestId, coreLoop.Currency));
}
}
public virtual bool IsValid(CoreLoop coreLoop)
{
return DebuffSources.Count == 0 && coreLoop.Currency.Energy >= Math.Max(0, EnergyCost);
}
public int EnergyCost { get; set; } = energyCost;
public HashSet<Modifier> DebuffSources { get; } = [];
public virtual bool Immune { get; }
public override string ToString() => $"Costs {EnergyCost}" + (DebuffSources.Count > 0 ? ", debuffed" : "");
}

View File

@@ -0,0 +1,90 @@
using RobotAndDonkey.Game.Board;
using RobotAndDonkey.Game.Cards;
using RobotAndDonkey.Game.Data;
using RobotAndDonkey.Game.Execution.Results;
using RobotAndDonkey.Game.GameState;
using RobotAndDonkey.Game.Pois;
using System;
using System.Collections.Generic;
namespace RobotAndDonkey.Game.Intents;
public class Interact(Cell cell, Card card, EDirection direction) : Intent
{
public override void Run(Guid requestId, CoreLoop coreLoop, List<Intent> newIntents, List<Result> results)
{
if (cell.Type == ECellType.Dry)
{
EnergyCost += Balancing.Instance.DryInteractEnergyMalus;
}
var neighbour = cell.Hex.GetNeighbour(Direction);
var neighbourIndex = coreLoop.Board.FindCellIndex(neighbour);
if (neighbourIndex < 0)
{
results.Add(new InvalidInstructionResult(requestId, EInvalidReason.NotFound, card, cell));
}
else
{
var neighbourCell = coreLoop.Board.Cells[neighbourIndex];
if (neighbourCell.Poi is Crate crate)
{
var amount = Math.Min(crate.Amount, coreLoop.Currency.MaxCarry - coreLoop.Currency.Carry);
if (amount <= 0)
{
results.Add(new InvalidInstructionResult(requestId, EInvalidReason.NoAmount, card, cell));
}
else
{
newIntents.Add(new PickUp(neighbourCell, crate, amount, 0));
}
}
else if (neighbourCell.Poi is Shed shed)
{
var carrying = coreLoop.Currency.Carry;
if (carrying <= 0)
{
results.Add(new InvalidInstructionResult(requestId, EInvalidReason.NoAmount, card, cell));
}
else
{
var toDeliver = Math.Min(carrying, shed.Remaining);
var overspill = Math.Max(0, carrying - toDeliver);
if (toDeliver > 0)
{
shed.Received += toDeliver;
coreLoop.Currency.Carry -= toDeliver;
coreLoop.Currency.Delivery += toDeliver;
coreLoop.Overspill += overspill;
results.Add(new PoiResult(requestId, new(coreLoop.Board), shed, neighbourCell.Hex));
results.Add(new CurrencyResult(requestId, coreLoop.Currency));
results.Add(new ModifyRobotResult(requestId));
}
}
}
else if (neighbourCell.Poi is Donkey)
{
newIntents.Add(new DonkeyIntent(card, neighbourCell, 0));
}
else if (neighbourCell.Poi is Tower tower)
{
newIntents.Add(new TowerIntent(neighbourCell, tower, false, 0));
results.Add(new PoiResult(requestId, new(coreLoop.Board), tower, cell.Hex));
}
else
{
results.Add(new InvalidInstructionResult(requestId, EInvalidReason.NoTarget, card, cell));
}
}
base.Run(requestId, coreLoop, newIntents, results);
}
public EDirection Direction { get; set; } = direction;
public override bool IsValid(CoreLoop coreLoop) => base.IsValid(coreLoop) && cell.Poi is Avatar;
public override string ToString() => $"Interact with {Direction} from {cell}, " + base.ToString();
}

View File

@@ -0,0 +1,49 @@
using System;
using System.Collections.Generic;
using System.Linq;
using RobotAndDonkey.Game.Cards;
using RobotAndDonkey.Game.Execution.Results;
using RobotAndDonkey.Game.GameState;
using RobotAndDonkey.Game.Modifiers;
namespace RobotAndDonkey.Game.Intents;
public class ModifyCard(Card card, EModifierId modifierId, EModifierDuration duration, ECardLocation location) : CardIntent(card)
{
public override void Run(Guid requestId, CoreLoop coreLoop, List<Intent> newIntents, List<Result> results)
{
Card.AddModifier(Create(ModifierId, Duration), newIntents);
results.Add(new ModifyCardResult(requestId, Card.DeepClone(), ModifierId, location));
base.Run(requestId, coreLoop, newIntents, results);
}
public override bool IsValid(CoreLoop coreLoop)
{
return base.IsValid(coreLoop) &&
ModifierId is EModifierId.Corrupt or EModifierId.Unreliable or EModifierId.RaceCondition or EModifierId.Throttled or EModifierId.Effective or EModifierId.Optimized or EModifierId.Efficient or EModifierId.Persistent &&
Card.Modifiers.All(m => m.Id != ModifierId);
}
public static Modifier Create(EModifierId id, EModifierDuration duration)
{
return id switch
{
EModifierId.Corrupt => new CorruptCardModifier(duration),
EModifierId.Unreliable => new UnreliableCardModifier(duration),
EModifierId.RaceCondition => new RaceConditionModifier(duration),
EModifierId.Throttled => new ThrottledModifier(duration),
EModifierId.Effective => new EffectiveModifier(duration),
EModifierId.Optimized => new OptimizedModifier(duration),
EModifierId.Efficient => new EfficientModifier(duration),
EModifierId.Persistent => new PersistentModifier(duration),
_ => throw new ArgumentOutOfRangeException()
};
}
public EModifierId ModifierId { get; set;} = modifierId;
public EModifierDuration Duration { get; set;} = duration;
public override string ToString() => $"Modify {Card.Name} with {ModifierId}, {Duration}, " + base.ToString();
}

View File

@@ -0,0 +1,38 @@
using System;
using System.Collections.Generic;
using System.Linq;
using RobotAndDonkey.Game.Board;
using RobotAndDonkey.Game.Execution.Results;
using RobotAndDonkey.Game.GameState;
using RobotAndDonkey.Game.Modifiers;
namespace RobotAndDonkey.Game.Intents;
public class ModifyCell(Cell cell, EModifierId modifierId, EModifierDuration duration) : Intent
{
public override void Run(Guid requestId, CoreLoop coreLoop, List<Intent> newIntents, List<Result> results)
{
Cell.AddModifier(ModifierId switch
{
EModifierId.Rain => new RainModifier(Duration),
EModifierId.Drought => new DroughtModifier(Duration),
_ => throw new ArgumentOutOfRangeException()
}, newIntents);
results.Add(new ModifyCellResult(requestId, new(coreLoop.Board), ModifierId, Cell.Hex));
base.Run(requestId, coreLoop, newIntents, results);
}
public override bool IsValid(CoreLoop coreLoop)
{
return base.IsValid(coreLoop) && ModifierId is EModifierId.Rain or EModifierId.Drought && Cell.Modifiers.All(m => m.Id != ModifierId);
}
public EModifierId ModifierId { get; set; } = modifierId;
public EModifierDuration Duration { get; set; } = duration;
public Cell Cell { get; set; } = cell;
public override string ToString() => $"Modify {Cell.Hex} with {ModifierId}, {Duration}, " + base.ToString();
}

View File

@@ -0,0 +1,40 @@
using System;
using System.Collections.Generic;
using RobotAndDonkey.Game.Execution.Results;
using RobotAndDonkey.Game.GameState;
using RobotAndDonkey.Game.Modifiers;
namespace RobotAndDonkey.Game.Intents;
public class ModifyCurrency(Currency delta, bool canCorrupt = false) : Intent
{
public override void Run(Guid requestId, CoreLoop coreLoop, List<Intent> newIntents, List<Result> results)
{
coreLoop.Currency.Energy += Delta.Energy;
coreLoop.Currency.MaxCarry += Delta.MaxCarry;
coreLoop.Currency.Carry += Delta.Carry;
coreLoop.Currency.Delivery += Delta.Delivery;
coreLoop.Currency.TapeLength += Delta.TapeLength;
coreLoop.Currency.HandSize += Delta.HandSize;
results.Add(new CurrencyResult(requestId, coreLoop.Currency));
base.Run(requestId, coreLoop, newIntents, results);
}
public override bool IsValid(CoreLoop coreLoop)
{
return base.IsValid(coreLoop) &&
coreLoop.Currency.Energy + Delta.Energy >= 0 &&
coreLoop.Currency.MaxCarry + Delta.MaxCarry >= 0 &&
coreLoop.Currency.Carry + Delta.Carry >= 0 &&
coreLoop.Currency.Delivery + Delta.Delivery >= 0 &&
coreLoop.Currency.TapeLength + Delta.TapeLength >= 0 &&
coreLoop.Currency.HandSize + Delta.HandSize >= 0;
}
public Currency Delta { get; set; } = delta;
public bool CanCorrupt { get; set; } = canCorrupt;
public override string ToString() => $"Currency delta: {Delta}, CanCorrupt={CanCorrupt} " + base.ToString();
}

View File

@@ -0,0 +1,35 @@
using RobotAndDonkey.Game.Execution.Results;
using RobotAndDonkey.Game.GameState;
using RobotAndDonkey.Game.Modifiers;
namespace RobotAndDonkey.Game.Intents;
public class ModifyRobot(EModifierId modifierId, EModifierDuration duration) : Intent
{
public override void Run(Guid requestId, CoreLoop coreLoop, List<Intent> newIntents, List<Result> results)
{
coreLoop.Robot.AddModifier(ModifierId switch
{
EModifierId.Pest => new PestModifier(Duration),
EModifierId.Gravity => new GravityModifier(Duration),
EModifierId.HeatWave => new HeatWaveModifier(Duration),
_ => throw new ArgumentOutOfRangeException()
}, newIntents);
results.Add(new ModifyRobotResult(requestId));
base.Run(requestId, coreLoop, newIntents, results);
}
public override bool IsValid(CoreLoop coreLoop)
{
return base.IsValid(coreLoop) &&
ModifierId is EModifierId.Pest or EModifierId.Gravity or EModifierId.HeatWave &&
coreLoop.Robot.Modifiers.All(m => m.Id != ModifierId);
}
public EModifierId ModifierId { get; set; } = modifierId;
public EModifierDuration Duration { get; set; } = duration;
public override string ToString() => $"Modify robot with {ModifierId}, {Duration}, " + base.ToString();
}

View File

@@ -0,0 +1,84 @@
using System;
using System.Collections.Generic;
using RobotAndDonkey.Game.Board;
using RobotAndDonkey.Game.Cards;
using RobotAndDonkey.Game.Data;
using RobotAndDonkey.Game.Execution.Results;
using RobotAndDonkey.Game.GameState;
using RobotAndDonkey.Game.Pois;
namespace RobotAndDonkey.Game.Intents;
public class Move(Cell cell, Card card, EDirection direction, bool ignoreBlocking, int amount) : Intent
{
public override void Run(Guid requestId, CoreLoop coreLoop, List<Intent> newIntents, List<Result> results)
{
if (cell.Type == ECellType.Mud)
{
newIntents.Add(new ModifyCurrency(new(-Balancing.Instance.MudMoveEnergyCost, 0, 0, 0, 0, 0)));
}
var neighbour = cell.Hex;
for (int i = 0; i < Amount; ++i)
neighbour = neighbour.GetNeighbour(Direction);
var neighbourIndex = coreLoop.Board.FindCellIndex(neighbour);
if (neighbourIndex < 0)
{
results.Add(new InvalidInstructionResult(requestId, EInvalidReason.OutOfBounds, card, cell));
}
else
{
var neighbourCell = coreLoop.Board.Cells[neighbourIndex];
if (!ignoreBlocking && (neighbourCell.Type == ECellType.Blocked || neighbourCell.Poi != null))
{
results.Add(new InvalidInstructionResult(requestId, EInvalidReason.Blocked, card, neighbourCell));
}
else
{
var avatar = (Avatar)cell.Poi!;
cell.Poi = null;
neighbourCell.Poi = avatar;
results.Add(new PoiResult(requestId, new(coreLoop.Board), null, cell.Hex));
results.Add(new PoiResult(requestId, new(coreLoop.Board), avatar, neighbour));
coreLoop.PathLength += Amount;
coreLoop.CellsVisited.Add(cell.Hex);
coreLoop.CellsVisited.Add(neighbour);
if (coreLoop.Currency.Energy == 0)
{
coreLoop.EnergyWasted += 1;
}
foreach (var modifier in neighbourCell.Modifiers)
{
if (modifier.DebuffSources.Count > 0)
continue;
switch (modifier.Id)
{
case EModifierId.Corrupt:
case EModifierId.Unreliable:
case EModifierId.RaceCondition:
{
var tape = coreLoop.GetTapeCards();
var currentIndex = tape.IndexOf(card);
for (int i = currentIndex + 1; i < tape.Count; ++i)
newIntents.Add(new ModifyCard(tape[i], modifier.Id, EModifierDuration.Temporary, ECardLocation.Tape));
break;
}
}
}
}
}
base.Run(requestId, coreLoop, newIntents, results);
}
public EDirection Direction { get; set; } = direction;
public override bool IsValid(CoreLoop coreLoop) => base.IsValid(coreLoop) && cell.Poi is Avatar;
public int Amount { get; set; } = amount;
public override string ToString() => $"Move towards {Direction} by {Amount}, " + base.ToString();
}

View File

@@ -0,0 +1,73 @@
using RobotAndDonkey.Game.Cards;
using RobotAndDonkey.Game.Data;
using RobotAndDonkey.Game.Execution;
using RobotAndDonkey.Game.Execution.Results;
using RobotAndDonkey.Game.GameState;
namespace RobotAndDonkey.Game.Intents;
public class MoveCards(Guid[] orderedCardIds, Guid[] tapeCardIds) : Intent
{
public override void Run(Guid requestId, CoreLoop coreLoop, List<Intent> newIntents, List<Result> results)
{
var pool = coreLoop.ProgramRow.ToDictionary(card => card.CardId);
var orderedCards = new List<Card>(orderedCardIds.Length);
foreach (var cardId in orderedCardIds)
{
if (pool.TryGetValue(cardId, out var card))
orderedCards.Add(card);
}
var tapeSet = tapeCardIds.ToHashSet();
var tapeCards = orderedCards.Where(card => tapeSet.Contains(card.CardId)).ToList();
var handCards = orderedCards.Where(card => !tapeSet.Contains(card.CardId)).ToList();
var occupiedHandSpace = handCards.Select(c => c.OccupiedSpace).Sum();
if (occupiedHandSpace > coreLoop.Currency.HandSize)
{
results.Add(new InvalidInstructionResult(requestId, EInvalidReason.NoSpace, null, null));
return;
}
var occupiedTapeSpace = tapeCards.Select(c => c.OccupiedSpace).Sum();
if (occupiedTapeSpace > coreLoop.Currency.TapeLength)
{
results.Add(new InvalidInstructionResult(requestId, EInvalidReason.NoSpace, null, null));
return;
}
coreLoop.SetProgramRow(orderedCards, tapeSet);
results.Add(new ProgramRowResult(requestId, coreLoop.ProgramRow.ToArray(), coreLoop.TapeCardIds));
results.Add(new HandResult(requestId, handCards.ToArray()));
results.Add(new TapeResult(requestId, tapeCards.ToArray()));
base.Run(requestId, coreLoop, newIntents, results);
}
public override bool IsValid(CoreLoop coreLoop)
{
if (!base.IsValid(coreLoop))
return false;
var inputSet = orderedCardIds.ToHashSet();
if (inputSet.Count != orderedCardIds.Length)
return false;
var coreLoopSet = coreLoop.ProgramRow.Select(card => card.CardId).ToHashSet();
if (!coreLoopSet.SetEquals(inputSet))
return false;
foreach (var cardId in tapeCardIds)
{
if (!inputSet.Contains(cardId))
return false;
}
return coreLoop.RunPhase == ERunPhase.ExecuteProgram;
}
public override bool Immune => true;
public override string ToString() => $"Rearrange, Ordered={string.Join(", ", orderedCardIds.Select(c => c.ToString()))}, Tape={string.Join(", ", tapeCardIds.Select(c => c.ToString()))}, " + base.ToString();
}

View File

@@ -0,0 +1,29 @@
using System;
using System.Collections.Generic;
using RobotAndDonkey.Game.Execution;
using RobotAndDonkey.Game.Execution.Results;
using RobotAndDonkey.Game.GameState;
namespace RobotAndDonkey.Game.Intents;
public class NextGlitch : Intent
{
public override void Run(Guid requestId, CoreLoop coreLoop, List<Intent> newIntents, List<Result> results)
{
coreLoop.NextGlitch = (coreLoop.NextGlitch + 1) % coreLoop.GlitchDeck.Length;
results.Add(new NextGlitchResult(requestId, coreLoop.NextGlitch));
base.Run(requestId, coreLoop, newIntents, results);
}
public override bool IsValid(CoreLoop coreLoop)
{
if (!base.IsValid(coreLoop))
return false;
return coreLoop.RunPhase == ERunPhase.DrawGlitch;
}
public override bool Immune => true;
public override string ToString() => "Next glitch, " + base.ToString();
}

View File

@@ -0,0 +1,32 @@
using RobotAndDonkey.Game.Execution;
using RobotAndDonkey.Game.Execution.Results;
using RobotAndDonkey.Game.GameState;
namespace RobotAndDonkey.Game.Intents;
public class NextPhase(ERunPhase phase) : Intent()
{
public override void Run(Guid requestId, CoreLoop coreLoop, List<Intent> newIntents, List<Result> results)
{
coreLoop.RunPhase = Phase;
results.Add(new RunPhaseResult(requestId, coreLoop.RunPhase));
base.Run(requestId, coreLoop, newIntents, results);
}
public override bool IsValid(CoreLoop coreLoop)
{
if (!base.IsValid(coreLoop))
return false;
if (coreLoop.RunPhase == Phase)
return false;
return true;
}
public override bool Immune => true;
public ERunPhase Phase { get; } = phase;
public override string ToString() => $"Next phase: {Phase}, " + base.ToString();
}

View File

@@ -0,0 +1,37 @@
using System;
using System.Collections.Generic;
using RobotAndDonkey.Game.Board;
using RobotAndDonkey.Game.Cards.Patches;
using RobotAndDonkey.Game.Execution.Results;
using RobotAndDonkey.Game.GameState;
using RobotAndDonkey.Game.Pois;
namespace RobotAndDonkey.Game.Intents;
public class PickUp(Cell neighbourCell, Crate crate, int amount, int energyCost) : Intent(energyCost)
{
public override void Run(Guid requestId, CoreLoop coreLoop, List<Intent> newIntents, List<Result> results)
{
if (Amount == 0)
{
results.Add(new InvalidInstructionResult(requestId, EInvalidReason.NoAmount, null, neighbourCell));
}
else
{
coreLoop.Currency.Carry += Amount;
crate.Amount -= Amount;
results.Add(new PoiResult(requestId, new(coreLoop.Board), crate, neighbourCell.Hex));
results.Add(new CurrencyResult(requestId, coreLoop.Currency));
base.Run(requestId, coreLoop, newIntents, results);
}
}
public override bool IsValid(CoreLoop coreLoop)
{
return base.IsValid(coreLoop) && Amount <= coreLoop.Currency.MaxCarry - coreLoop.Currency.Carry;
}
public int Amount { get; set; } = amount;
public override string ToString() => $"Pick up {Amount}, " + base.ToString();
}

View File

@@ -0,0 +1,28 @@
using System;
using System.Collections.Generic;
using RobotAndDonkey.Game.Execution;
using RobotAndDonkey.Game.Execution.Results;
using RobotAndDonkey.Game.GameState;
namespace RobotAndDonkey.Game.Intents;
public class Reroll(int energyCost) : Intent(energyCost)
{
public override void Run(Guid requestId, CoreLoop coreLoop, List<Intent> newIntents, List<Result> results)
{
coreLoop.RerollPatchDeck();
base.Run(requestId, coreLoop, newIntents, results);
}
public override bool IsValid(CoreLoop coreLoop)
{
if (!base.IsValid(coreLoop))
return false;
return coreLoop.RunPhase == ERunPhase.Improve;
}
public override bool Immune => true;
public override string ToString() => "Reroll, " + base.ToString();
}

View File

@@ -0,0 +1,24 @@
using RobotAndDonkey.Game.Board;
using RobotAndDonkey.Game.Data;
using RobotAndDonkey.Game.Execution.Results;
using RobotAndDonkey.Game.GameState;
namespace RobotAndDonkey.Game.Intents;
public class Rest(Cell cell, int restoreEnergyAmount) : Intent(-restoreEnergyAmount)
{
public override void Run(Guid requestId, CoreLoop coreLoop, List<Intent> newIntents, List<Result> results)
{
if (cell.Type == ECellType.Fertile)
{
EnergyCost -= Balancing.Instance.FertileRestEnergyReplenish;
}
else if (cell.Type == ECellType.Dry)
{
EnergyCost = Math.Min(0, EnergyCost + Balancing.Instance.DryRestEnergyMalus);
}
base.Run(requestId, coreLoop, newIntents, results);
}
public override string ToString() => "Rest, " + base.ToString();
}

View File

@@ -0,0 +1,178 @@
using RobotAndDonkey.Game.Board;
using RobotAndDonkey.Game.Cards;
using RobotAndDonkey.Game.Data;
using RobotAndDonkey.Game.Execution;
using RobotAndDonkey.Game.Execution.Results;
using RobotAndDonkey.Game.GameState;
using RobotAndDonkey.Game.Modifiers;
using RobotAndDonkey.Game.Pois;
using RobotAndDonkey.Game.Robots;
using RobotAndDonkey.Game.Utils;
namespace RobotAndDonkey.Game.Intents;
public class RunProgram : Intent
{
public static IEnumerable<(Entity Entity, ECardLocation Location)> CollectEntities(CoreLoop coreLoop)
{
yield return (coreLoop.Robot, ECardLocation.Robot);
foreach (var card in coreLoop.PatchDeck)
yield return (card, ECardLocation.Deck);
foreach (var card in coreLoop.Hand)
yield return (card, ECardLocation.Hand);
foreach (var card in coreLoop.Tape)
yield return (card, ECardLocation.Tape);
foreach (var card in coreLoop.Discard)
yield return (card, ECardLocation.Discard);
foreach (var card in coreLoop.Board.Cells)
yield return (card, ECardLocation.Board);
}
public override void Run(Guid requestId, CoreLoop coreLoop, List<Intent> newIntents, List<Result> results)
{
var board = coreLoop.Board;
coreLoop.ProgramsExecuted += 1;
results.Add(new ProgramResult(requestId, coreLoop.ProgramsExecuted));
var modifierStack = new ModifierStack();
modifierStack.Push(coreLoop.Robot);
var tapeCards = coreLoop.GetTapeCards();
var minCount = Math.Min(tapeCards.Count, coreLoop.Currency.TapeLength);
for (var i = 0; i < minCount; i++)
{
var card = tapeCards[i];
if (!card.Modifiers.Any(m => m is { Id: EModifierId.RaceCondition, DebuffSources.Count: 0 }))
continue;
if (coreLoop.Random.NextSingle() > Balancing.Instance.RaceConditionChance)
continue;
var targetIndex = CardExtensions.NextIndexConsideringCorruption(i, coreLoop);
if (targetIndex >= 0 && targetIndex < tapeCards.Count)
{
results.Add(new ModifyCardResult(requestId, tapeCards[targetIndex].DeepClone(), EModifierId.RaceCondition, ECardLocation.Tape));
if (coreLoop.SwapTapeCards(i, targetIndex))
{
(tapeCards[i], tapeCards[targetIndex]) = (tapeCards[targetIndex], tapeCards[i]);
results.Add(new ProgramRowResult(requestId, coreLoop.ProgramRow.ToArray(), coreLoop.TapeCardIds));
results.Add(new TapeResult(requestId, tapeCards.ToArray()));
}
}
}
tapeCards = coreLoop.GetTapeCards();
minCount = Math.Min(tapeCards.Count, coreLoop.Currency.TapeLength);
for (var i = 0; i < minCount; i++)
{
var card = tapeCards[i];
var avatarCell = board.Cells.Single(c => c.Poi is Avatar);
modifierStack.Push(avatarCell);
modifierStack.Push(card);
results.Add(new RunCardResult(requestId, card));
var intents = new List<Intent>();
card.CreateIntents(avatarCell, coreLoop, requestId, intents, results);
modifierStack.Execute(requestId, coreLoop, intents, results, false, true);
coreLoop.InstructionsUsed += 1;
modifierStack.Pop();
modifierStack.Pop();
}
modifierStack.Pop();
static IEnumerable<(Entity Owner, Modifier Modifier, ECardLocation Location)> CollectModifiers(CoreLoop coreLoop, EModifierDuration duration)
{
foreach (var (entity, location) in CollectEntities(coreLoop))
{
foreach (var modifier in entity.Modifiers)
{
if (modifier.Duration == duration)
yield return (entity, modifier, location);
}
}
}
void RemoveModifier(Entity entity, Modifier modifier, ECardLocation location)
{
entity.RemoveModifier(modifier, newIntents);
if (entity is Cell cell)
results.Add(new ModifyCellResult(requestId, new(coreLoop.Board), modifier.Id, cell.Hex));
if (entity is Card card)
results.Add(new ModifyCardResult(requestId, card.DeepClone(), modifier.Id, location));
if (entity is Robot)
results.Add(new ModifyRobotResult(requestId));
}
foreach (var (entity, modifier, location) in CollectModifiers(coreLoop, EModifierDuration.ShortTerm).ToArray())
RemoveModifier(entity, modifier, location);
tapeCards = coreLoop.GetTapeCards();
foreach (var card in tapeCards)
{
var isPersistent = card.Modifiers.Any(m => m is PersistentModifier && m.DebuffSources.Count == 0);
if (!isPersistent)
{
coreLoop.Discard.Add(card);
coreLoop.RemoveProgramCard(card);
}
}
coreLoop.ClearTapeSelection();
coreLoop.ProgramCount -= 1;
results.Add(new ProgramRowResult(requestId, coreLoop.ProgramRow.ToArray(), coreLoop.TapeCardIds));
results.Add(new CurrencyResult(requestId, coreLoop.Currency));
results.Add(new HandResult(requestId, coreLoop.Hand.ToArray()));
results.Add(new DiscardResult(requestId, coreLoop.Discard.ToArray()));
results.Add(new ProgramResult(requestId, coreLoop.ProgramCount));
results.Add(new TapeResult(requestId, coreLoop.Tape.ToArray()));
results.Add(new DeckResult(requestId, coreLoop.PatchDeck.ToArray()));
var victory = coreLoop.Board.Cells.Select(c => c.Poi as Shed).Where(s => s != null).Sum(s => s!.Remaining) == 0;
if (coreLoop.ProgramCount == 0 || coreLoop.PatchDeck.Count == 0 || coreLoop.Currency.Energy <= 0 || victory)
{
coreLoop.Currency.Energy += Balancing.Instance.EndOfProgramEnergyReplenish;
coreLoop.RunPhase = ERunPhase.Scoring;
foreach (var (entity, modifier, location) in CollectModifiers(coreLoop, EModifierDuration.Temporary).ToArray())
{
RemoveModifier(entity, modifier, location);
}
results.Add(new HandResult(requestId, coreLoop.Hand.ToArray()));
results.Add(new DiscardResult(requestId, coreLoop.Discard.ToArray()));
results.Add(new TapeResult(requestId, coreLoop.Tape.ToArray()));
results.Add(new DeckResult(requestId, coreLoop.PatchDeck.ToArray()));
results.Add(new RunPhaseResult(requestId, coreLoop.RunPhase));
}
else
{
coreLoop.DrawHand();
}
base.Run(requestId, coreLoop, newIntents, results);
}
public override bool IsValid(CoreLoop coreLoop)
{
if (!base.IsValid(coreLoop))
return false;
return coreLoop.RunPhase == ERunPhase.ExecuteProgram;
}
public override bool Immune => true;
public override string ToString() => "Run program, " + base.ToString();
}

View File

@@ -0,0 +1,77 @@
using System;
using System.Collections.Generic;
using RobotAndDonkey.Game.Board;
using RobotAndDonkey.Game.Execution.Results;
using RobotAndDonkey.Game.GameState;
using RobotAndDonkey.Game.Pois;
namespace RobotAndDonkey.Game.Intents;
public class TowerIntent(Cell cell, Tower tower, bool active, int energyCost) : Intent(energyCost)
{
public override void Run(Guid requestId, CoreLoop coreLoop, List<Intent> newIntents, List<Result> results)
{
var board = coreLoop.Board;
foreach (var modifier in Cell.Modifiers)
{
if (modifier.Id != EModifierId.Unreliable)
continue;
if (Active)
{
modifier.DebuffSources.Remove(Cell);
}
else
{
modifier.DebuffSources.Add(Cell);
}
}
Tower.Active = Active;
results.Add(new PoiResult(requestId, new(coreLoop.Board), Tower, Cell.Hex));
for (var direction = 0; direction < 6; direction++)
{
var neighbourHex = Cell.Hex.GetNeighbour((EDirection)direction);
var neighbourIndex = board.FindCellIndex(neighbourHex);
if (neighbourIndex < 0)
continue;
var neighbourCell = board.Cells[neighbourIndex];
foreach (var modifier in neighbourCell.Modifiers)
{
if (modifier.Id != EModifierId.Unreliable)
continue;
if (Active)
{
modifier.DebuffSources.Remove(Cell);
}
else
{
modifier.DebuffSources.Add(Cell);
}
results.Add(new ModifyCellResult(requestId, new(coreLoop.Board), EModifierId.Unreliable, neighbourCell.Hex));
}
}
base.Run(requestId, coreLoop, newIntents, results);
}
public override bool IsValid(CoreLoop coreLoop)
{
if (Tower.Active == Active)
return false;
return base.IsValid(coreLoop);
}
public Cell Cell { get; } = cell;
public Tower Tower { get; } = tower;
public bool Active { get; set; } = active;
public override string ToString() => $"Tower at {Cell.Hex}, Active={Active}, " + base.ToString();
}

View File

@@ -0,0 +1,27 @@
using RobotAndDonkey.Game.Board;
using RobotAndDonkey.Game.Execution.Results;
using RobotAndDonkey.Game.GameState;
using RobotAndDonkey.Game.Pois;
namespace RobotAndDonkey.Game.Intents;
public class Turn(Cell cell, int delta) : Intent
{
public override void Run(Guid requestId, CoreLoop coreLoop, List<Intent> newIntents, List<Result> results)
{
var avatar = (Avatar)cell.Poi!;
var newDirection = ((int)avatar.Direction + Delta) % 6;
if (newDirection < 0)
newDirection += 6;
avatar.Direction = (EDirection)newDirection;
results.Add(new PoiResult(requestId, new(coreLoop.Board), avatar, cell.Hex));
base.Run(requestId, coreLoop, newIntents, results);
}
public override bool IsValid(CoreLoop coreLoop) => base.IsValid(coreLoop) && cell.Poi is Avatar;
public int Delta { get; set; } = delta;
public override string ToString() => $"Turn by {Delta}, " + base.ToString();
}