ported from perforce
This commit is contained in:
373
RobotAndDonkey.Game/Board/Board.cs
Normal file
373
RobotAndDonkey.Game/Board/Board.cs
Normal file
@@ -0,0 +1,373 @@
|
||||
using System.Collections.Immutable;
|
||||
using System.Diagnostics;
|
||||
using System.Runtime.InteropServices;
|
||||
using RobotAndDonkey.Game.Data;
|
||||
using RobotAndDonkey.Game.Modifiers;
|
||||
using RobotAndDonkey.Game.Pois;
|
||||
using RobotAndDonkey.Game.Utils;
|
||||
|
||||
namespace RobotAndDonkey.Game.Board;
|
||||
|
||||
public record Board(ImmutableArray<Cell> Cells, int TargetDeliveryAmount)
|
||||
{
|
||||
public Board(Board clone)
|
||||
{
|
||||
Cells = [.. clone.Cells.Select(c => new Cell(c))];
|
||||
TargetDeliveryAmount = clone.TargetDeliveryAmount;
|
||||
}
|
||||
|
||||
public int FindCellIndex(Hex hex)
|
||||
{
|
||||
// TODO calculate in O(1) with BoardSize
|
||||
for (var i = 0; i < Cells.Length; i++)
|
||||
if (Cells[i].Hex == hex)
|
||||
return i;
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
public static Board Generate(ref SRandom random, EDifficulty difficulty)
|
||||
{
|
||||
var cells = new List<Cell>();
|
||||
var fDifficulty = 0.5f + (int)difficulty / 4.0f;
|
||||
var boardSize = (int)(2 + fDifficulty * 2);
|
||||
// TODO: find better order of adding cells to facilitate FindCellIndex
|
||||
for (var x = -boardSize; x <= boardSize; x++)
|
||||
for (var y = -boardSize; y <= boardSize; y++)
|
||||
{
|
||||
var hex = new Hex(x, y);
|
||||
if (Hex.Distance(hex, new()) > boardSize)
|
||||
continue;
|
||||
|
||||
var cell = new Cell(hex) { Type = ECellType.Grass };
|
||||
cells.Add(cell);
|
||||
}
|
||||
|
||||
var avatarCell = cells.Single(c => c.Hex is { X: 0, Y: 0 });
|
||||
var avatar = new Avatar { Direction = (EDirection)random.Next(6) };
|
||||
avatarCell.Poi = avatar;
|
||||
|
||||
var freeCells = cells.ToList();
|
||||
freeCells.Remove(avatarCell);
|
||||
|
||||
for (var x = -boardSize - 1; x <= boardSize + 1; x++)
|
||||
for (var y = -boardSize - 1; y <= boardSize + 1; y++)
|
||||
{
|
||||
var hex = new Hex(x, y);
|
||||
if (Hex.Distance(hex, new()) != boardSize + 1)
|
||||
continue;
|
||||
|
||||
var cell = new Cell(hex) { Type = ECellType.Grass };
|
||||
cells.Add(cell);
|
||||
}
|
||||
|
||||
var blockedAmount = (int)(freeCells.Count * Balancing.Instance.BlockedAmount * fDifficulty);
|
||||
Splat(freeCells, freeCells.Where(c => Hex.Distance(c.Hex, new()) is 1 or 2).ToList(), ref random, blockedAmount, Balancing.Instance.BlockedSpread, c =>
|
||||
{
|
||||
if (!PlacementKeepsConnectivity(cells, c))
|
||||
return false;
|
||||
|
||||
c.Type = ECellType.Blocked;
|
||||
return true;
|
||||
});
|
||||
Sprinkle(freeCells, ref random, Balancing.Instance.DonkeySprinkle, c => c != ECellType.Blocked, c => c.Poi = new Donkey());
|
||||
Sprinkle(freeCells, ref random, Balancing.Instance.ShedSprinkle, c => c != ECellType.Blocked, c => c.Poi = new Shed());
|
||||
Sprinkle(freeCells, ref random, Balancing.Instance.CrateSprinkle, c => c != ECellType.Blocked, c => c.Poi = new Crate { Amount = Balancing.Instance.CrateAmount });
|
||||
Sprinkle(freeCells, ref random, Balancing.Instance.TowerSprinkle, c => c != ECellType.Blocked, c => c.Poi = new Tower());
|
||||
|
||||
var poiCells = freeCells.Where(c =>
|
||||
{
|
||||
if (c.Poi != null)
|
||||
return false;
|
||||
|
||||
for (var dir = EDirection.Right; dir <= EDirection.BottomRight; ++dir)
|
||||
{
|
||||
var neighbour = c.Hex.GetNeighbour(dir);
|
||||
var next = cells.FindIndex(n => n.Hex == neighbour);
|
||||
if (next >= 0 && cells[next].Poi != null)
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}).ToList();
|
||||
|
||||
freeCells.RemoveAll(c => c.Poi != null);
|
||||
|
||||
var goodCells = cells.Where(c => c.Type != ECellType.Blocked).ToList();
|
||||
var dryAmount = (int)(goodCells.Count * Balancing.Instance.DryAmount * fDifficulty);
|
||||
var fertileAmount = (int)(goodCells.Count * Balancing.Instance.FertileAmount * (1 - fDifficulty));
|
||||
var mudAmount = (int)(goodCells.Count * Balancing.Instance.MudAmount * fDifficulty);
|
||||
var rockyAmount = (int)(goodCells.Count * Balancing.Instance.RockyAmount * fDifficulty);
|
||||
Splat(goodCells, goodCells, ref random, dryAmount, Balancing.Instance.DrySpread, c =>
|
||||
{
|
||||
c.Type = ECellType.Dry;
|
||||
return true;
|
||||
});
|
||||
Splat(goodCells, goodCells, ref random, fertileAmount, Balancing.Instance.FertileSpread, c =>
|
||||
{
|
||||
c.Type = ECellType.Fertile;
|
||||
return true;
|
||||
});
|
||||
Splat(goodCells, goodCells, ref random, mudAmount, Balancing.Instance.MudSpread, c =>
|
||||
{
|
||||
c.Type = ECellType.Mud;
|
||||
return true;
|
||||
});
|
||||
Splat(goodCells, goodCells, ref random, rockyAmount, Balancing.Instance.RockySpread, c =>
|
||||
{
|
||||
c.Type = ECellType.Rocky;
|
||||
return true;
|
||||
});
|
||||
|
||||
foreach (var cell in cells)
|
||||
{
|
||||
if (cell.Poi is Tower)
|
||||
{
|
||||
if (cell.Modifiers.All(m => m.Id != EModifierId.Unreliable))
|
||||
{
|
||||
freeCells.Remove(cell);
|
||||
cell.AddModifier(new UnreliableCellModifier(EModifierDuration.Permanent), []);
|
||||
}
|
||||
|
||||
var hex = cell.Hex;
|
||||
for (var dir = EDirection.Right; dir <= EDirection.BottomRight; ++dir)
|
||||
{
|
||||
var neighbour = hex.GetNeighbour(dir);
|
||||
var neighbourCell = cells.FirstOrDefault(c => c.Hex == neighbour);
|
||||
if (neighbourCell == null)
|
||||
continue;
|
||||
|
||||
if (neighbourCell.Modifiers.All(m => m.Id != EModifierId.Unreliable))
|
||||
{
|
||||
freeCells.Remove(neighbourCell);
|
||||
poiCells.Remove(neighbourCell);
|
||||
neighbourCell.AddModifier(new UnreliableCellModifier(EModifierDuration.Permanent), []);
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (cell.Poi is Donkey donkey)
|
||||
donkey.Direction = (EDirection)random.Next(6);
|
||||
}
|
||||
|
||||
var corruptAmount = (int)(freeCells.Count * Balancing.Instance.CorruptedAmount * fDifficulty);
|
||||
Splat(freeCells, poiCells, ref random, corruptAmount, Balancing.Instance.CorruptedSpread, c =>
|
||||
{
|
||||
if (!PlacementKeepsConnectivity(cells, c))
|
||||
return false;
|
||||
|
||||
c.AddModifier(new CorruptCellModifier(EModifierDuration.Permanent), []);
|
||||
return true;
|
||||
});
|
||||
|
||||
random.Shuffle(CollectionsMarshal.AsSpan(cells));
|
||||
|
||||
var sheds = cells.Where(c => c.Poi is Shed).Select(c => (Shed)c.Poi!).ToArray();
|
||||
var crates = cells.Where(c => c.Poi is Crate).Select(c => (Crate)c.Poi!).ToArray();
|
||||
var targetDeliveryAmount = sheds.Length * Balancing.Instance.DefaultShedRequest;
|
||||
var totalRequests = 0;
|
||||
for (var i = 0; i < sheds.Length - 1; ++i)
|
||||
{
|
||||
var randomness = (random.NextSingle() - 0.5f) * Balancing.Instance.CrateShedRandomness;
|
||||
var request = (int)Math.Ceiling(Balancing.Instance.DefaultShedRequest * (1 + randomness));
|
||||
sheds[i].Requested = request;
|
||||
totalRequests += request;
|
||||
}
|
||||
|
||||
if (totalRequests >= targetDeliveryAmount)
|
||||
targetDeliveryAmount = totalRequests + 1;
|
||||
|
||||
sheds[^1].Requested = targetDeliveryAmount - totalRequests;
|
||||
var targetOfferAmount = (int)((1 + Balancing.Instance.CrateOfferBonus * (1 - fDifficulty)) * targetDeliveryAmount);
|
||||
var defaultCrateOffer = targetOfferAmount / crates.Length;
|
||||
var totalOffers = 0;
|
||||
for (var i = 0; i < crates.Length - 1; ++i)
|
||||
{
|
||||
var randomness = (random.NextSingle() - 0.5f) * Balancing.Instance.CrateShedRandomness;
|
||||
var offer = (int)Math.Max(1, Math.Floor(defaultCrateOffer * (1 + randomness)));
|
||||
crates[i].Amount = offer;
|
||||
totalOffers += offer;
|
||||
}
|
||||
|
||||
crates[^1].Amount = targetOfferAmount - totalOffers;
|
||||
|
||||
if (!AllPoisReachable(cells))
|
||||
throw new InvalidOperationException();
|
||||
|
||||
return new([.. cells], targetDeliveryAmount);
|
||||
}
|
||||
|
||||
private static void Splat(List<Cell> cells, List<Cell> startCells, ref SRandom random, int needed, float spread, Func<Cell, bool> callback)
|
||||
{
|
||||
var converted = 0;
|
||||
while (converted < needed)
|
||||
{
|
||||
if (startCells.Count == 0)
|
||||
break;
|
||||
|
||||
var open = new Queue<Cell>();
|
||||
open.Enqueue(startCells[random.Next(startCells.Count)]);
|
||||
var closed = new HashSet<Cell>();
|
||||
while (open.TryDequeue(out var cell))
|
||||
{
|
||||
if (!closed.Add(cell))
|
||||
continue;
|
||||
|
||||
if (!callback(cell))
|
||||
continue;
|
||||
|
||||
cells.Remove(cell);
|
||||
startCells.Remove(cell);
|
||||
converted += 1;
|
||||
if (converted >= needed)
|
||||
break;
|
||||
|
||||
var hex = cell.Hex;
|
||||
for (var dir = EDirection.Right; dir <= EDirection.BottomRight; ++dir)
|
||||
{
|
||||
if (random.NextSingle() < spread)
|
||||
continue;
|
||||
|
||||
var neighbour = hex.GetNeighbour(dir);
|
||||
var next = cells.FindIndex(c => c.Hex == neighbour);
|
||||
if (next >= 0)
|
||||
open.Enqueue(cells[next]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void Sprinkle(List<Cell> cells, ref SRandom random, float intensity, Predicate<ECellType> filter, Action<Cell> callback)
|
||||
{
|
||||
var needed = (int)(cells.Count * intensity);
|
||||
var candidates = cells.Select((c, i) => (Cell: c, OriginalIndex: i)).Where(c => c.Cell.Poi == null && filter(c.Cell.Type)).ToArray();
|
||||
random.Shuffle(candidates.AsSpan());
|
||||
|
||||
var placed = 0;
|
||||
for (var i = 0; i < candidates.Length && placed < needed; ++i)
|
||||
{
|
||||
var (_, originalIndex) = candidates[i];
|
||||
var cell = cells[originalIndex];
|
||||
|
||||
if (cell.Poi != null || !filter(cell.Type))
|
||||
continue;
|
||||
|
||||
if (!PlacementKeepsConnectivity(cells, cell))
|
||||
continue;
|
||||
|
||||
callback(cell);
|
||||
placed++;
|
||||
}
|
||||
}
|
||||
|
||||
private static HashSet<Cell> GetReachableFromAvatar(List<Cell> cells)
|
||||
{
|
||||
var avatarCell = cells.Single(c => c.Poi is Avatar);
|
||||
|
||||
var visited = new HashSet<Cell>();
|
||||
var queue = new Queue<Cell>();
|
||||
|
||||
visited.Add(avatarCell);
|
||||
queue.Enqueue(avatarCell);
|
||||
|
||||
while (queue.TryDequeue(out var current))
|
||||
{
|
||||
var hex = current.Hex;
|
||||
for (var dir = EDirection.Right; dir <= EDirection.BottomRight; ++dir)
|
||||
{
|
||||
var neighbourHex = hex.GetNeighbour(dir);
|
||||
var neighbour = cells.FirstOrDefault(c => c.Hex == neighbourHex);
|
||||
if (neighbour == null)
|
||||
continue;
|
||||
|
||||
if (!IsWalkable(neighbour) || neighbour.Modifiers.Any(c => c.Id == EModifierId.Corrupt))
|
||||
continue;
|
||||
|
||||
if (visited.Add(neighbour))
|
||||
queue.Enqueue(neighbour);
|
||||
}
|
||||
}
|
||||
|
||||
return visited;
|
||||
}
|
||||
|
||||
private static bool AllPoisReachable(List<Cell> cells)
|
||||
{
|
||||
var reachable = GetReachableFromAvatar(cells);
|
||||
|
||||
bool IsPoiAccessible(Cell poiCell)
|
||||
{
|
||||
if (reachable.Contains(poiCell))
|
||||
return true;
|
||||
|
||||
var hex = poiCell.Hex;
|
||||
for (var dir = EDirection.Right; dir <= EDirection.BottomRight; ++dir)
|
||||
{
|
||||
var neighbourHex = hex.GetNeighbour(dir);
|
||||
var neighbour = cells.FirstOrDefault(c => c.Hex == neighbourHex);
|
||||
if (neighbour != null && reachable.Contains(neighbour))
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
foreach (var poiCell in cells.Where(c => c.Poi is Donkey or Shed or Crate or Tower))
|
||||
{
|
||||
if (!IsPoiAccessible(poiCell))
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private static bool IsWalkable(Cell cell)
|
||||
{
|
||||
return cell.Type != ECellType.Blocked && cell.Poi is Avatar or null;
|
||||
}
|
||||
|
||||
private static int CountComponentSize(List<Cell> cells, Hex start, Cell? blocked = null)
|
||||
{
|
||||
var visited = new HashSet<Hex>();
|
||||
var queue = new Queue<Hex>();
|
||||
|
||||
visited.Add(start);
|
||||
queue.Enqueue(start);
|
||||
|
||||
while (queue.TryDequeue(out var current))
|
||||
{
|
||||
var hex = current;
|
||||
for (var dir = EDirection.Right; dir <= EDirection.BottomRight; ++dir)
|
||||
{
|
||||
var neighbourHex = hex.GetNeighbour(dir);
|
||||
var neighbour = cells.FirstOrDefault(c => c.Hex == neighbourHex);
|
||||
if (neighbour == null)
|
||||
continue;
|
||||
|
||||
if (neighbour == blocked)
|
||||
continue;
|
||||
|
||||
if (neighbour.Type == ECellType.Blocked || neighbour.Modifiers.Any(m => m.Id == EModifierId.Corrupt))
|
||||
continue;
|
||||
|
||||
if (visited.Add(neighbourHex))
|
||||
{
|
||||
if (neighbour.Poi is Avatar or null)
|
||||
queue.Enqueue(neighbourHex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return visited.Count;
|
||||
}
|
||||
|
||||
private static bool PlacementKeepsConnectivity(List<Cell> cells, Cell candidate)
|
||||
{
|
||||
if (!IsWalkable(candidate))
|
||||
return true;
|
||||
|
||||
var sizeWithCandidate = CountComponentSize(cells, new());
|
||||
var sizeWithoutCandidate = CountComponentSize(cells, new(), candidate);
|
||||
|
||||
return sizeWithoutCandidate == sizeWithCandidate - 1;
|
||||
}
|
||||
}
|
||||
25
RobotAndDonkey.Game/Board/Cell.cs
Normal file
25
RobotAndDonkey.Game/Board/Cell.cs
Normal file
@@ -0,0 +1,25 @@
|
||||
using RobotAndDonkey.Game.Modifiers;
|
||||
using RobotAndDonkey.Game.Pois;
|
||||
using RobotAndDonkey.Game.Utils;
|
||||
|
||||
namespace RobotAndDonkey.Game.Board;
|
||||
|
||||
public record Cell(Hex Hex) : Entity
|
||||
{
|
||||
public Cell(Cell clone)
|
||||
: base(clone)
|
||||
{
|
||||
Hex = clone.Hex;
|
||||
Type = clone.Type;
|
||||
Poi = clone.Poi?.DeepClone();
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return $"Hex={Hex}, {Type}, Poi={Poi}" + base.ToString();
|
||||
}
|
||||
|
||||
public Poi? Poi { get; set; }
|
||||
|
||||
public ECellType Type { get; set; }
|
||||
}
|
||||
11
RobotAndDonkey.Game/Board/ECellType.cs
Normal file
11
RobotAndDonkey.Game/Board/ECellType.cs
Normal file
@@ -0,0 +1,11 @@
|
||||
namespace RobotAndDonkey.Game.Board;
|
||||
|
||||
public enum ECellType
|
||||
{
|
||||
Grass,
|
||||
Dry,
|
||||
Fertile,
|
||||
Mud,
|
||||
Blocked,
|
||||
Rocky
|
||||
}
|
||||
28
RobotAndDonkey.Game/Cards/AvailableCards.cs
Normal file
28
RobotAndDonkey.Game/Cards/AvailableCards.cs
Normal file
@@ -0,0 +1,28 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace RobotAndDonkey.Game.Cards;
|
||||
|
||||
public class AvailableCards
|
||||
{
|
||||
static AvailableCards()
|
||||
{
|
||||
Instance = new();
|
||||
var cardTypes = Instance.GetType().Assembly.GetTypes().Where(t => !t.IsAbstract && t.BaseType?.BaseType == typeof(Card) || t.BaseType?.BaseType?.BaseType == typeof(Card));
|
||||
foreach (var type in cardTypes)
|
||||
{
|
||||
var card = (Card)Activator.CreateInstance(type)!;
|
||||
Instance.Cards.Add(card.Id, card);
|
||||
}
|
||||
}
|
||||
|
||||
public static AvailableCards Instance { get; }
|
||||
|
||||
public Dictionary<ECard, Card> Cards { get; } = [];
|
||||
|
||||
public Card Create(ECard card)
|
||||
{
|
||||
return Cards[card].DeepClone();
|
||||
}
|
||||
}
|
||||
43
RobotAndDonkey.Game/Cards/Card.cs
Normal file
43
RobotAndDonkey.Game/Cards/Card.cs
Normal file
@@ -0,0 +1,43 @@
|
||||
using RobotAndDonkey.Game.Board;
|
||||
using RobotAndDonkey.Game.Execution.Results;
|
||||
using RobotAndDonkey.Game.GameState;
|
||||
using RobotAndDonkey.Game.Intents;
|
||||
using RobotAndDonkey.Game.Modifiers;
|
||||
|
||||
namespace RobotAndDonkey.Game.Cards;
|
||||
|
||||
public record Card(ECard Id, ECardType CardType, ERarity Rarity, int ShopCost, int BasePlayCost) : Entity
|
||||
{
|
||||
public Card DeepClone()
|
||||
{
|
||||
var result = (Card)Activator.CreateInstance(GetType())!;
|
||||
result.PlayCost = PlayCost;
|
||||
result.CardId = CardId;
|
||||
foreach (var modifier in Modifiers)
|
||||
result.AddModifier(modifier.DeepClone(), []);
|
||||
return result;
|
||||
}
|
||||
|
||||
public virtual void CreateIntents(Cell? avatarCell, CoreLoop coreLoop, Guid requestId, List<Intent> intents, List<Result> results)
|
||||
{
|
||||
if (PlayCost > 0)
|
||||
intents.Add(new CardCostIntent(this, PlayCost));
|
||||
}
|
||||
|
||||
public int PlayCost { get; set; } = BasePlayCost;
|
||||
|
||||
public virtual string Name => Id.ToString();
|
||||
|
||||
public virtual string ToolTip => Name;
|
||||
|
||||
public sealed override string ToString()
|
||||
{
|
||||
return $"{Id}" + base.ToString();
|
||||
}
|
||||
|
||||
public int OccupiedSpace => Modifiers.Any(m => m.Id == EModifierId.Efficient) ? 0 : 1;
|
||||
|
||||
public Guid CardId { get; private set; } = Guid.NewGuid();
|
||||
|
||||
public virtual Modifier[] TooltipModifiers => [];
|
||||
}
|
||||
42
RobotAndDonkey.Game/Cards/ECard.cs
Normal file
42
RobotAndDonkey.Game/Cards/ECard.cs
Normal file
@@ -0,0 +1,42 @@
|
||||
namespace RobotAndDonkey.Game.Cards;
|
||||
|
||||
public enum ECard
|
||||
{
|
||||
// Glitches
|
||||
Bitflip, // corrupt one instruction, permanent
|
||||
ShortCircuit, // unreliable one instruction, permanent
|
||||
Slipstream, // race condition one instruction, permanent
|
||||
Latency, // throttle one instruction, permanent
|
||||
Rain, // 1 in X grass cells become mud, dry grass becomes grass, permanent
|
||||
Drought, // fertile becomes grass, 1 in X grass cells become dry, #Temporary
|
||||
Pest, // delivery malus, temporary
|
||||
Gravity, // carry malus, temporary
|
||||
HeatWave, // energy malus, temporary
|
||||
//SandStorm, // JAM, preview malus, temporary
|
||||
SolarFlare, // corrupt everything, temporary
|
||||
LightningStorm, // unreliable everything, temporary
|
||||
MeteorStorm, // race condition everything, temporary
|
||||
WindStorm, // throttle everything, temporary
|
||||
|
||||
// Patches
|
||||
Move = 0x1000,
|
||||
TurnLeft,
|
||||
TurnRight,
|
||||
Interact,
|
||||
NoOp,
|
||||
Potentiate, // adds effective
|
||||
Optimize, // adds optimized
|
||||
Streamline, // adds efficient
|
||||
Persist, // adds persistent
|
||||
Remember, // adds memory
|
||||
Reason, // adds hand size
|
||||
DetoxiumPrime, // Converts #Forward mud into grass or grass into fertile
|
||||
FlyingDisk, // Move #Forward into 1 blocked cell
|
||||
AluminumHat, // instructions are #Immune to the effects of unreliable, #ShortTerm
|
||||
EMField, // instructions are #Immune to the effects of corrupt #ShortTerm
|
||||
AtomicClock, // instructions are #Immune to the effects of race condition #ShortTerm
|
||||
Jump, // move #Forward 2 cells, ignoring blocking
|
||||
Repeat, // copy #Next
|
||||
Rest, // restores energy
|
||||
Stabilize // nullifies the effect of any modifiers of #Next
|
||||
}
|
||||
7
RobotAndDonkey.Game/Cards/ECardType.cs
Normal file
7
RobotAndDonkey.Game/Cards/ECardType.cs
Normal file
@@ -0,0 +1,7 @@
|
||||
namespace RobotAndDonkey.Game.Cards;
|
||||
|
||||
public enum ECardType
|
||||
{
|
||||
Glitch,
|
||||
Patch,
|
||||
}
|
||||
10
RobotAndDonkey.Game/Cards/ERarity.cs
Normal file
10
RobotAndDonkey.Game/Cards/ERarity.cs
Normal file
@@ -0,0 +1,10 @@
|
||||
namespace RobotAndDonkey.Game.Cards;
|
||||
|
||||
public enum ERarity
|
||||
{
|
||||
Common,
|
||||
Magic,
|
||||
Uncommon,
|
||||
Rare,
|
||||
Legendary
|
||||
}
|
||||
3
RobotAndDonkey.Game/Cards/Glitches/Bitflip.cs
Normal file
3
RobotAndDonkey.Game/Cards/Glitches/Bitflip.cs
Normal file
@@ -0,0 +1,3 @@
|
||||
namespace RobotAndDonkey.Game.Cards.Glitches;
|
||||
|
||||
public record Bitflip() : ModifyInstructionPermanently(EModifierId.Corrupt, ECard.Bitflip);
|
||||
34
RobotAndDonkey.Game/Cards/Glitches/Drought.cs
Normal file
34
RobotAndDonkey.Game/Cards/Glitches/Drought.cs
Normal file
@@ -0,0 +1,34 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using RobotAndDonkey.Game.Board;
|
||||
using RobotAndDonkey.Game.Data;
|
||||
using RobotAndDonkey.Game.Execution.Results;
|
||||
using RobotAndDonkey.Game.GameState;
|
||||
using RobotAndDonkey.Game.Intents;
|
||||
using RobotAndDonkey.Game.Modifiers;
|
||||
|
||||
namespace RobotAndDonkey.Game.Cards.Glitches;
|
||||
|
||||
public record Drought() : GlitchCard(ECard.Drought)
|
||||
{
|
||||
public override void CreateIntents(Cell? avatarCell, CoreLoop coreLoop, Guid requestId, List<Intent> intents, List<Result> results)
|
||||
{
|
||||
var cells = coreLoop.Board.Cells;
|
||||
foreach (var cell in cells)
|
||||
{
|
||||
if (cell.Type == ECellType.Fertile)
|
||||
{
|
||||
intents.Add(new ModifyCell(cell, EModifierId.Drought, EModifierDuration.Temporary));
|
||||
}
|
||||
else if (cell.Type == ECellType.Grass)
|
||||
{
|
||||
if (coreLoop.Random.NextSingle() < Balancing.Instance.DroughtTransformProbability)
|
||||
intents.Add(new ModifyCell(cell, EModifierId.Drought, EModifierDuration.Temporary));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override string ToolTip => "Fertility in the land is drastically reduced.";
|
||||
|
||||
public override Modifier[] TooltipModifiers => [new DroughtModifier(EModifierDuration.Temporary)];
|
||||
}
|
||||
16
RobotAndDonkey.Game/Cards/Glitches/GlitchCard.cs
Normal file
16
RobotAndDonkey.Game/Cards/Glitches/GlitchCard.cs
Normal file
@@ -0,0 +1,16 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using RobotAndDonkey.Game.Board;
|
||||
using RobotAndDonkey.Game.Execution.Results;
|
||||
using RobotAndDonkey.Game.GameState;
|
||||
using RobotAndDonkey.Game.Intents;
|
||||
|
||||
namespace RobotAndDonkey.Game.Cards.Glitches;
|
||||
|
||||
public record GlitchCard(ECard Id) : Card(Id, ECardType.Glitch, ERarity.Common, 0, 0)
|
||||
{
|
||||
public override void CreateIntents(Cell? avatarCell, CoreLoop coreLoop, Guid requestId, List<Intent> intents, List<Result> results)
|
||||
{
|
||||
base.CreateIntents(avatarCell, coreLoop, requestId, intents, results);
|
||||
}
|
||||
}
|
||||
21
RobotAndDonkey.Game/Cards/Glitches/Gravity.cs
Normal file
21
RobotAndDonkey.Game/Cards/Glitches/Gravity.cs
Normal file
@@ -0,0 +1,21 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using RobotAndDonkey.Game.Board;
|
||||
using RobotAndDonkey.Game.Execution.Results;
|
||||
using RobotAndDonkey.Game.GameState;
|
||||
using RobotAndDonkey.Game.Intents;
|
||||
using RobotAndDonkey.Game.Modifiers;
|
||||
|
||||
namespace RobotAndDonkey.Game.Cards.Glitches;
|
||||
|
||||
public record Gravity() : GlitchCard(ECard.Gravity)
|
||||
{
|
||||
public override void CreateIntents(Cell? avatarCell, CoreLoop coreLoop, Guid requestId, List<Intent> intents, List<Result> results)
|
||||
{
|
||||
intents.Add(new ModifyRobot(EModifierId.Gravity, EModifierDuration.Temporary));
|
||||
}
|
||||
|
||||
public override string ToolTip => "Carry capacity temporarily reduced.";
|
||||
|
||||
public override Modifier[] TooltipModifiers => [new GravityModifier(EModifierDuration.Temporary)];
|
||||
}
|
||||
21
RobotAndDonkey.Game/Cards/Glitches/HeatWave.cs
Normal file
21
RobotAndDonkey.Game/Cards/Glitches/HeatWave.cs
Normal file
@@ -0,0 +1,21 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using RobotAndDonkey.Game.Board;
|
||||
using RobotAndDonkey.Game.Execution.Results;
|
||||
using RobotAndDonkey.Game.GameState;
|
||||
using RobotAndDonkey.Game.Intents;
|
||||
using RobotAndDonkey.Game.Modifiers;
|
||||
|
||||
namespace RobotAndDonkey.Game.Cards.Glitches;
|
||||
|
||||
public record HeatWave() : GlitchCard(ECard.HeatWave)
|
||||
{
|
||||
public override void CreateIntents(Cell? avatarCell, CoreLoop coreLoop, Guid requestId, List<Intent> intents, List<Result> results)
|
||||
{
|
||||
intents.Add(new ModifyRobot(EModifierId.HeatWave, EModifierDuration.Temporary));
|
||||
}
|
||||
|
||||
public override string ToolTip => "Energy consumption temporarily increased.";
|
||||
|
||||
public override Modifier[] TooltipModifiers => [new HeatWaveModifier(EModifierDuration.Temporary)];
|
||||
}
|
||||
7
RobotAndDonkey.Game/Cards/Glitches/Latency.cs
Normal file
7
RobotAndDonkey.Game/Cards/Glitches/Latency.cs
Normal file
@@ -0,0 +1,7 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using RobotAndDonkey.Game.Board;
|
||||
using RobotAndDonkey.Game.Execution.Results;
|
||||
namespace RobotAndDonkey.Game.Cards.Glitches;
|
||||
|
||||
public record Latency() : ModifyInstructionPermanently(EModifierId.Throttled, ECard.Latency);
|
||||
3
RobotAndDonkey.Game/Cards/Glitches/LightningStorm.cs
Normal file
3
RobotAndDonkey.Game/Cards/Glitches/LightningStorm.cs
Normal file
@@ -0,0 +1,3 @@
|
||||
namespace RobotAndDonkey.Game.Cards.Glitches;
|
||||
|
||||
public record LightningStorm() : ModifyInstructionsTemporarily(EModifierId.Unreliable, ECard.LightningStorm);
|
||||
3
RobotAndDonkey.Game/Cards/Glitches/MeteorStorm.cs
Normal file
3
RobotAndDonkey.Game/Cards/Glitches/MeteorStorm.cs
Normal file
@@ -0,0 +1,3 @@
|
||||
namespace RobotAndDonkey.Game.Cards.Glitches;
|
||||
|
||||
public record MeteorStorm() : ModifyInstructionsTemporarily(EModifierId.RaceCondition, ECard.MeteorStorm);
|
||||
@@ -0,0 +1,21 @@
|
||||
using RobotAndDonkey.Game;
|
||||
using RobotAndDonkey.Game.Board;
|
||||
using RobotAndDonkey.Game.Cards;
|
||||
using RobotAndDonkey.Game.Cards.Glitches;
|
||||
using RobotAndDonkey.Game.Execution.Results;
|
||||
using RobotAndDonkey.Game.GameState;
|
||||
using RobotAndDonkey.Game.Intents;
|
||||
using RobotAndDonkey.Game.Modifiers;
|
||||
|
||||
public abstract record ModifyInstructionPermanently(EModifierId Modifier, ECard Id) : GlitchCard(Id)
|
||||
{
|
||||
public override void CreateIntents(Cell? avatarCell, CoreLoop coreLoop, Guid requestId, List<Intent> intents, List<Result> results)
|
||||
{
|
||||
var victim = coreLoop.PatchDeck[coreLoop.Random.Next(coreLoop.PatchDeck.Count)];
|
||||
intents.Add(new ModifyCard(victim, Modifier, EModifierDuration.Permanent, ECardLocation.Deck));
|
||||
}
|
||||
|
||||
public override string ToolTip => $"Adds {Modifier} to one instruction, permanently.";
|
||||
|
||||
public override Modifier[] TooltipModifiers => [ModifyCard.Create(Modifier, EModifierDuration.Permanent)];
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
using RobotAndDonkey.Game;
|
||||
using RobotAndDonkey.Game.Board;
|
||||
using RobotAndDonkey.Game.Cards;
|
||||
using RobotAndDonkey.Game.Cards.Glitches;
|
||||
using RobotAndDonkey.Game.Execution.Results;
|
||||
using RobotAndDonkey.Game.GameState;
|
||||
using RobotAndDonkey.Game.Intents;
|
||||
using RobotAndDonkey.Game.Modifiers;
|
||||
|
||||
public abstract record ModifyInstructionsTemporarily(EModifierId Modifier, ECard Id) : GlitchCard(Id)
|
||||
{
|
||||
public override void CreateIntents(Cell? avatarCell, CoreLoop coreLoop, Guid requestId, List<Intent> intents, List<Result> results)
|
||||
{
|
||||
intents.AddRange(coreLoop.PatchDeck.Select(victim => new ModifyCard(victim, Modifier, EModifierDuration.Temporary, ECardLocation.Deck)));
|
||||
}
|
||||
|
||||
public override string ToolTip => $"Adds {Modifier} to all instructions, temporarily.";
|
||||
|
||||
public override Modifier[] TooltipModifiers => [ModifyCard.Create(Modifier, EModifierDuration.Temporary)];
|
||||
}
|
||||
22
RobotAndDonkey.Game/Cards/Glitches/Pest.cs
Normal file
22
RobotAndDonkey.Game/Cards/Glitches/Pest.cs
Normal file
@@ -0,0 +1,22 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using RobotAndDonkey.Game.Board;
|
||||
using RobotAndDonkey.Game.Data;
|
||||
using RobotAndDonkey.Game.Execution.Results;
|
||||
using RobotAndDonkey.Game.GameState;
|
||||
using RobotAndDonkey.Game.Intents;
|
||||
using RobotAndDonkey.Game.Modifiers;
|
||||
|
||||
namespace RobotAndDonkey.Game.Cards.Glitches;
|
||||
|
||||
public record Pest() : GlitchCard(ECard.Pest)
|
||||
{
|
||||
public override void CreateIntents(Cell? avatarCell, CoreLoop coreLoop, Guid requestId, List<Intent> intents, List<Result> results)
|
||||
{
|
||||
intents.Add(new ModifyRobot(EModifierId.Pest, EModifierDuration.Temporary));
|
||||
}
|
||||
|
||||
public override string ToolTip => $"Deliveries temporarily reduced by {Balancing.Instance.PestDeliveryMultiplier * 100:N0}%.";
|
||||
|
||||
public override Modifier[] TooltipModifiers => [new PestModifier(EModifierDuration.Temporary)];
|
||||
}
|
||||
34
RobotAndDonkey.Game/Cards/Glitches/Rain.cs
Normal file
34
RobotAndDonkey.Game/Cards/Glitches/Rain.cs
Normal file
@@ -0,0 +1,34 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using RobotAndDonkey.Game.Board;
|
||||
using RobotAndDonkey.Game.Data;
|
||||
using RobotAndDonkey.Game.Execution.Results;
|
||||
using RobotAndDonkey.Game.GameState;
|
||||
using RobotAndDonkey.Game.Intents;
|
||||
using RobotAndDonkey.Game.Modifiers;
|
||||
|
||||
namespace RobotAndDonkey.Game.Cards.Glitches;
|
||||
|
||||
public record Rain() : GlitchCard(ECard.Rain)
|
||||
{
|
||||
public override void CreateIntents(Cell? avatarCell, CoreLoop coreLoop, Guid requestId, List<Intent> intents, List<Result> results)
|
||||
{
|
||||
var cells = coreLoop.Board.Cells;
|
||||
foreach (var cell in cells)
|
||||
{
|
||||
if (cell.Type == ECellType.Rocky)
|
||||
{
|
||||
intents.Add(new ModifyCell(cell, EModifierId.Rain, EModifierDuration.Temporary));
|
||||
}
|
||||
else if (cell.Type == ECellType.Grass)
|
||||
{
|
||||
if (coreLoop.Random.NextSingle() < Balancing.Instance.RainTransformProbability)
|
||||
intents.Add(new ModifyCell(cell, EModifierId.Rain, EModifierDuration.Temporary));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override string ToolTip => "Increases fertility throughout the map.";
|
||||
|
||||
public override Modifier[] TooltipModifiers => [new RainModifier(EModifierDuration.Temporary)];
|
||||
}
|
||||
3
RobotAndDonkey.Game/Cards/Glitches/ShortCircuit.cs
Normal file
3
RobotAndDonkey.Game/Cards/Glitches/ShortCircuit.cs
Normal file
@@ -0,0 +1,3 @@
|
||||
namespace RobotAndDonkey.Game.Cards.Glitches;
|
||||
|
||||
public record ShortCircuit() : ModifyInstructionPermanently(EModifierId.Unreliable, ECard.ShortCircuit);
|
||||
3
RobotAndDonkey.Game/Cards/Glitches/Slipstream.cs
Normal file
3
RobotAndDonkey.Game/Cards/Glitches/Slipstream.cs
Normal file
@@ -0,0 +1,3 @@
|
||||
namespace RobotAndDonkey.Game.Cards.Glitches;
|
||||
|
||||
public record Slipstream() : ModifyInstructionPermanently(EModifierId.RaceCondition, ECard.Slipstream);
|
||||
3
RobotAndDonkey.Game/Cards/Glitches/SolarFlare.cs
Normal file
3
RobotAndDonkey.Game/Cards/Glitches/SolarFlare.cs
Normal file
@@ -0,0 +1,3 @@
|
||||
namespace RobotAndDonkey.Game.Cards.Glitches;
|
||||
|
||||
public record SolarFlare() : ModifyInstructionsTemporarily(EModifierId.Corrupt, ECard.SolarFlare);
|
||||
3
RobotAndDonkey.Game/Cards/Glitches/WindStorm.cs
Normal file
3
RobotAndDonkey.Game/Cards/Glitches/WindStorm.cs
Normal file
@@ -0,0 +1,3 @@
|
||||
namespace RobotAndDonkey.Game.Cards.Glitches;
|
||||
|
||||
public record WindStorm() : ModifyInstructionsTemporarily(EModifierId.Throttled, ECard.WindStorm);
|
||||
34
RobotAndDonkey.Game/Cards/Patches/AluminumHat.cs
Normal file
34
RobotAndDonkey.Game/Cards/Patches/AluminumHat.cs
Normal file
@@ -0,0 +1,34 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using RobotAndDonkey.Game.Board;
|
||||
using RobotAndDonkey.Game.Data;
|
||||
using RobotAndDonkey.Game.Execution.Results;
|
||||
using RobotAndDonkey.Game.GameState;
|
||||
using RobotAndDonkey.Game.Intents;
|
||||
using RobotAndDonkey.Game.Modifiers;
|
||||
using RobotAndDonkey.Game.Pois;
|
||||
|
||||
namespace RobotAndDonkey.Game.Cards.Patches;
|
||||
|
||||
public record AluminumHat() : PatchCard(ECard.AluminumHat, Balancing.Instance.AluminumHatCost, Balancing.Instance.CardPlayEnergyCost, ERarity.Uncommon)
|
||||
{
|
||||
protected override void CreateIntents(Cell avatarCell, Avatar avatar, CoreLoop coreLoop, Guid requestId, List<Intent> intents, List<Result> results)
|
||||
{
|
||||
var tape = coreLoop.GetTapeCards();
|
||||
var index = tape.IndexOf(this);
|
||||
if (index < 0)
|
||||
{
|
||||
results.Add(new InvalidInstructionResult(requestId, EInvalidReason.NotFound, this, avatarCell));
|
||||
return;
|
||||
}
|
||||
|
||||
for (int i = index + 1; i < tape.Count; ++i)
|
||||
{
|
||||
intents.Add(new ImmunizeCard(this, tape[i], EModifierId.Unreliable, EModifierDuration.ShortTerm));
|
||||
}
|
||||
}
|
||||
|
||||
public override string ToolTip => "Instructions on tape become #Immune to the effects of unreliable";
|
||||
|
||||
public override Modifier[] TooltipModifiers => [new UnreliableCardModifier(EModifierDuration.Temporary)];
|
||||
}
|
||||
34
RobotAndDonkey.Game/Cards/Patches/AtomicClock.cs
Normal file
34
RobotAndDonkey.Game/Cards/Patches/AtomicClock.cs
Normal file
@@ -0,0 +1,34 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using RobotAndDonkey.Game.Board;
|
||||
using RobotAndDonkey.Game.Data;
|
||||
using RobotAndDonkey.Game.Execution.Results;
|
||||
using RobotAndDonkey.Game.GameState;
|
||||
using RobotAndDonkey.Game.Intents;
|
||||
using RobotAndDonkey.Game.Modifiers;
|
||||
using RobotAndDonkey.Game.Pois;
|
||||
|
||||
namespace RobotAndDonkey.Game.Cards.Patches;
|
||||
|
||||
public record AtomicClock() : PatchCard(ECard.AtomicClock, Balancing.Instance.AtomicClockCost, Balancing.Instance.CardPlayEnergyCost, ERarity.Uncommon)
|
||||
{
|
||||
protected override void CreateIntents(Cell avatarCell, Avatar avatar, CoreLoop coreLoop, Guid requestId, List<Intent> intents, List<Result> results)
|
||||
{
|
||||
var tape = coreLoop.GetTapeCards();
|
||||
var index = tape.IndexOf(this);
|
||||
if (index < 0)
|
||||
{
|
||||
results.Add(new InvalidInstructionResult(requestId, EInvalidReason.NotFound, this, avatarCell));
|
||||
return;
|
||||
}
|
||||
|
||||
for (int i = index + 1; i < tape.Count; ++i)
|
||||
{
|
||||
intents.Add(new ImmunizeCard(this, tape[i], EModifierId.RaceCondition, EModifierDuration.ShortTerm));
|
||||
}
|
||||
}
|
||||
|
||||
public override string ToolTip => "Instructions on tape become #Immune to the effects of race condition";
|
||||
|
||||
public override Modifier[] TooltipModifiers => [new RaceConditionModifier(EModifierDuration.Temporary)];
|
||||
}
|
||||
23
RobotAndDonkey.Game/Cards/Patches/DetoxiumPrime.cs
Normal file
23
RobotAndDonkey.Game/Cards/Patches/DetoxiumPrime.cs
Normal file
@@ -0,0 +1,23 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using RobotAndDonkey.Game.Board;
|
||||
using RobotAndDonkey.Game.Data;
|
||||
using RobotAndDonkey.Game.Execution.Results;
|
||||
using RobotAndDonkey.Game.GameState;
|
||||
using RobotAndDonkey.Game.Intents;
|
||||
using RobotAndDonkey.Game.Modifiers;
|
||||
using RobotAndDonkey.Game.Pois;
|
||||
|
||||
namespace RobotAndDonkey.Game.Cards.Patches;
|
||||
|
||||
public record DetoxiumPrime() : PatchCard(ECard.DetoxiumPrime, Balancing.Instance.DetoxiumPrimeCost, Balancing.Instance.CardPlayEnergyCost, ERarity.Uncommon)
|
||||
{
|
||||
protected override void CreateIntents(Cell avatarCell, Avatar avatar, CoreLoop coreLoop, Guid requestId, List<Intent> intents, List<Result> results)
|
||||
{
|
||||
intents.Add(new Intents.DetoxiumPrime(avatarCell, this, avatar.Direction));
|
||||
}
|
||||
|
||||
public override string Name => "Detoxium Prime";
|
||||
|
||||
public override string ToolTip => "Heals the cell in front";
|
||||
}
|
||||
34
RobotAndDonkey.Game/Cards/Patches/EMField.cs
Normal file
34
RobotAndDonkey.Game/Cards/Patches/EMField.cs
Normal file
@@ -0,0 +1,34 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using RobotAndDonkey.Game.Board;
|
||||
using RobotAndDonkey.Game.Data;
|
||||
using RobotAndDonkey.Game.Execution.Results;
|
||||
using RobotAndDonkey.Game.GameState;
|
||||
using RobotAndDonkey.Game.Intents;
|
||||
using RobotAndDonkey.Game.Modifiers;
|
||||
using RobotAndDonkey.Game.Pois;
|
||||
|
||||
namespace RobotAndDonkey.Game.Cards.Patches;
|
||||
|
||||
public record EMField() : PatchCard(ECard.EMField, Balancing.Instance.EMFieldCost, Balancing.Instance.CardPlayEnergyCost, ERarity.Uncommon)
|
||||
{
|
||||
protected override void CreateIntents(Cell avatarCell, Avatar avatar, CoreLoop coreLoop, Guid requestId, List<Intent> intents, List<Result> results)
|
||||
{
|
||||
var tape = coreLoop.GetTapeCards();
|
||||
var index = tape.IndexOf(this);
|
||||
if (index < 0)
|
||||
{
|
||||
results.Add(new InvalidInstructionResult(requestId, EInvalidReason.NotFound, this, avatarCell));
|
||||
return;
|
||||
}
|
||||
|
||||
for (int i = index + 1; i < tape.Count; ++i)
|
||||
{
|
||||
intents.Add(new ImmunizeCard(this, tape[i], EModifierId.Corrupt, EModifierDuration.Temporary));
|
||||
}
|
||||
}
|
||||
|
||||
public override string ToolTip => "Instructions on tape become #Immune to the effects of corrupt";
|
||||
|
||||
public override Modifier[] TooltipModifiers => [new CorruptCardModifier(EModifierDuration.Temporary)];
|
||||
}
|
||||
20
RobotAndDonkey.Game/Cards/Patches/FlyingDisk.cs
Normal file
20
RobotAndDonkey.Game/Cards/Patches/FlyingDisk.cs
Normal file
@@ -0,0 +1,20 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using RobotAndDonkey.Game.Board;
|
||||
using RobotAndDonkey.Game.Data;
|
||||
using RobotAndDonkey.Game.Execution.Results;
|
||||
using RobotAndDonkey.Game.GameState;
|
||||
using RobotAndDonkey.Game.Intents;
|
||||
using RobotAndDonkey.Game.Pois;
|
||||
|
||||
namespace RobotAndDonkey.Game.Cards.Patches;
|
||||
|
||||
public record FlyingDisk() : PatchCard(ECard.FlyingDisk, Balancing.Instance.FlyingDiskCost, Balancing.Instance.CardPlayEnergyCost, ERarity.Uncommon)
|
||||
{
|
||||
protected override void CreateIntents(Cell avatarCell, Avatar avatar, CoreLoop coreLoop, Guid requestId, List<Intent> intents, List<Result> results)
|
||||
{
|
||||
intents.Add(new Intents.Move(avatarCell, this, avatar.Direction, true, 1));
|
||||
}
|
||||
|
||||
public override string ToolTip => "Fly forward 1 cell, ignoring blocked areas";
|
||||
}
|
||||
20
RobotAndDonkey.Game/Cards/Patches/Interact.cs
Normal file
20
RobotAndDonkey.Game/Cards/Patches/Interact.cs
Normal file
@@ -0,0 +1,20 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using RobotAndDonkey.Game.Board;
|
||||
using RobotAndDonkey.Game.Data;
|
||||
using RobotAndDonkey.Game.Execution.Results;
|
||||
using RobotAndDonkey.Game.GameState;
|
||||
using RobotAndDonkey.Game.Intents;
|
||||
using RobotAndDonkey.Game.Pois;
|
||||
|
||||
namespace RobotAndDonkey.Game.Cards.Patches;
|
||||
|
||||
public record Interact() : PatchCard(ECard.Interact, Balancing.Instance.InteractCost, Balancing.Instance.CardPlayEnergyCost, ERarity.Common)
|
||||
{
|
||||
protected override void CreateIntents(Cell avatarCell, Avatar avatar, CoreLoop coreLoop, Guid requestId, List<Intent> intents, List<Result> results)
|
||||
{
|
||||
intents.Add(new Intents.Interact(avatarCell, this, avatar.Direction));
|
||||
}
|
||||
|
||||
public override string ToolTip => "Interact with the forward cell";
|
||||
}
|
||||
20
RobotAndDonkey.Game/Cards/Patches/Jump.cs
Normal file
20
RobotAndDonkey.Game/Cards/Patches/Jump.cs
Normal file
@@ -0,0 +1,20 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using RobotAndDonkey.Game.Board;
|
||||
using RobotAndDonkey.Game.Data;
|
||||
using RobotAndDonkey.Game.Execution.Results;
|
||||
using RobotAndDonkey.Game.GameState;
|
||||
using RobotAndDonkey.Game.Intents;
|
||||
using RobotAndDonkey.Game.Pois;
|
||||
|
||||
namespace RobotAndDonkey.Game.Cards.Patches;
|
||||
|
||||
public record Jump() : PatchCard(ECard.Jump, Balancing.Instance.JumpCost, Balancing.Instance.CardPlayEnergyCost, ERarity.Legendary)
|
||||
{
|
||||
protected override void CreateIntents(Cell avatarCell, Avatar avatar, CoreLoop coreLoop, Guid requestId, List<Intent> intents, List<Result> results)
|
||||
{
|
||||
intents.Add(new Intents.Move(avatarCell, this, avatar.Direction, true, 2));
|
||||
}
|
||||
|
||||
public override string ToolTip => "Move forward 2 cells, ignoring obstacles";
|
||||
}
|
||||
35
RobotAndDonkey.Game/Cards/Patches/ModifyCardBase.cs
Normal file
35
RobotAndDonkey.Game/Cards/Patches/ModifyCardBase.cs
Normal file
@@ -0,0 +1,35 @@
|
||||
using RobotAndDonkey.Game.Board;
|
||||
using RobotAndDonkey.Game.Execution.Results;
|
||||
using RobotAndDonkey.Game.GameState;
|
||||
using RobotAndDonkey.Game.Intents;
|
||||
using RobotAndDonkey.Game.Modifiers;
|
||||
using RobotAndDonkey.Game.Pois;
|
||||
|
||||
namespace RobotAndDonkey.Game.Cards.Patches;
|
||||
|
||||
public abstract record ModifyCardBase(ECard Id, int ShopCost, int PlayCost, ERarity Rarity, EModifierId Modifier) : PatchCard(Id, ShopCost, PlayCost, Rarity)
|
||||
{
|
||||
protected override void CreateIntents(Cell avatarCell, Avatar avatar, CoreLoop coreLoop, Guid requestId, List<Intent> intents, List<Result> results)
|
||||
{
|
||||
var tape = coreLoop.GetTapeCards();
|
||||
var tapeIndex = tape.IndexOf(this);
|
||||
if (tapeIndex < 0)
|
||||
results.Add(new InvalidInstructionResult(requestId, EInvalidReason.NotFound, this, null));
|
||||
else
|
||||
{
|
||||
var corrupt = Modifiers.Any(m => m is CorruptModifierBase { DebuffSources.Count: 0 });
|
||||
for (var i = tapeIndex + 1; i < tape.Count; ++i)
|
||||
{
|
||||
var victim = tape[i];
|
||||
if (corrupt)
|
||||
intents.Add(new ImmunizeCard(this, victim, Modifier, EModifierDuration.Temporary, false));
|
||||
else
|
||||
intents.Add(new ModifyCard(victim, Modifier, EModifierDuration.Temporary, ECardLocation.Tape));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override string ToolTip => $"All following instructions in tape become {Modifier} temporarily";
|
||||
|
||||
public override Modifier[] TooltipModifiers => [ModifyCard.Create(Modifier, EModifierDuration.Temporary)];
|
||||
}
|
||||
20
RobotAndDonkey.Game/Cards/Patches/Move.cs
Normal file
20
RobotAndDonkey.Game/Cards/Patches/Move.cs
Normal file
@@ -0,0 +1,20 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using RobotAndDonkey.Game.Board;
|
||||
using RobotAndDonkey.Game.Data;
|
||||
using RobotAndDonkey.Game.Execution.Results;
|
||||
using RobotAndDonkey.Game.GameState;
|
||||
using RobotAndDonkey.Game.Intents;
|
||||
using RobotAndDonkey.Game.Pois;
|
||||
|
||||
namespace RobotAndDonkey.Game.Cards.Patches;
|
||||
|
||||
public record Move() : PatchCard(ECard.Move, Balancing.Instance.MoveCost, Balancing.Instance.CardPlayEnergyCost, ERarity.Common)
|
||||
{
|
||||
protected override void CreateIntents(Cell avatarCell, Avatar avatar, CoreLoop coreLoop, Guid requestId, List<Intent> intents, List<Result> results)
|
||||
{
|
||||
intents.Add(new Intents.Move(avatarCell, this, avatar.Direction, false, 1));
|
||||
}
|
||||
|
||||
public override string ToolTip => "Move one cell forward if it's free";
|
||||
}
|
||||
20
RobotAndDonkey.Game/Cards/Patches/NoOp.cs
Normal file
20
RobotAndDonkey.Game/Cards/Patches/NoOp.cs
Normal file
@@ -0,0 +1,20 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using RobotAndDonkey.Game.Board;
|
||||
using RobotAndDonkey.Game.Data;
|
||||
using RobotAndDonkey.Game.Execution.Results;
|
||||
using RobotAndDonkey.Game.GameState;
|
||||
using RobotAndDonkey.Game.Intents;
|
||||
using RobotAndDonkey.Game.Pois;
|
||||
|
||||
namespace RobotAndDonkey.Game.Cards.Patches;
|
||||
|
||||
public record NoOp() : PatchCard(ECard.NoOp, Balancing.Instance.InteractCost, 0, ERarity.Common)
|
||||
{
|
||||
protected override void CreateIntents(Cell avatarCell, Avatar avatar, CoreLoop coreLoop, Guid requestId, List<Intent> intents, List<Result> results)
|
||||
{
|
||||
intents.Add(new Intents.Rest(avatarCell, Balancing.Instance.CardNoOpEnergyReplenish));
|
||||
}
|
||||
|
||||
public override string ToolTip => $"Take a break, do nothing. Restore {Balancing.Instance.CardNoOpEnergyReplenish} energy.";
|
||||
}
|
||||
5
RobotAndDonkey.Game/Cards/Patches/Optimize.cs
Normal file
5
RobotAndDonkey.Game/Cards/Patches/Optimize.cs
Normal file
@@ -0,0 +1,5 @@
|
||||
using RobotAndDonkey.Game.Data;
|
||||
|
||||
namespace RobotAndDonkey.Game.Cards.Patches;
|
||||
|
||||
public record Optimize() : ModifyCardBase(ECard.Optimize, Balancing.Instance.OptimizeCost, Balancing.Instance.CardPlayEnergyCost, ERarity.Magic, EModifierId.Optimized);
|
||||
25
RobotAndDonkey.Game/Cards/Patches/PatchCard.cs
Normal file
25
RobotAndDonkey.Game/Cards/Patches/PatchCard.cs
Normal file
@@ -0,0 +1,25 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using RobotAndDonkey.Game.Board;
|
||||
using RobotAndDonkey.Game.Execution.Results;
|
||||
using RobotAndDonkey.Game.GameState;
|
||||
using RobotAndDonkey.Game.Intents;
|
||||
using RobotAndDonkey.Game.Pois;
|
||||
|
||||
namespace RobotAndDonkey.Game.Cards.Patches;
|
||||
|
||||
public record PatchCard(ECard Id, int ShopCost, int PlayCost, ERarity Rarity) : Card(Id, ECardType.Patch, Rarity, ShopCost, PlayCost)
|
||||
{
|
||||
public override void CreateIntents(Cell? avatarCell, CoreLoop coreLoop, Guid requestId, List<Intent> intents, List<Result> results)
|
||||
{
|
||||
if (avatarCell?.Poi is not Avatar avatar)
|
||||
return;
|
||||
|
||||
base.CreateIntents(avatarCell, coreLoop, requestId, intents, results);
|
||||
CreateIntents(avatarCell, avatar, coreLoop, requestId, intents, results);
|
||||
}
|
||||
|
||||
protected virtual void CreateIntents(Cell avatarCell, Avatar avatar, CoreLoop coreLoop, Guid requestId, List<Intent> intents, List<Result> results)
|
||||
{
|
||||
}
|
||||
}
|
||||
5
RobotAndDonkey.Game/Cards/Patches/Persist.cs
Normal file
5
RobotAndDonkey.Game/Cards/Patches/Persist.cs
Normal file
@@ -0,0 +1,5 @@
|
||||
using RobotAndDonkey.Game.Data;
|
||||
|
||||
namespace RobotAndDonkey.Game.Cards.Patches;
|
||||
|
||||
public record Persist() : ModifyCardBase(ECard.Persist, Balancing.Instance.PersistCost, Balancing.Instance.CardPlayEnergyCost, ERarity.Magic, EModifierId.Persistent);
|
||||
5
RobotAndDonkey.Game/Cards/Patches/Potentiate.cs
Normal file
5
RobotAndDonkey.Game/Cards/Patches/Potentiate.cs
Normal file
@@ -0,0 +1,5 @@
|
||||
using RobotAndDonkey.Game.Data;
|
||||
|
||||
namespace RobotAndDonkey.Game.Cards.Patches;
|
||||
|
||||
public record Potentiate() : ModifyCardBase(ECard.Potentiate, Balancing.Instance.PotentiateCost, Balancing.Instance.CardPlayEnergyCost, ERarity.Magic, EModifierId.Effective);
|
||||
18
RobotAndDonkey.Game/Cards/Patches/Reason.cs
Normal file
18
RobotAndDonkey.Game/Cards/Patches/Reason.cs
Normal file
@@ -0,0 +1,18 @@
|
||||
using RobotAndDonkey.Game.Board;
|
||||
using RobotAndDonkey.Game.Data;
|
||||
using RobotAndDonkey.Game.Execution.Results;
|
||||
using RobotAndDonkey.Game.GameState;
|
||||
using RobotAndDonkey.Game.Intents;
|
||||
using RobotAndDonkey.Game.Pois;
|
||||
|
||||
namespace RobotAndDonkey.Game.Cards.Patches;
|
||||
|
||||
public record Reason() : PatchCard(ECard.Reason, Balancing.Instance.ReasonCost, Balancing.Instance.CardPlayEnergyCost, ERarity.Rare)
|
||||
{
|
||||
protected override void CreateIntents(Cell avatarCell, Avatar avatar, CoreLoop coreLoop, Guid requestId, List<Intent> intents, List<Result> results)
|
||||
{
|
||||
intents.Add(new ModifyCurrency(new(0, 0, 0, 0, 0, HandSize: 1), true));
|
||||
}
|
||||
|
||||
public override string ToolTip => "Adds hand size, permanently.";
|
||||
}
|
||||
20
RobotAndDonkey.Game/Cards/Patches/Remember.cs
Normal file
20
RobotAndDonkey.Game/Cards/Patches/Remember.cs
Normal file
@@ -0,0 +1,20 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using RobotAndDonkey.Game.Board;
|
||||
using RobotAndDonkey.Game.Data;
|
||||
using RobotAndDonkey.Game.Execution.Results;
|
||||
using RobotAndDonkey.Game.GameState;
|
||||
using RobotAndDonkey.Game.Intents;
|
||||
using RobotAndDonkey.Game.Pois;
|
||||
|
||||
namespace RobotAndDonkey.Game.Cards.Patches;
|
||||
|
||||
public record Remember() : PatchCard(ECard.Remember, Balancing.Instance.RememberCost, Balancing.Instance.CardPlayEnergyCost, ERarity.Rare)
|
||||
{
|
||||
protected override void CreateIntents(Cell avatarCell, Avatar avatar, CoreLoop coreLoop, Guid requestId, List<Intent> intents, List<Result> results)
|
||||
{
|
||||
intents.Add(new ModifyCurrency(new(0, 0, 0, 0, TapeLength: 1, 0), true));
|
||||
}
|
||||
|
||||
public override string ToolTip => "Increases tape length, permanently.";
|
||||
}
|
||||
47
RobotAndDonkey.Game/Cards/Patches/Repeat.cs
Normal file
47
RobotAndDonkey.Game/Cards/Patches/Repeat.cs
Normal file
@@ -0,0 +1,47 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using RobotAndDonkey.Game.Board;
|
||||
using RobotAndDonkey.Game.Data;
|
||||
using RobotAndDonkey.Game.Execution.Results;
|
||||
using RobotAndDonkey.Game.GameState;
|
||||
using RobotAndDonkey.Game.Intents;
|
||||
using RobotAndDonkey.Game.Modifiers;
|
||||
using RobotAndDonkey.Game.Pois;
|
||||
using RobotAndDonkey.Game.Utils;
|
||||
|
||||
namespace RobotAndDonkey.Game.Cards.Patches;
|
||||
|
||||
public record Repeat() : PatchCard(ECard.Repeat, Balancing.Instance.RepeatCost, 0, ERarity.Legendary)
|
||||
{
|
||||
protected override void CreateIntents(Cell avatarCell, Avatar avatar, CoreLoop coreLoop, Guid requestId, List<Intent> intents, List<Result> results)
|
||||
{
|
||||
var tape = coreLoop.GetTapeCards();
|
||||
var index = tape.IndexOf(this);
|
||||
if (index < 0)
|
||||
{
|
||||
results.Add(new InvalidInstructionResult(requestId, EInvalidReason.NotFound, this, avatarCell));
|
||||
return;
|
||||
}
|
||||
|
||||
var targetIndex = CardExtensions.NextIndexConsideringCorruption(index, coreLoop);
|
||||
if (targetIndex < 0 || targetIndex >= tape.Count)
|
||||
{
|
||||
results.Add(new InvalidInstructionResult(requestId, EInvalidReason.OutOfBounds, this, avatarCell));
|
||||
return;
|
||||
}
|
||||
|
||||
var targetCard = tape[targetIndex];
|
||||
if (!m_RepeatedCards.Add(targetCard))
|
||||
{
|
||||
results.Add(new InvalidInstructionResult(requestId, EInvalidReason.AlreadyExecuted, this, avatarCell));
|
||||
return;
|
||||
}
|
||||
|
||||
targetCard.CreateIntents(avatarCell, coreLoop, requestId, intents, results);
|
||||
m_RepeatedCards.Remove(targetCard);
|
||||
}
|
||||
|
||||
public override string ToolTip => "Behaves exactly like the next instruction.";
|
||||
|
||||
private readonly HashSet<Card> m_RepeatedCards = [];
|
||||
}
|
||||
18
RobotAndDonkey.Game/Cards/Patches/Rest.cs
Normal file
18
RobotAndDonkey.Game/Cards/Patches/Rest.cs
Normal file
@@ -0,0 +1,18 @@
|
||||
using RobotAndDonkey.Game.Board;
|
||||
using RobotAndDonkey.Game.Data;
|
||||
using RobotAndDonkey.Game.Execution.Results;
|
||||
using RobotAndDonkey.Game.GameState;
|
||||
using RobotAndDonkey.Game.Intents;
|
||||
using RobotAndDonkey.Game.Pois;
|
||||
|
||||
namespace RobotAndDonkey.Game.Cards.Patches;
|
||||
|
||||
public record Rest() : PatchCard(ECard.Rest, Balancing.Instance.RestCost, 0, ERarity.Legendary)
|
||||
{
|
||||
protected override void CreateIntents(Cell avatarCell, Avatar avatar, CoreLoop coreLoop, Guid requestId, List<Intent> intents, List<Result> results)
|
||||
{
|
||||
intents.Add(new Intents.Rest(avatarCell, Balancing.Instance.CardRestEnergyReplenish));
|
||||
}
|
||||
|
||||
public override string ToolTip => $"Reboot the system and restore {Balancing.Instance.CardRestEnergyReplenish} energy.";
|
||||
}
|
||||
36
RobotAndDonkey.Game/Cards/Patches/Stabilize.cs
Normal file
36
RobotAndDonkey.Game/Cards/Patches/Stabilize.cs
Normal file
@@ -0,0 +1,36 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using RobotAndDonkey.Game.Board;
|
||||
using RobotAndDonkey.Game.Data;
|
||||
using RobotAndDonkey.Game.Execution.Results;
|
||||
using RobotAndDonkey.Game.GameState;
|
||||
using RobotAndDonkey.Game.Intents;
|
||||
using RobotAndDonkey.Game.Modifiers;
|
||||
using RobotAndDonkey.Game.Pois;
|
||||
|
||||
namespace RobotAndDonkey.Game.Cards.Patches;
|
||||
|
||||
public record Stabilize() : PatchCard(ECard.Stabilize, Balancing.Instance.StabilizeCost, Balancing.Instance.CardPlayEnergyCost, ERarity.Legendary)
|
||||
{
|
||||
protected override void CreateIntents(Cell avatarCell, Avatar avatar, CoreLoop coreLoop, Guid requestId, List<Intent> intents, List<Result> results)
|
||||
{
|
||||
var tape = coreLoop.GetTapeCards();
|
||||
var index = tape.IndexOf(this);
|
||||
if (index < 0)
|
||||
{
|
||||
results.Add(new InvalidInstructionResult(requestId, EInvalidReason.NotFound, this, avatarCell));
|
||||
return;
|
||||
}
|
||||
|
||||
var targetIndex = index + 1;
|
||||
if (targetIndex >= tape.Count)
|
||||
{
|
||||
results.Add(new InvalidInstructionResult(requestId, EInvalidReason.OutOfBounds, this, avatarCell));
|
||||
return;
|
||||
}
|
||||
|
||||
intents.Add(new ImmunizeCard(this, tape[targetIndex], EModifierId._Invalid, EModifierDuration.ShortTerm));
|
||||
}
|
||||
|
||||
public override string ToolTip => "Temporarily nullifies all modifiers of the next card.";
|
||||
}
|
||||
5
RobotAndDonkey.Game/Cards/Patches/Streamline.cs
Normal file
5
RobotAndDonkey.Game/Cards/Patches/Streamline.cs
Normal file
@@ -0,0 +1,5 @@
|
||||
using RobotAndDonkey.Game.Data;
|
||||
|
||||
namespace RobotAndDonkey.Game.Cards.Patches;
|
||||
|
||||
public record Streamline() : ModifyCardBase(ECard.Streamline, Balancing.Instance.StreamlineCost, Balancing.Instance.CardPlayEnergyCost, ERarity.Magic, EModifierId.Efficient);
|
||||
20
RobotAndDonkey.Game/Cards/Patches/TurnLeft.cs
Normal file
20
RobotAndDonkey.Game/Cards/Patches/TurnLeft.cs
Normal file
@@ -0,0 +1,20 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using RobotAndDonkey.Game.Board;
|
||||
using RobotAndDonkey.Game.Data;
|
||||
using RobotAndDonkey.Game.Execution.Results;
|
||||
using RobotAndDonkey.Game.GameState;
|
||||
using RobotAndDonkey.Game.Intents;
|
||||
using RobotAndDonkey.Game.Pois;
|
||||
|
||||
namespace RobotAndDonkey.Game.Cards.Patches;
|
||||
|
||||
public record TurnLeft() : PatchCard(ECard.TurnLeft, Balancing.Instance.TurnLeftCost, Balancing.Instance.CardPlayEnergyCost, ERarity.Common)
|
||||
{
|
||||
protected override void CreateIntents(Cell avatarCell, Avatar avatar, CoreLoop coreLoop, Guid requestId, List<Intent> intents, List<Result> results)
|
||||
{
|
||||
intents.Add(new Turn(avatarCell, 1));
|
||||
}
|
||||
|
||||
public override string ToolTip => "Change the orientation by 60 degrees counter-clockwise";
|
||||
}
|
||||
20
RobotAndDonkey.Game/Cards/Patches/TurnRight.cs
Normal file
20
RobotAndDonkey.Game/Cards/Patches/TurnRight.cs
Normal file
@@ -0,0 +1,20 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using RobotAndDonkey.Game.Board;
|
||||
using RobotAndDonkey.Game.Data;
|
||||
using RobotAndDonkey.Game.Execution.Results;
|
||||
using RobotAndDonkey.Game.GameState;
|
||||
using RobotAndDonkey.Game.Intents;
|
||||
using RobotAndDonkey.Game.Pois;
|
||||
|
||||
namespace RobotAndDonkey.Game.Cards.Patches;
|
||||
|
||||
public record TurnRight() : PatchCard(ECard.TurnRight, Balancing.Instance.TurnRightCost, Balancing.Instance.CardPlayEnergyCost, ERarity.Common)
|
||||
{
|
||||
protected override void CreateIntents(Cell avatarCell, Avatar avatar, CoreLoop coreLoop, Guid requestId, List<Intent> intents, List<Result> results)
|
||||
{
|
||||
intents.Add(new Turn(avatarCell, -1));
|
||||
}
|
||||
|
||||
public override string ToolTip => "Change the orientation by 60 degrees clockwise";
|
||||
}
|
||||
122
RobotAndDonkey.Game/Data/Balancing.cs
Normal file
122
RobotAndDonkey.Game/Data/Balancing.cs
Normal file
@@ -0,0 +1,122 @@
|
||||
namespace RobotAndDonkey.Game.Data;
|
||||
|
||||
public class Balancing
|
||||
{
|
||||
public static Balancing Instance { get; } = new();
|
||||
|
||||
// Robots
|
||||
|
||||
public int RobotEasyEnergy => 50;
|
||||
public int RobotMediumEnergy => 40;
|
||||
public int RobotHardEnergy => 30;
|
||||
public int EasyMaxCarry => 10;
|
||||
public int MediumMaxCarry => 8;
|
||||
public int HardMaxCarry => 6;
|
||||
public int EasyHandSize => 9;
|
||||
public int MediumHandSize => 8;
|
||||
public int HardHandSize => 7;
|
||||
public int EasyTapeLength => 6;
|
||||
public int MediumTapeLength => 5;
|
||||
public int HardTapeLength => 4;
|
||||
|
||||
public int CourierEnergyReplenishOnDelivery => 5;
|
||||
public int RangerFertileRestEnergyDelta => 1;
|
||||
|
||||
public int AnalysisHandSizeDelta => 1;
|
||||
public int AnalysisEnergyDelta => -1;
|
||||
|
||||
// Gameplay
|
||||
|
||||
public int DiscardEnergyCost => 1;
|
||||
public int LegendaryWeight => 1;
|
||||
public int RareWeight => 3;
|
||||
public int UncommonWeight => 12;
|
||||
public int MagicWeight => 36;
|
||||
public int CommonWeight => 108;
|
||||
|
||||
public int GetDeferGlitchEnergyCost(int deferCount)
|
||||
{
|
||||
deferCount += 1;
|
||||
return deferCount * (deferCount + 1) / 2;
|
||||
}
|
||||
|
||||
// Shop
|
||||
public float ShopBuffChance => 0.25f;
|
||||
public float ShopDebuffChance => 0.25f;
|
||||
public int GambleBoosterSize => 6;
|
||||
public int GambleEnergyCost => 5;
|
||||
public int BufferOverflowEnergyCost => 10;
|
||||
public int ShopSize => 5;
|
||||
|
||||
public int GetRerollEnergyCost(int rerollCount)
|
||||
{
|
||||
return rerollCount + 1;
|
||||
}
|
||||
|
||||
// Modifiers
|
||||
public float EffectiveChance => 0.25f;
|
||||
public float UnreliableChance => 0.25f;
|
||||
public float RaceConditionChance => 0.25f;
|
||||
public int GravityMaxCarryPenalty => 2;
|
||||
public int GravityExtraMoveCost => 1;
|
||||
|
||||
// Board
|
||||
|
||||
public int DefaultShedRequest => 10;
|
||||
public float CrateShedRandomness => 0.5f;
|
||||
public float CrateOfferBonus => 1.0f;
|
||||
public int CrateAmount => 10;
|
||||
public float DryAmount => 0.25f;
|
||||
public float DrySpread => 0.75f;
|
||||
public float FertileAmount => 0.5f;
|
||||
public float FertileSpread => 0.75f;
|
||||
public float MudAmount => 0.375f;
|
||||
public float MudSpread => 0.5f;
|
||||
public float BlockedAmount => 0.25f;
|
||||
public float BlockedSpread => 0.5f;
|
||||
public float CorruptedAmount => 0.375f;
|
||||
public float CorruptedSpread => 0.5f;
|
||||
public float RockyAmount => 0.2f;
|
||||
public float RockySpread => 0.9f;
|
||||
public float DonkeySprinkle => 0.05f;
|
||||
public float ShedSprinkle => 0.1f;
|
||||
public float CrateSprinkle => 0.2f;
|
||||
public float TowerSprinkle => 0.05f;
|
||||
|
||||
public float RainTransformProbability => 0.5f;
|
||||
public float DroughtTransformProbability => 0.5f;
|
||||
|
||||
// Cards
|
||||
|
||||
public int CardPlayEnergyCost => 1;
|
||||
public int CardNoOpEnergyReplenish => 5;
|
||||
public int CardRestEnergyReplenish => 20;
|
||||
public int DonkeyMaxCarryBonus => 10;
|
||||
public int HeatWaveEnergyPenalty => 10;
|
||||
public float PestDeliveryMultiplier => 0.5f;
|
||||
|
||||
public int MoveCost => 1;
|
||||
public int TurnLeftCost => 1;
|
||||
public int TurnRightCost => 1;
|
||||
public int InteractCost => 1;
|
||||
public int PotentiateCost => 3;
|
||||
public int OptimizeCost => 3;
|
||||
public int StreamlineCost => 3;
|
||||
public int PersistCost => 3;
|
||||
public int RememberCost => 3;
|
||||
public int ReasonCost => 3;
|
||||
public int DetoxiumPrimeCost => 5;
|
||||
public int FlyingDiskCost => 5;
|
||||
public int AluminumHatCost => 5;
|
||||
public int EMFieldCost => 5;
|
||||
public int AtomicClockCost => 5;
|
||||
public int JumpCost => 8;
|
||||
public int RepeatCost => 8;
|
||||
public int RestCost => 8;
|
||||
public int StabilizeCost => 8;
|
||||
public int MudMoveEnergyCost => 1;
|
||||
public int FertileRestEnergyReplenish => 1;
|
||||
public int DryRestEnergyMalus => 1;
|
||||
public int DryInteractEnergyMalus => 1;
|
||||
public int EndOfProgramEnergyReplenish => 10;
|
||||
}
|
||||
11
RobotAndDonkey.Game/EDirection.cs
Normal file
11
RobotAndDonkey.Game/EDirection.cs
Normal file
@@ -0,0 +1,11 @@
|
||||
namespace RobotAndDonkey.Game;
|
||||
|
||||
public enum EDirection
|
||||
{
|
||||
Right = 0,
|
||||
TopRight = 1,
|
||||
TopLeft = 2,
|
||||
Left = 3,
|
||||
BottomLeft = 4,
|
||||
BottomRight = 5
|
||||
}
|
||||
8
RobotAndDonkey.Game/EModifierDuration.cs
Normal file
8
RobotAndDonkey.Game/EModifierDuration.cs
Normal file
@@ -0,0 +1,8 @@
|
||||
namespace RobotAndDonkey.Game;
|
||||
|
||||
public enum EModifierDuration
|
||||
{
|
||||
ShortTerm,
|
||||
Temporary,
|
||||
Permanent
|
||||
}
|
||||
24
RobotAndDonkey.Game/EModifierId.cs
Normal file
24
RobotAndDonkey.Game/EModifierId.cs
Normal file
@@ -0,0 +1,24 @@
|
||||
namespace RobotAndDonkey.Game;
|
||||
|
||||
public enum EModifierId
|
||||
{
|
||||
_Invalid = -1,
|
||||
Corrupt,
|
||||
Unreliable,
|
||||
RaceCondition,
|
||||
Throttled,
|
||||
Effective,
|
||||
Optimized,
|
||||
Efficient,
|
||||
Persistent,
|
||||
Analytic,
|
||||
Rain,
|
||||
Drought,
|
||||
Pest,
|
||||
Gravity,
|
||||
HeatWave,
|
||||
//SandStorm, // JAM
|
||||
CourierOverspill,
|
||||
RangerFertileRest,
|
||||
GlobalImmunity,
|
||||
}
|
||||
8
RobotAndDonkey.Game/EModifierKind.cs
Normal file
8
RobotAndDonkey.Game/EModifierKind.cs
Normal file
@@ -0,0 +1,8 @@
|
||||
namespace RobotAndDonkey.Game;
|
||||
|
||||
public enum EModifierKind
|
||||
{
|
||||
Card,
|
||||
Robot,
|
||||
Cell
|
||||
}
|
||||
16
RobotAndDonkey.Game/ERobotType.cs
Normal file
16
RobotAndDonkey.Game/ERobotType.cs
Normal file
@@ -0,0 +1,16 @@
|
||||
namespace RobotAndDonkey.Game;
|
||||
|
||||
public enum EDifficulty
|
||||
{
|
||||
Easy,
|
||||
Medium,
|
||||
Hard
|
||||
}
|
||||
|
||||
public enum ERobotType
|
||||
{
|
||||
Vintage = 0,
|
||||
Courier,
|
||||
Analyst,
|
||||
Ranger,
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
using RobotAndDonkey.Game.Execution.Requests;
|
||||
using RobotAndDonkey.Game.GameState;
|
||||
using RobotAndDonkey.Game.Intents;
|
||||
|
||||
namespace RobotAndDonkey.Game.Execution.Commands;
|
||||
|
||||
public sealed record AcceptCardCommand(Guid RequestId) : Command(RequestId, typeof(DrawGlitchRequest))
|
||||
{
|
||||
protected override void CreateIntents(CoreLoop coreLoop, List<Intent> intents)
|
||||
{
|
||||
intents.Add(new AcceptGlitch(coreLoop.GlitchDeck[coreLoop.NextGlitch]));
|
||||
intents.Add(new NextGlitch());
|
||||
intents.Add(new NextPhase(ERunPhase.Improve));
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return "Accept card command";
|
||||
}
|
||||
}
|
||||
31
RobotAndDonkey.Game/Execution/Commands/BuyCardsCommand.cs
Normal file
31
RobotAndDonkey.Game/Execution/Commands/BuyCardsCommand.cs
Normal file
@@ -0,0 +1,31 @@
|
||||
using RobotAndDonkey.Game.Execution.Requests;
|
||||
using RobotAndDonkey.Game.GameState;
|
||||
using RobotAndDonkey.Game.Intents;
|
||||
|
||||
namespace RobotAndDonkey.Game.Execution.Commands;
|
||||
|
||||
public sealed record BuyCardsCommand(Guid RequestId, int[] Cards) : Command(RequestId, typeof(ImproveRequest), typeof(GambleRequest))
|
||||
{
|
||||
protected override void CreateIntents(CoreLoop coreLoop, List<Intent> intents)
|
||||
{
|
||||
var gamble = coreLoop.RunPhase == ERunPhase.Gamble;
|
||||
foreach (var index in Cards)
|
||||
{
|
||||
if (index < 0)
|
||||
continue;
|
||||
|
||||
if (gamble && index < coreLoop.BoosterPack.Count)
|
||||
intents.Add(new BuyPatch(coreLoop.BoosterPack[index], true));
|
||||
else if (!gamble && index < coreLoop.Shop.Count)
|
||||
intents.Add(new BuyPatch(coreLoop.Shop[index], false));
|
||||
}
|
||||
|
||||
if (coreLoop.RunPhase == ERunPhase.Gamble)
|
||||
intents.Add(new NextPhase(ERunPhase.Improve));
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return $"Buy cards command {string.Join(", ", Cards.Select(c => c.ToString()))}";
|
||||
}
|
||||
}
|
||||
82
RobotAndDonkey.Game/Execution/Commands/Command.cs
Normal file
82
RobotAndDonkey.Game/Execution/Commands/Command.cs
Normal file
@@ -0,0 +1,82 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using RobotAndDonkey.Game.Execution.Results;
|
||||
using RobotAndDonkey.Game.GameState;
|
||||
using RobotAndDonkey.Game.Intents;
|
||||
using RobotAndDonkey.Game.Modifiers;
|
||||
using RobotAndDonkey.Game.Pois;
|
||||
|
||||
namespace RobotAndDonkey.Game.Execution.Commands;
|
||||
|
||||
public abstract record Command(Guid RequestId, params Type[] RequestTypes)
|
||||
{
|
||||
protected abstract void CreateIntents(CoreLoop coreLoop, List<Intent> intents);
|
||||
|
||||
public List<Result> Preview(CoreLoop coreLoop)
|
||||
{
|
||||
var mockCoreLoop = new CoreLoop(coreLoop) { IsPreview = true };
|
||||
return Execute(mockCoreLoop, false, false);
|
||||
}
|
||||
|
||||
public List<Result> Execute(CoreLoop coreLoop, bool force, bool verbose)
|
||||
{
|
||||
var results = new List<Result>();
|
||||
var intents = new List<Intent>();
|
||||
CreateIntents(coreLoop, intents);
|
||||
|
||||
var modifiers = GetModifierStack(coreLoop);
|
||||
modifiers.Execute(RequestId, coreLoop, intents, results, force, verbose);
|
||||
return results;
|
||||
}
|
||||
|
||||
private static ModifierStack GetModifierStack(CoreLoop coreLoop)
|
||||
{
|
||||
var modifiers = new ModifierStack();
|
||||
modifiers.Push(coreLoop.Robot);
|
||||
|
||||
foreach (var cell in coreLoop.Board.Cells)
|
||||
{
|
||||
if (cell.Poi is Avatar)
|
||||
{
|
||||
modifiers.Push(cell);
|
||||
}
|
||||
}
|
||||
|
||||
return modifiers;
|
||||
}
|
||||
|
||||
public bool IsValid(CoreLoop coreLoop, out EInvalidReason reason)
|
||||
{
|
||||
var results = Preview(coreLoop);
|
||||
if (results.Count == 0)
|
||||
{
|
||||
reason = EInvalidReason.Invariant;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (results is [InvalidInstructionResult])
|
||||
{
|
||||
reason = EInvalidReason.Invalid;
|
||||
return false;
|
||||
}
|
||||
|
||||
reason = (EInvalidReason)(-1);
|
||||
return true;
|
||||
}
|
||||
|
||||
public int EstimateEnergyCost(CoreLoop coreLoop)
|
||||
{
|
||||
var results = Preview(coreLoop);
|
||||
if (results.Count == 0)
|
||||
return 0;
|
||||
|
||||
var newEnergy = 0;
|
||||
foreach (var result in results)
|
||||
{
|
||||
if (result is CurrencyResult currencyResult)
|
||||
newEnergy = currencyResult.NewCurrency.Energy;
|
||||
}
|
||||
|
||||
return coreLoop.Currency.Energy - newEnergy;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using RobotAndDonkey.Game.Data;
|
||||
using RobotAndDonkey.Game.Execution.Requests;
|
||||
using RobotAndDonkey.Game.GameState;
|
||||
using RobotAndDonkey.Game.Intents;
|
||||
|
||||
namespace RobotAndDonkey.Game.Execution.Commands;
|
||||
|
||||
public sealed record DeferCardCommand(Guid RequestId) : Command(RequestId, typeof(DrawGlitchRequest))
|
||||
{
|
||||
protected override void CreateIntents(CoreLoop coreLoop, List<Intent> intents)
|
||||
{
|
||||
intents.Add(new DeferGlitch(Balancing.Instance.GetDeferGlitchEnergyCost(coreLoop.DeferGlitchCount)));
|
||||
intents.Add(new NextGlitch());
|
||||
intents.Add(new NextPhase(ERunPhase.Improve));
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return $"Defer card command";
|
||||
}
|
||||
}
|
||||
22
RobotAndDonkey.Game/Execution/Commands/DestroyCardCommand.cs
Normal file
22
RobotAndDonkey.Game/Execution/Commands/DestroyCardCommand.cs
Normal file
@@ -0,0 +1,22 @@
|
||||
using RobotAndDonkey.Game.Execution.Requests;
|
||||
using RobotAndDonkey.Game.GameState;
|
||||
using RobotAndDonkey.Game.Intents;
|
||||
|
||||
namespace RobotAndDonkey.Game.Execution.Commands;
|
||||
|
||||
public sealed record DestroyCardCommand(Guid RequestId, int HandIndex) : Command(RequestId, typeof(BufferOverflowRequest))
|
||||
{
|
||||
protected override void CreateIntents(CoreLoop coreLoop, List<Intent> intents)
|
||||
{
|
||||
var handCards = coreLoop.GetHandCards();
|
||||
if (HandIndex < 0 || HandIndex >= handCards.Count)
|
||||
return;
|
||||
|
||||
intents.Add(new DestroyCard(handCards[HandIndex]));
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return $"Destroy card command {HandIndex}";
|
||||
}
|
||||
}
|
||||
22
RobotAndDonkey.Game/Execution/Commands/DiscardCommand.cs
Normal file
22
RobotAndDonkey.Game/Execution/Commands/DiscardCommand.cs
Normal file
@@ -0,0 +1,22 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using RobotAndDonkey.Game.Data;
|
||||
using RobotAndDonkey.Game.Execution.Requests;
|
||||
using RobotAndDonkey.Game.GameState;
|
||||
using RobotAndDonkey.Game.Intents;
|
||||
|
||||
namespace RobotAndDonkey.Game.Execution.Commands;
|
||||
|
||||
public sealed record DiscardCommand(Guid RequestId, Guid[] CardIds) : Command(RequestId, typeof(ExecuteProgramRequest))
|
||||
{
|
||||
protected override void CreateIntents(CoreLoop coreLoop, List<Intent> intents)
|
||||
{
|
||||
var cost = CardIds.Length * Balancing.Instance.DiscardEnergyCost;
|
||||
intents.Add(new Discard(CardIds, cost));
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return $"Discard cards command {string.Join(", ", CardIds.Select(id => id.ToString()))}";
|
||||
}
|
||||
}
|
||||
20
RobotAndDonkey.Game/Execution/Commands/MoveCardsCommand.cs
Normal file
20
RobotAndDonkey.Game/Execution/Commands/MoveCardsCommand.cs
Normal file
@@ -0,0 +1,20 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using RobotAndDonkey.Game.Execution.Requests;
|
||||
using RobotAndDonkey.Game.GameState;
|
||||
using RobotAndDonkey.Game.Intents;
|
||||
|
||||
namespace RobotAndDonkey.Game.Execution.Commands;
|
||||
|
||||
public sealed record MoveCardsCommand(Guid RequestId, Guid[] OrderedCards, Guid[] TapeCardIds) : Command(RequestId, typeof(ExecuteProgramRequest))
|
||||
{
|
||||
protected override void CreateIntents(CoreLoop coreLoop, List<Intent> intents)
|
||||
{
|
||||
intents.Add(new MoveCards(OrderedCards, TapeCardIds));
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return $"Move cards command: order={string.Join(", ", OrderedCards.Select(c => c.ToString()))}; tape={string.Join(", ", TapeCardIds.Select(c => c.ToString()))}";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
using RobotAndDonkey.Game.Execution.Requests;
|
||||
using RobotAndDonkey.Game.GameState;
|
||||
using RobotAndDonkey.Game.Intents;
|
||||
|
||||
namespace RobotAndDonkey.Game.Execution.Commands;
|
||||
|
||||
public sealed record NextAssignmentCommand(Guid RequestId) : Command(RequestId, typeof(ScoringRequest))
|
||||
{
|
||||
protected override void CreateIntents(CoreLoop coreLoop, List<Intent> intents)
|
||||
{
|
||||
coreLoop.ResetShop();
|
||||
intents.Add(new NextPhase(ERunPhase.DrawGlitch));
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return $"Next assignment command";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
using RobotAndDonkey.Game.Execution.Requests;
|
||||
using RobotAndDonkey.Game.GameState;
|
||||
using RobotAndDonkey.Game.Intents;
|
||||
|
||||
namespace RobotAndDonkey.Game.Execution.Commands;
|
||||
|
||||
public sealed record PreviewProgramCommand(Guid RequestId) : Command(RequestId, typeof(ImproveRequest))
|
||||
{
|
||||
protected override void CreateIntents(CoreLoop coreLoop, List<Intent> intents)
|
||||
{
|
||||
intents.Add(new NextPhase(ERunPhase.ExecuteProgram));
|
||||
intents.Add(new EnterPreview());
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return "Preview program command";
|
||||
}
|
||||
}
|
||||
21
RobotAndDonkey.Game/Execution/Commands/RerollCommand.cs
Normal file
21
RobotAndDonkey.Game/Execution/Commands/RerollCommand.cs
Normal file
@@ -0,0 +1,21 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using RobotAndDonkey.Game.Data;
|
||||
using RobotAndDonkey.Game.Execution.Requests;
|
||||
using RobotAndDonkey.Game.GameState;
|
||||
using RobotAndDonkey.Game.Intents;
|
||||
|
||||
namespace RobotAndDonkey.Game.Execution.Commands;
|
||||
|
||||
public sealed record RerollCommand(Guid RequestId) : Command(RequestId, typeof(ImproveRequest))
|
||||
{
|
||||
protected override void CreateIntents(CoreLoop coreLoop, List<Intent> intents)
|
||||
{
|
||||
intents.Add(new Reroll(Balancing.Instance.GetRerollEnergyCost(coreLoop.RerollCount)));
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return "Reroll command";
|
||||
}
|
||||
}
|
||||
18
RobotAndDonkey.Game/Execution/Commands/RunProgramCommand.cs
Normal file
18
RobotAndDonkey.Game/Execution/Commands/RunProgramCommand.cs
Normal file
@@ -0,0 +1,18 @@
|
||||
using RobotAndDonkey.Game.Execution.Requests;
|
||||
using RobotAndDonkey.Game.GameState;
|
||||
using RobotAndDonkey.Game.Intents;
|
||||
|
||||
namespace RobotAndDonkey.Game.Execution.Commands;
|
||||
|
||||
public sealed record RunProgramCommand(Guid RequestId) : Command(RequestId, typeof(ExecuteProgramRequest))
|
||||
{
|
||||
protected override void CreateIntents(CoreLoop coreLoop, List<Intent> intents)
|
||||
{
|
||||
intents.Add(new RunProgram());
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return "Run program command";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using RobotAndDonkey.Game.Data;
|
||||
using RobotAndDonkey.Game.Execution.Requests;
|
||||
using RobotAndDonkey.Game.GameState;
|
||||
using RobotAndDonkey.Game.Intents;
|
||||
|
||||
namespace RobotAndDonkey.Game.Execution.Commands;
|
||||
|
||||
public sealed record StartBufferOverflowCommand(Guid RequestId) : Command(RequestId, typeof(ImproveRequest))
|
||||
{
|
||||
protected override void CreateIntents(CoreLoop coreLoop, List<Intent> intents)
|
||||
{
|
||||
intents.Add(new EnterBufferOverflow(Balancing.Instance.BufferOverflowEnergyCost));
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return "Start buffer overflow command";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using RobotAndDonkey.Game.Data;
|
||||
using RobotAndDonkey.Game.Execution.Requests;
|
||||
using RobotAndDonkey.Game.GameState;
|
||||
using RobotAndDonkey.Game.Intents;
|
||||
|
||||
namespace RobotAndDonkey.Game.Execution.Commands;
|
||||
|
||||
public sealed record StartGamblingCommand(Guid RequestId) : Command(RequestId, typeof(ImproveRequest))
|
||||
{
|
||||
protected override void CreateIntents(CoreLoop coreLoop, List<Intent> intents)
|
||||
{
|
||||
intents.Add(new EnterGamble(Balancing.Instance.GambleEnergyCost));
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return "Start gambling command";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using RobotAndDonkey.Game.Execution.Requests;
|
||||
using RobotAndDonkey.Game.GameState;
|
||||
using RobotAndDonkey.Game.Intents;
|
||||
|
||||
namespace RobotAndDonkey.Game.Execution.Commands;
|
||||
|
||||
public sealed record StopBufferOverflowCommand(Guid RequestId) : Command(RequestId, typeof(BufferOverflowRequest))
|
||||
{
|
||||
protected override void CreateIntents(CoreLoop coreLoop, List<Intent> intents)
|
||||
{
|
||||
var handCards = coreLoop.GetHandCards();
|
||||
coreLoop.PatchDeck.AddRange(handCards);
|
||||
foreach (var handCard in handCards)
|
||||
{
|
||||
coreLoop.RemoveProgramCard(handCard);
|
||||
}
|
||||
coreLoop.ClearTapeSelection();
|
||||
intents.Add(new NextPhase(ERunPhase.Improve));
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return "Stop buffer overflow command";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using RobotAndDonkey.Game.Execution.Requests;
|
||||
using RobotAndDonkey.Game.GameState;
|
||||
using RobotAndDonkey.Game.Intents;
|
||||
|
||||
namespace RobotAndDonkey.Game.Execution.Commands;
|
||||
|
||||
public sealed record StopGamblingCommand(Guid RequestId) : Command(RequestId, typeof(GambleRequest))
|
||||
{
|
||||
protected override void CreateIntents(CoreLoop coreLoop, List<Intent> intents)
|
||||
{
|
||||
intents.Add(new NextPhase(ERunPhase.Improve));
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return "Stop gambling command";
|
||||
}
|
||||
}
|
||||
13
RobotAndDonkey.Game/Execution/ERunPhase.cs
Normal file
13
RobotAndDonkey.Game/Execution/ERunPhase.cs
Normal file
@@ -0,0 +1,13 @@
|
||||
namespace RobotAndDonkey.Game.Execution;
|
||||
|
||||
public enum ERunPhase
|
||||
{
|
||||
Init,
|
||||
ExecuteProgram,
|
||||
Scoring,
|
||||
DrawGlitch,
|
||||
Improve,
|
||||
Gamble,
|
||||
BufferOverflow,
|
||||
_Count
|
||||
}
|
||||
139
RobotAndDonkey.Game/Execution/GameRuntime.cs
Normal file
139
RobotAndDonkey.Game/Execution/GameRuntime.cs
Normal file
@@ -0,0 +1,139 @@
|
||||
using RobotAndDonkey.Game.Execution.Commands;
|
||||
using RobotAndDonkey.Game.Execution.Requests;
|
||||
using RobotAndDonkey.Game.Execution.Results;
|
||||
using RobotAndDonkey.Game.GameState;
|
||||
using RobotAndDonkey.Game.Intents;
|
||||
using System;
|
||||
using System.Collections.Immutable;
|
||||
using System.Threading;
|
||||
|
||||
namespace RobotAndDonkey.Game.Execution;
|
||||
|
||||
public sealed record GameEvent(long Sequence, DateTimeOffset Time, object Payload);
|
||||
|
||||
public sealed record NextStepIssued(Request Step);
|
||||
|
||||
public sealed record StepApplied(ImmutableArray<Result> Results);
|
||||
|
||||
public sealed record StepRejected(Guid RequestId, string Reason);
|
||||
|
||||
public sealed record StateChanged;
|
||||
|
||||
public sealed record GameOver(string Reason);
|
||||
|
||||
public sealed class GameRuntime
|
||||
{
|
||||
public event EventHandler<GameEvent>? Published;
|
||||
|
||||
public void Start(CoreLoop coreLoop)
|
||||
{
|
||||
lock (m_Gate)
|
||||
{
|
||||
m_CoreLoop = coreLoop;
|
||||
Publish(new StateChanged());
|
||||
IssueNextStepLocked();
|
||||
}
|
||||
}
|
||||
|
||||
public void Submit(Command command, CancellationToken ct = default)
|
||||
{
|
||||
lock (m_Gate)
|
||||
{
|
||||
if (m_CoreLoop == null)
|
||||
{
|
||||
Publish(new StepRejected(command.RequestId, "No core loop started."));
|
||||
return;
|
||||
}
|
||||
|
||||
if (m_ExpectedRequest is null)
|
||||
{
|
||||
Publish(new StepRejected(command.RequestId, "No step expected (game over or not started)."));
|
||||
return;
|
||||
}
|
||||
|
||||
if (m_ExpectedRequest.RequestId != command.RequestId)
|
||||
{
|
||||
Publish(new StepRejected(command.RequestId, "Command does not match the expected step."));
|
||||
return;
|
||||
}
|
||||
|
||||
if (!m_ExpectedRequest.IsCommandCompatible(command))
|
||||
{
|
||||
Publish(new StepRejected(command.RequestId, "Command not compatible with the current step."));
|
||||
return;
|
||||
}
|
||||
|
||||
var results = command.Execute(m_CoreLoop, false, false);
|
||||
|
||||
Publish(new StepApplied([..results]));
|
||||
Publish(new StateChanged());
|
||||
|
||||
IssueNextStepLocked();
|
||||
}
|
||||
}
|
||||
|
||||
private void IssueNextStepLocked()
|
||||
{
|
||||
m_ExpectedRequest = NextRequest();
|
||||
|
||||
if (m_ExpectedRequest is null)
|
||||
Publish(new GameOver("No more steps."));
|
||||
else
|
||||
Publish(new NextStepIssued(m_ExpectedRequest));
|
||||
}
|
||||
|
||||
private Request? NextRequest()
|
||||
{
|
||||
var coreLoop = m_CoreLoop!;
|
||||
switch (coreLoop.RunPhase)
|
||||
{
|
||||
case ERunPhase.Init:
|
||||
{
|
||||
const ERunPhase firstPhase = ERunPhase.ExecuteProgram;
|
||||
coreLoop.ResetShop();
|
||||
new EnterPreview().Run(Guid.Empty, coreLoop, [], []);
|
||||
// DEBUG
|
||||
// const ERunPhase firstPhase = ERunPhase.DrawGlitch;
|
||||
coreLoop.RunPhase = firstPhase;
|
||||
goto case firstPhase;
|
||||
}
|
||||
case ERunPhase.DrawGlitch:
|
||||
{
|
||||
return DrawGlitchRequest.Create(coreLoop);
|
||||
}
|
||||
case ERunPhase.Improve:
|
||||
{
|
||||
return ImproveRequest.Create(coreLoop);
|
||||
}
|
||||
case ERunPhase.Gamble:
|
||||
{
|
||||
return GambleRequest.Create(coreLoop);
|
||||
}
|
||||
case ERunPhase.BufferOverflow:
|
||||
{
|
||||
return BufferOverflowRequest.Create(coreLoop);
|
||||
}
|
||||
case ERunPhase.ExecuteProgram:
|
||||
{
|
||||
return ExecuteProgramRequest.Create(coreLoop);
|
||||
}
|
||||
case ERunPhase.Scoring:
|
||||
{
|
||||
return ScoringRequest.Create(coreLoop);
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private void Publish(object payload)
|
||||
{
|
||||
Published?.Invoke(this, new(Interlocked.Increment(ref m_Sequence), DateTimeOffset.UtcNow, payload));
|
||||
}
|
||||
|
||||
private CoreLoop? m_CoreLoop;
|
||||
|
||||
private readonly Lock m_Gate = new();
|
||||
private Request? m_ExpectedRequest;
|
||||
private long m_Sequence;
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
using RobotAndDonkey.Game.GameState;
|
||||
|
||||
namespace RobotAndDonkey.Game.Execution.Requests;
|
||||
|
||||
public sealed record BufferOverflowRequest(Guid RequestId) : Request(RequestId)
|
||||
{
|
||||
public static BufferOverflowRequest Create(CoreLoop coreLoop)
|
||||
{
|
||||
coreLoop.ShuffleDeck();
|
||||
coreLoop.DrawHand();
|
||||
return new(Guid.NewGuid());
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return "Buffer overflow request";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
using System;
|
||||
using RobotAndDonkey.Game.Cards;
|
||||
using RobotAndDonkey.Game.GameState;
|
||||
|
||||
namespace RobotAndDonkey.Game.Execution.Requests;
|
||||
|
||||
public sealed record DrawGlitchRequest(Guid RequestId, Card Card) : Request(RequestId)
|
||||
{
|
||||
public static DrawGlitchRequest Create(CoreLoop coreLoop)
|
||||
{
|
||||
return new(Guid.NewGuid(), coreLoop.GlitchDeck[coreLoop.NextGlitch]);
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return $"Draw glitch request: {Card}";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
using System;
|
||||
using RobotAndDonkey.Game.Execution.Commands;
|
||||
using RobotAndDonkey.Game.GameState;
|
||||
|
||||
namespace RobotAndDonkey.Game.Execution.Requests;
|
||||
|
||||
public sealed record ExecuteProgramRequest(Guid RequestId) : Request(RequestId)
|
||||
{
|
||||
public static ExecuteProgramRequest Create(CoreLoop coreLoop)
|
||||
{
|
||||
return new(Guid.NewGuid());
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return "Execute program request";
|
||||
}
|
||||
}
|
||||
20
RobotAndDonkey.Game/Execution/Requests/GambleRequest.cs
Normal file
20
RobotAndDonkey.Game/Execution/Requests/GambleRequest.cs
Normal file
@@ -0,0 +1,20 @@
|
||||
using System;
|
||||
using System.Collections.Immutable;
|
||||
using RobotAndDonkey.Game.Cards;
|
||||
using RobotAndDonkey.Game.Data;
|
||||
using RobotAndDonkey.Game.GameState;
|
||||
|
||||
namespace RobotAndDonkey.Game.Execution.Requests;
|
||||
|
||||
public sealed record GambleRequest(Guid RequestId) : Request(RequestId)
|
||||
{
|
||||
public static GambleRequest Create(CoreLoop coreLoop)
|
||||
{
|
||||
return new(Guid.NewGuid());
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return "Gamble request";
|
||||
}
|
||||
}
|
||||
18
RobotAndDonkey.Game/Execution/Requests/ImproveRequest.cs
Normal file
18
RobotAndDonkey.Game/Execution/Requests/ImproveRequest.cs
Normal file
@@ -0,0 +1,18 @@
|
||||
using System;
|
||||
using RobotAndDonkey.Game.Execution.Commands;
|
||||
using RobotAndDonkey.Game.GameState;
|
||||
|
||||
namespace RobotAndDonkey.Game.Execution.Requests;
|
||||
|
||||
public sealed record ImproveRequest(Guid RequestId) : Request(RequestId)
|
||||
{
|
||||
public static ImproveRequest Create(CoreLoop coreLoop)
|
||||
{
|
||||
return new(Guid.NewGuid());
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return "Improve request";
|
||||
}
|
||||
}
|
||||
22
RobotAndDonkey.Game/Execution/Requests/Request.cs
Normal file
22
RobotAndDonkey.Game/Execution/Requests/Request.cs
Normal file
@@ -0,0 +1,22 @@
|
||||
using System;
|
||||
using RobotAndDonkey.Game.Execution.Commands;
|
||||
|
||||
namespace RobotAndDonkey.Game.Execution.Requests;
|
||||
|
||||
public abstract record Request(Guid RequestId)
|
||||
{
|
||||
public bool IsCommandCompatible(Command command)
|
||||
{
|
||||
foreach (var requestType in command.RequestTypes)
|
||||
{
|
||||
if (requestType == GetType())
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public static readonly EmptyRequest s_Empty = new();
|
||||
}
|
||||
|
||||
public sealed record EmptyRequest() : Request(Guid.Empty);
|
||||
18
RobotAndDonkey.Game/Execution/Requests/ScoringRequest.cs
Normal file
18
RobotAndDonkey.Game/Execution/Requests/ScoringRequest.cs
Normal file
@@ -0,0 +1,18 @@
|
||||
using System;
|
||||
using RobotAndDonkey.Game.Execution.Commands;
|
||||
using RobotAndDonkey.Game.GameState;
|
||||
|
||||
namespace RobotAndDonkey.Game.Execution.Requests;
|
||||
|
||||
public sealed record ScoringRequest(Guid RequestId) : Request(RequestId)
|
||||
{
|
||||
public static ScoringRequest Create(CoreLoop coreLoop)
|
||||
{
|
||||
return new(Guid.NewGuid());
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return "Scoring request";
|
||||
}
|
||||
}
|
||||
11
RobotAndDonkey.Game/Execution/Results/CellTypeResult.cs
Normal file
11
RobotAndDonkey.Game/Execution/Results/CellTypeResult.cs
Normal file
@@ -0,0 +1,11 @@
|
||||
using RobotAndDonkey.Game.Utils;
|
||||
|
||||
namespace RobotAndDonkey.Game.Execution.Results;
|
||||
|
||||
public record CellTypeResult(Guid RequestId, Board.Board Board, Hex Hex) : Result(RequestId)
|
||||
{
|
||||
public override string ToString()
|
||||
{
|
||||
return $"Cell type result: {Hex}";
|
||||
}
|
||||
}
|
||||
13
RobotAndDonkey.Game/Execution/Results/CurrencyResult.cs
Normal file
13
RobotAndDonkey.Game/Execution/Results/CurrencyResult.cs
Normal file
@@ -0,0 +1,13 @@
|
||||
using RobotAndDonkey.Game.Board;
|
||||
using RobotAndDonkey.Game.GameState;
|
||||
using System;
|
||||
|
||||
namespace RobotAndDonkey.Game.Execution.Results;
|
||||
|
||||
public record CurrencyResult(Guid RequestId, Currency NewCurrency) : Result(RequestId)
|
||||
{
|
||||
public override string ToString()
|
||||
{
|
||||
return $"Currency result: {NewCurrency}";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
using System;
|
||||
|
||||
namespace RobotAndDonkey.Game.Execution.Results;
|
||||
|
||||
public record DeferGlitchCountResult(Guid RequestId, int NewDeferGlitchCount) : Result(RequestId)
|
||||
{
|
||||
public override string ToString()
|
||||
{
|
||||
return $"Defer glitch count result: {NewDeferGlitchCount}";
|
||||
}
|
||||
}
|
||||
13
RobotAndDonkey.Game/Execution/Results/DiscardResult.cs
Normal file
13
RobotAndDonkey.Game/Execution/Results/DiscardResult.cs
Normal file
@@ -0,0 +1,13 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using RobotAndDonkey.Game.Cards;
|
||||
|
||||
namespace RobotAndDonkey.Game.Execution.Results;
|
||||
|
||||
public record DiscardResult(Guid RequestId, IReadOnlyList<Card> Discard) : Result(RequestId)
|
||||
{
|
||||
public override string ToString()
|
||||
{
|
||||
return $"Discard result: {string.Join(", ", Discard.Select(c => c.ToString()))}";
|
||||
}
|
||||
}
|
||||
13
RobotAndDonkey.Game/Execution/Results/HandResult.cs
Normal file
13
RobotAndDonkey.Game/Execution/Results/HandResult.cs
Normal file
@@ -0,0 +1,13 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using RobotAndDonkey.Game.Cards;
|
||||
|
||||
namespace RobotAndDonkey.Game.Execution.Results;
|
||||
|
||||
public record HandResult(Guid RequestId, IReadOnlyList<Card> Hand) : Result(RequestId)
|
||||
{
|
||||
public override string ToString()
|
||||
{
|
||||
return $"Hand result: {string.Join(", ", Hand.Select(c => c.ToString()))}";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
using System;
|
||||
using RobotAndDonkey.Game.Board;
|
||||
using RobotAndDonkey.Game.Cards;
|
||||
|
||||
namespace RobotAndDonkey.Game.Execution.Results;
|
||||
|
||||
public enum EInvalidReason
|
||||
{
|
||||
NotFound,
|
||||
Invariant,
|
||||
Blocked,
|
||||
OutOfBounds,
|
||||
NoEnergy,
|
||||
NoTarget,
|
||||
NoAmount,
|
||||
AlreadyExecuted,
|
||||
Invalid,
|
||||
NoSpace
|
||||
}
|
||||
|
||||
public record InvalidInstructionResult(Guid RequestId, EInvalidReason Reason, Card? Card, Cell? Cell) : Result(RequestId)
|
||||
{
|
||||
public override string ToString()
|
||||
{
|
||||
return $"Invalid instruction result: {Reason}, {Card}, {Cell}";
|
||||
}
|
||||
}
|
||||
13
RobotAndDonkey.Game/Execution/Results/InventoryResult.cs
Normal file
13
RobotAndDonkey.Game/Execution/Results/InventoryResult.cs
Normal file
@@ -0,0 +1,13 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using RobotAndDonkey.Game.Cards;
|
||||
|
||||
namespace RobotAndDonkey.Game.Execution.Results;
|
||||
|
||||
public record DeckResult(Guid RequestId, IReadOnlyList<Card> Deck) : Result(RequestId)
|
||||
{
|
||||
public override string ToString()
|
||||
{
|
||||
return $"Deck result: {string.Join(", ", Deck.Select(c => c.ToString()))}";
|
||||
}
|
||||
}
|
||||
22
RobotAndDonkey.Game/Execution/Results/ModifyCardResult.cs
Normal file
22
RobotAndDonkey.Game/Execution/Results/ModifyCardResult.cs
Normal file
@@ -0,0 +1,22 @@
|
||||
using System;
|
||||
using RobotAndDonkey.Game.Cards;
|
||||
|
||||
namespace RobotAndDonkey.Game.Execution.Results;
|
||||
|
||||
public enum ECardLocation
|
||||
{
|
||||
Tape,
|
||||
Hand,
|
||||
Deck,
|
||||
Discard,
|
||||
Robot,
|
||||
Board
|
||||
}
|
||||
|
||||
public record ModifyCardResult(Guid RequestId, Card Card, EModifierId Modifier, ECardLocation Location) : Result(RequestId)
|
||||
{
|
||||
public override string ToString()
|
||||
{
|
||||
return $"Modify card result in {Location}: {Card} - {Modifier}";
|
||||
}
|
||||
}
|
||||
11
RobotAndDonkey.Game/Execution/Results/ModifyCellResult.cs
Normal file
11
RobotAndDonkey.Game/Execution/Results/ModifyCellResult.cs
Normal file
@@ -0,0 +1,11 @@
|
||||
using RobotAndDonkey.Game.Utils;
|
||||
|
||||
namespace RobotAndDonkey.Game.Execution.Results;
|
||||
|
||||
public record ModifyCellResult(Guid RequestId, Board.Board Board, EModifierId Modifier, Hex Hex) : Result(RequestId)
|
||||
{
|
||||
public override string ToString()
|
||||
{
|
||||
return $"Modify Cell result: {Hex} {Modifier}";
|
||||
}
|
||||
}
|
||||
11
RobotAndDonkey.Game/Execution/Results/ModifyRobotResult.cs
Normal file
11
RobotAndDonkey.Game/Execution/Results/ModifyRobotResult.cs
Normal file
@@ -0,0 +1,11 @@
|
||||
using System;
|
||||
|
||||
namespace RobotAndDonkey.Game.Execution.Results;
|
||||
|
||||
public record ModifyRobotResult(Guid RequestId) : Result(RequestId)
|
||||
{
|
||||
public override string ToString()
|
||||
{
|
||||
return "Modify robot result";
|
||||
}
|
||||
}
|
||||
11
RobotAndDonkey.Game/Execution/Results/NextGlitchResult.cs
Normal file
11
RobotAndDonkey.Game/Execution/Results/NextGlitchResult.cs
Normal file
@@ -0,0 +1,11 @@
|
||||
using System;
|
||||
|
||||
namespace RobotAndDonkey.Game.Execution.Results;
|
||||
|
||||
public record NextGlitchResult(Guid RequestId, int NewNextGlitch) : Result(RequestId)
|
||||
{
|
||||
public override string ToString()
|
||||
{
|
||||
return $"Next glitch result: {NewNextGlitch}";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
using System;
|
||||
|
||||
namespace RobotAndDonkey.Game.Execution.Results;
|
||||
|
||||
public record NoMoreBufferOverflowResult(Guid RequestId) : Result(RequestId)
|
||||
{
|
||||
public override string ToString()
|
||||
{
|
||||
return $"No more buffer overflow result";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
using System;
|
||||
|
||||
namespace RobotAndDonkey.Game.Execution.Results;
|
||||
|
||||
public record NoMoreGamblingResult(Guid RequestId) : Result(RequestId)
|
||||
{
|
||||
public override string ToString()
|
||||
{
|
||||
return "No more gambling result";
|
||||
}
|
||||
}
|
||||
12
RobotAndDonkey.Game/Execution/Results/PoiResult.cs
Normal file
12
RobotAndDonkey.Game/Execution/Results/PoiResult.cs
Normal file
@@ -0,0 +1,12 @@
|
||||
using RobotAndDonkey.Game.Pois;
|
||||
using RobotAndDonkey.Game.Utils;
|
||||
|
||||
namespace RobotAndDonkey.Game.Execution.Results;
|
||||
|
||||
public record PoiResult(Guid RequestId, Board.Board Board, Poi? Poi, Hex Hex) : Result(RequestId)
|
||||
{
|
||||
public override string ToString()
|
||||
{
|
||||
return $"Poi result: {Hex}";
|
||||
}
|
||||
}
|
||||
11
RobotAndDonkey.Game/Execution/Results/ProgramResult.cs
Normal file
11
RobotAndDonkey.Game/Execution/Results/ProgramResult.cs
Normal file
@@ -0,0 +1,11 @@
|
||||
using System;
|
||||
|
||||
namespace RobotAndDonkey.Game.Execution.Results;
|
||||
|
||||
public record ProgramResult(Guid RequestId, int NewProgram) : Result(RequestId)
|
||||
{
|
||||
public override string ToString()
|
||||
{
|
||||
return $"Program result: {NewProgram}";
|
||||
}
|
||||
}
|
||||
14
RobotAndDonkey.Game/Execution/Results/ProgramRowResult.cs
Normal file
14
RobotAndDonkey.Game/Execution/Results/ProgramRowResult.cs
Normal file
@@ -0,0 +1,14 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using RobotAndDonkey.Game.Cards;
|
||||
|
||||
namespace RobotAndDonkey.Game.Execution.Results;
|
||||
|
||||
public record ProgramRowResult(Guid RequestId, IReadOnlyList<Card> OrderedCards, IReadOnlyCollection<Guid> TapeCardIds) : Result(RequestId)
|
||||
{
|
||||
public override string ToString()
|
||||
{
|
||||
return $"Program row result: {string.Join(", ", OrderedCards.Select(c => c.ToString()))}; tape={string.Join(", ", TapeCardIds.Select(id => id.ToString()))}";
|
||||
}
|
||||
}
|
||||
5
RobotAndDonkey.Game/Execution/Results/Result.cs
Normal file
5
RobotAndDonkey.Game/Execution/Results/Result.cs
Normal file
@@ -0,0 +1,5 @@
|
||||
using System;
|
||||
|
||||
namespace RobotAndDonkey.Game.Execution.Results;
|
||||
|
||||
public abstract record Result(Guid RequestId);
|
||||
12
RobotAndDonkey.Game/Execution/Results/RunCardResult.cs
Normal file
12
RobotAndDonkey.Game/Execution/Results/RunCardResult.cs
Normal file
@@ -0,0 +1,12 @@
|
||||
using System;
|
||||
using RobotAndDonkey.Game.Cards;
|
||||
|
||||
namespace RobotAndDonkey.Game.Execution.Results;
|
||||
|
||||
public record RunCardResult(Guid RequestId, Card? Card) : Result(RequestId)
|
||||
{
|
||||
public override string ToString()
|
||||
{
|
||||
return $"Run card result: {Card}";
|
||||
}
|
||||
}
|
||||
11
RobotAndDonkey.Game/Execution/Results/RunPhaseResult.cs
Normal file
11
RobotAndDonkey.Game/Execution/Results/RunPhaseResult.cs
Normal file
@@ -0,0 +1,11 @@
|
||||
using System;
|
||||
|
||||
namespace RobotAndDonkey.Game.Execution.Results;
|
||||
|
||||
public record RunPhaseResult(Guid RequestId, ERunPhase NewRunPhase) : Result(RequestId)
|
||||
{
|
||||
public override string ToString()
|
||||
{
|
||||
return $"Run phase result: {NewRunPhase}";
|
||||
}
|
||||
}
|
||||
11
RobotAndDonkey.Game/Execution/Results/ShopResult.cs
Normal file
11
RobotAndDonkey.Game/Execution/Results/ShopResult.cs
Normal file
@@ -0,0 +1,11 @@
|
||||
using RobotAndDonkey.Game.Cards;
|
||||
|
||||
namespace RobotAndDonkey.Game.Execution.Results;
|
||||
|
||||
public record ShopResult(Guid RequestId, Card[] Shop) : Result(RequestId)
|
||||
{
|
||||
public override string ToString()
|
||||
{
|
||||
return $"Shop result: {string.Join(", ", Shop.Select(c => c.ToString()))}";
|
||||
}
|
||||
}
|
||||
11
RobotAndDonkey.Game/Execution/Results/TapeResult.cs
Normal file
11
RobotAndDonkey.Game/Execution/Results/TapeResult.cs
Normal file
@@ -0,0 +1,11 @@
|
||||
using RobotAndDonkey.Game.Cards;
|
||||
|
||||
namespace RobotAndDonkey.Game.Execution.Results;
|
||||
|
||||
public record TapeResult(Guid RequestId, IReadOnlyList<Card> Tape) : Result(RequestId)
|
||||
{
|
||||
public override string ToString()
|
||||
{
|
||||
return $"Tape result: {string.Join(", ", Tape.Select(c => c.ToString()))}";
|
||||
}
|
||||
}
|
||||
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);
|
||||
}
|
||||
3
RobotAndDonkey.Game/GameState/Currency.cs
Normal file
3
RobotAndDonkey.Game/GameState/Currency.cs
Normal file
@@ -0,0 +1,3 @@
|
||||
namespace RobotAndDonkey.Game.GameState;
|
||||
|
||||
public record struct Currency(int Energy, int MaxCarry, int Carry, int Delivery, int TapeLength, int HandSize);
|
||||
14
RobotAndDonkey.Game/GameState/GameState.cs
Normal file
14
RobotAndDonkey.Game/GameState/GameState.cs
Normal file
@@ -0,0 +1,14 @@
|
||||
using RobotAndDonkey.Game.Robots;
|
||||
using RobotAndDonkey.Game.Utils;
|
||||
|
||||
namespace RobotAndDonkey.Game.GameState;
|
||||
|
||||
public record GameState(CoreLoop CoreLoop, MetaGame MetaGame)
|
||||
{
|
||||
public static GameState CreateNew(MatchParameters parameters)
|
||||
{
|
||||
var random = new SRandom((ulong)parameters.Seed);
|
||||
var coreLoop = new CoreLoop(random, parameters);
|
||||
return new(coreLoop, new([]));
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user