feat: implement CardWars game engine
Implement the core CardWars game logic using an ECS-based architecture. This includes: - Card effect registry and various card effect implementations. - Game phase management (Setup, Play, Flip, Scoring, Cleanup). - Command system for player actions (PlayCard, FlipCard, etc.). - Component-based game state and entity relationships. - Automated game setup and scoring systems. - Unit tests for game setup, card effects, and play logs.
This commit is contained in:
@@ -0,0 +1,9 @@
|
||||
using MessagePack;
|
||||
|
||||
namespace Game.CardWars;
|
||||
|
||||
/// <summary>
|
||||
/// Tag component for a player's banner entity.
|
||||
/// </summary>
|
||||
[MessagePackObject]
|
||||
public struct Banner { }
|
||||
@@ -0,0 +1,17 @@
|
||||
using MessagePack;
|
||||
|
||||
namespace Game.CardWars;
|
||||
|
||||
/// <summary>
|
||||
/// Instance data for a card that has been played to the field.
|
||||
/// Cards in hand/deck only have CardDef; this is added when played.
|
||||
/// </summary>
|
||||
[MessagePackObject]
|
||||
public record struct Card
|
||||
{
|
||||
/// <summary>The rank chosen for this play (one of CardDef.Ranks).</summary>
|
||||
[Key(0)] public int Rank;
|
||||
|
||||
/// <summary>Whether the card is face-down on the field.</summary>
|
||||
[Key(1)] public bool FaceDown;
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
using MessagePack;
|
||||
|
||||
namespace Game.CardWars;
|
||||
|
||||
/// <summary>
|
||||
/// Static card definition — name, possible ranks, effect, and kind.
|
||||
/// Cards in the deck/hand only have CardDef. When played to the field,
|
||||
/// they also get a Card component with the chosen rank.
|
||||
/// </summary>
|
||||
[MessagePackObject]
|
||||
public record struct CardDef
|
||||
{
|
||||
[Key(0)] public string Name;
|
||||
[Key(1)] public int[] Ranks;
|
||||
[Key(2)] public CardEffect Effect;
|
||||
[Key(3)] public CardKind Kind;
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
using MessagePack;
|
||||
|
||||
namespace Game.CardWars;
|
||||
|
||||
/// <summary>
|
||||
/// A castle that can be won each round.
|
||||
/// </summary>
|
||||
[MessagePackObject]
|
||||
public record struct Castle
|
||||
{
|
||||
[Key(0)] public CastleColor Color;
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
using MessagePack;
|
||||
|
||||
namespace Game.CardWars;
|
||||
|
||||
/// <summary>
|
||||
/// Tag component for a faction deck entity.
|
||||
/// </summary>
|
||||
[MessagePackObject]
|
||||
public struct FactionDeck { }
|
||||
@@ -0,0 +1,15 @@
|
||||
using MessagePack;
|
||||
using OECS;
|
||||
|
||||
namespace Game.CardWars;
|
||||
|
||||
/// <summary>
|
||||
/// Relationship from a card entity to the player who holds it in hand.
|
||||
/// Added to the card: Source=card, Target=player.
|
||||
/// </summary>
|
||||
[MessagePackObject]
|
||||
public record struct HeldBy : IRelationship
|
||||
{
|
||||
[Key(0)] public Entity Source { get; set; }
|
||||
[Key(1)] public Entity Target { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
using MessagePack;
|
||||
|
||||
namespace Game.CardWars;
|
||||
|
||||
/// <summary>
|
||||
/// Tag component for a horn token. Can be placed on cards, banners, or leaders.
|
||||
/// </summary>
|
||||
[MessagePackObject]
|
||||
public struct Horn { }
|
||||
@@ -0,0 +1,14 @@
|
||||
using MessagePack;
|
||||
using OECS;
|
||||
|
||||
namespace Game.CardWars;
|
||||
|
||||
/// <summary>
|
||||
/// Relationship from a card entity to a deck entity.
|
||||
/// </summary>
|
||||
[MessagePackObject]
|
||||
public record struct InDeck : IRelationship
|
||||
{
|
||||
[Key(0)] public Entity Source { get; set; }
|
||||
[Key(1)] public Entity Target { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
using MessagePack;
|
||||
|
||||
namespace Game.CardWars;
|
||||
|
||||
/// <summary>
|
||||
/// Tag component marking a card entity as the leader card.
|
||||
/// The leader is always on the field and contributes its rank to power.
|
||||
/// </summary>
|
||||
[MessagePackObject]
|
||||
public struct Leader { }
|
||||
@@ -0,0 +1,14 @@
|
||||
using MessagePack;
|
||||
using OECS;
|
||||
|
||||
namespace Game.CardWars;
|
||||
|
||||
/// <summary>
|
||||
/// Relationship from a card entity to the player entity whose field it's on.
|
||||
/// </summary>
|
||||
[MessagePackObject]
|
||||
public record struct OnField : IRelationship
|
||||
{
|
||||
[Key(0)] public Entity Source { get; set; }
|
||||
[Key(1)] public Entity Target { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
using MessagePack;
|
||||
using OECS;
|
||||
|
||||
namespace Game.CardWars;
|
||||
|
||||
// ────────────────────────────────────────────────────────────
|
||||
// Per-effect pending choice components.
|
||||
// Each is placed on the singleton entity when a card effect
|
||||
// requires player input. Their presence blocks game advancement.
|
||||
// ────────────────────────────────────────────────────────────
|
||||
|
||||
/// <summary>Mercenary: choose a combatant to give a skull.</summary>
|
||||
[MessagePackObject]
|
||||
public record struct PendingMercenary
|
||||
{
|
||||
[Key(0)] public Entity CardEntity;
|
||||
[Key(1)] public Entity Player;
|
||||
}
|
||||
|
||||
/// <summary>Dancer: choose up to 2 combatants for horn. Step tracks progress.</summary>
|
||||
[MessagePackObject]
|
||||
public record struct PendingDancer
|
||||
{
|
||||
[Key(0)] public Entity CardEntity;
|
||||
[Key(1)] public Entity Player;
|
||||
[Key(2)] public int Step; // 0 = first choice, 1 = second choice
|
||||
}
|
||||
|
||||
/// <summary>Paladin: choose a combatant or banner for horn.</summary>
|
||||
[MessagePackObject]
|
||||
public record struct PendingPaladin
|
||||
{
|
||||
[Key(0)] public Entity CardEntity;
|
||||
[Key(1)] public Entity Player;
|
||||
}
|
||||
|
||||
/// <summary>Nun: choose a field card to discard.</summary>
|
||||
[MessagePackObject]
|
||||
public record struct PendingNun
|
||||
{
|
||||
[Key(0)] public Entity CardEntity;
|
||||
[Key(1)] public Entity Player;
|
||||
}
|
||||
|
||||
/// <summary>Scout: choose an opponent player to scout.</summary>
|
||||
[MessagePackObject]
|
||||
public record struct PendingScout
|
||||
{
|
||||
[Key(0)] public Entity CardEntity;
|
||||
[Key(1)] public Entity Player;
|
||||
}
|
||||
|
||||
/// <summary>Pegasus: choose a field card to return to hand (or null to pass).</summary>
|
||||
[MessagePackObject]
|
||||
public record struct PendingPegasus
|
||||
{
|
||||
[Key(0)] public Entity CardEntity;
|
||||
[Key(1)] public Entity Player;
|
||||
}
|
||||
|
||||
/// <summary>CurseMaster: choose a horn token to turn into skull.</summary>
|
||||
[MessagePackObject]
|
||||
public record struct PendingCurseMaster
|
||||
{
|
||||
[Key(0)] public Entity CardEntity;
|
||||
[Key(1)] public Entity Player;
|
||||
}
|
||||
|
||||
// ────────────────────────────────────────────────────────────
|
||||
// Helper to detect any pending component on the singleton.
|
||||
// ────────────────────────────────────────────────────────────
|
||||
|
||||
public static class PendingChoice
|
||||
{
|
||||
/// <summary>Returns true if any pending choice component exists on the singleton.</summary>
|
||||
public static bool Any(World world)
|
||||
{
|
||||
return world.HasSingleton<PendingMercenary>()
|
||||
|| world.HasSingleton<PendingDancer>()
|
||||
|| world.HasSingleton<PendingPaladin>()
|
||||
|| world.HasSingleton<PendingNun>()
|
||||
|| world.HasSingleton<PendingScout>()
|
||||
|| world.HasSingleton<PendingPegasus>()
|
||||
|| world.HasSingleton<PendingCurseMaster>();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
using MessagePack;
|
||||
|
||||
namespace Game.CardWars;
|
||||
|
||||
/// <summary>
|
||||
/// Placed on the singleton when a 决斗家 (Duelist) is played.
|
||||
/// The play phase ends when the turn comes back to this player.
|
||||
/// Removed automatically when the phase transitions.
|
||||
/// </summary>
|
||||
[MessagePackObject]
|
||||
public struct PendingDuelist
|
||||
{
|
||||
[Key(0)] public int PlayerIndex;
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
using MessagePack;
|
||||
using OECS;
|
||||
|
||||
namespace Game.CardWars;
|
||||
|
||||
/// <summary>
|
||||
/// Relationship from a horn/skull token entity to the target entity it's placed on.
|
||||
/// </summary>
|
||||
[MessagePackObject]
|
||||
public record struct PlacedOn : IRelationship
|
||||
{
|
||||
[Key(0)] public Entity Source { get; set; }
|
||||
[Key(1)] public Entity Target { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
using MessagePack;
|
||||
|
||||
namespace Game.CardWars;
|
||||
|
||||
/// <summary>
|
||||
/// Tag + index for a player entity.
|
||||
/// </summary>
|
||||
[MessagePackObject]
|
||||
public record struct Player
|
||||
{
|
||||
[Key(0)] public int Index;
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
using MessagePack;
|
||||
|
||||
namespace Game.CardWars;
|
||||
|
||||
/// <summary>
|
||||
/// Tag component for the public deck entity.
|
||||
/// </summary>
|
||||
[MessagePackObject]
|
||||
public struct PublicDeck { }
|
||||
@@ -0,0 +1,9 @@
|
||||
using MessagePack;
|
||||
|
||||
namespace Game.CardWars;
|
||||
|
||||
/// <summary>
|
||||
/// Tag component for a skull token. Can be placed on cards, banners, or leaders.
|
||||
/// </summary>
|
||||
[MessagePackObject]
|
||||
public struct Skull { }
|
||||
Reference in New Issue
Block a user