A Python engine for MTG-like card games.
Project description
PTG — Python The Gathering
A library for building MTG-like card games. Define your cards in YAML, assemble decks, and drive the game through a small set of pure, testable actions — no side-effects, no global state.
Table of Contents
- Quick Start
- Core Concepts
- Card Definition Reference
- Mana System
- Abilities & Effects
- Triggers
- Combat System
- Techniques
- Group (Tribes)
- Loading Cards & Building Decks
- Public API Reference
- Appendix: Complete Card Examples
1. Quick Start
Minimum working example — two players, hard‑coded cards, one turn.
from ptg.api import (
Game, Player, CardDefinition,
CardType, ManaType, ManaPool, ManaRequirement,
PlayCardAction, DeclareAttackAction,
)
from ptg.engine.types import CardAbility, AlterationData
from ptg.effects.draw import DrawCardEffect
# ── Card definitions ──────────────────────────────────────────
goblin = CardDefinition("gob_01", "Goblin Warrior", "Fast.",
CardType.CREATURE,
mana={ManaType.FIRE: 1},
attack=2, defense=2)
fire_mana = CardDefinition("fire_01", "Fire Mana", "Gives 1 fire.",
CardType.MANA,
mana={ManaType.FIRE: 1})
# ── Players ───────────────────────────────────────────────────
alice = Player("Alice", [fire_mana] * 3 + [goblin] * 3, hand_size=3)
bob = Player("Bob", [fire_mana] * 3 + [goblin] * 3, hand_size=3)
game = Game([alice, bob])
game.start()
game.advance_phase() # DRAW → MAIN
# Play mana, then a creature
for _ in range(3):
cid = next(id for id in game.state.hands[alice.uuid]
if game.state.all_cards[id].name == "Fire Mana")
game.apply_action(PlayCardAction(), player_id=alice.uuid, card_id=cid)
goblin_id = next(id for id in game.state.hands[alice.uuid]
if game.state.all_cards[id].name == "Goblin Warrior")
game.apply_action(PlayCardAction(), player_id=alice.uuid, card_id=goblin_id)
# Attack and end turn (advances through ATTACK, DEFENSE, COMBAT, POSTCOMBAT, END → DRAW)
attacker_id = next(id for id in game.state.battlefield[alice.uuid]
if game.state.all_cards[id].name == "Goblin Warrior")
game.apply_action(DeclareAttackAction(), player_id=alice.uuid,
attacker_id=attacker_id)
game.advance_phase() # MAIN → ATTACK
game.advance_phase() # ATTACK → DEFENSE
game.advance_phase() # DEFENSE → COMBAT (auto-resolve)
game.advance_phase() # COMBAT → MAIN_POSTCOMBAT
game.advance_phase() # MAIN_POSTCOMBAT → END → next player DRAW
print(f"Bob HP: {game.state.players[bob.uuid].health}") # 18
2. Core Concepts
flowchart LR
subgraph Setup
YAML[Card YAML files] -->|yaml_loader| Def[CardDefinition]
Def -->|deck_builder| Deck[Deck list]
end
subgraph Runtime
Deck -->|Player.initialize| GS[GameState]
GS --> Actions[Play / Attack / Defend / Activate]
Actions -->|returns new| GS2[GameState]
GS2 -->|check_victory| Winner
end
Card
A card has two representations:
- CardDefinition — the blueprint. Immutable. Lives in YAML or Python.
Contains
card_id,name,type,manacost,health,attack,defense, and optionalabilities. - CardInstance — one copy in play. Has a unique
uuid, currenthealth,attack,defense, asummoning_sickflag that prevents attacking until the next turn, and a reference back to itsCardDefinition.
Player
Each player owns four zones:
| Zone | Contents | Notes |
|---|---|---|
deck |
Cards not yet drawn | Shuffled at game start. |
hand |
Cards available to play | Hidden from opponent. |
battlefield |
Creatures in play | Public. |
graveyard |
Discarded / destroyed cards | Public. |
A player also has health, a mana_pool, and a maximum hand size.
Game
The Game object owns the GameState and the EventBus. It provides
methods to advance turns, apply actions, resolve combat, and check victory.
GameState
The entire game at one point in time — cards, zones, players, pending combats.
Every action returns a new GameState; the old one is never modified.
3. Card Definition Reference
Fields
| Field | Type | Required | Default | Notes |
|---|---|---|---|---|
card_id |
str |
yes | — | Unique identifier. |
name |
str |
yes | — | Display name. |
description |
str |
no | "" |
Flavour or tooltip text. |
type |
CardType |
yes | — | See table below. |
mana |
dict[ManaType, int] |
no | {} |
Cost to play OR mana provided (for mana cards). |
attack |
int |
no | 0 |
Damage dealt when attacking. |
defense |
int |
no | 0 |
Creature toughness — its current life. Regenerates each turn. |
abilities |
list[CardAbility] |
no | None |
See Abilities & Effects. |
techniques |
set[Technique] |
no | set() |
Passive creature keywords. See Techniques. |
group |
str |
no | None |
Tribal identifier. See Group. |
CardType
| Value | Meaning |
|---|---|
creature |
Stays on the battlefield. Can attack and block. Its defense is its current life; it regenerates each turn. Enters with summoning sickness. |
spell |
Resolves immediately, then goes to graveyard. |
mana |
Stays on the battlefield as a permanent. Provides mana every turn. Free to play. |
Examples
# A simple creature
card_id: goblin_01
name: Goblin Warrior
type: creature
mana:
fire: 1
attack: 2
defense: 2
# A mana card (always free to play)
card_id: fire_mana_01
name: Fire Mana
type: mana
mana:
fire: 1
# A spell that deals 3 damage
card_id: fireball_01
name: Fireball
type: spell
mana:
fire: 2
abilities:
- name: Explosion
description: Deal 3 damage.
ability_type: activated
mana_requirement: {}
alterations:
- effect: damage_card
start_trigger: null
params:
amount: 3
4. Mana System
ManaType
| Type | Colour / flavour |
|---|---|
fire |
Red |
earth |
Green |
water |
Blue |
air |
White |
any |
Generic — paid with leftover mana of any type |
Playing mana
Mana cards are free to play and stay on the battlefield as permanents.
Playing a Fire Mana adds 1 fire to your pool and to your mana_sources.
At the start of each of your turns, your pool is refilled from your sources.
You may only play one mana card per turn. Effects that generate mana
(e.g. gain_mana) do not count toward this limit.
Hand limit
Your maximum hand size is 7 cards. At the end of your turn, if you have more than 7 cards, you discard down to 7 at random.
First turn
The player who goes first skips their draw step on the first turn.
The ANY type
When a requirement includes any, specific types are paid first, then
the any shortfall is covered from the remaining pool:
Pool: {fire: 2, earth: 1}
Cost: {fire: 1, any: 1}
1. Pay fire: pool → {fire: 1, earth: 1}
2. Cover any: pool → {fire: 0, earth: 1}
Cost fields in YAML
| Field | Where | Meaning |
|---|---|---|
mana |
Top-level on a card | Cost to play the card (free for mana cards). |
mana_requirement |
Inside an ability |
Cost to activate that ability. |
5. Abilities & Effects
An ability is a named power on a card. An ability contains one or more alterations; each alteration links an effect to a target with an optional trigger.
AbilityType
| Value | Behaviour |
|---|---|
triggered |
Fires automatically when its start_trigger event occurs. No mana cost at trigger time. |
activated |
The player chooses when to use it and pays its mana_requirement. |
Alteration structure
alterations:
- effect: <registered effect name> # required
target: <TargetSpec | null> # default: null (see PendingTrigger below)
start_trigger: <EventType | null> # default: null
end_trigger: <EventType | null> # default: null
trigger_self: <bool> # default: true
turn_duration: <int | null> # default: null — number of turns the effect lasts
params: # depends on the effect
...
TargetSpec
| Value | Resolves to |
|---|---|
self |
The card that owns the ability. |
owner |
The player who controls the card. |
opponent |
The other player. |
choose |
The player must choose a target manually (see PendingTrigger). |
null |
Same as choose — the player must choose. |
trigger_self
Controls whether a triggered ability only fires for events originating from the owning card itself:
| Value | Behaviour |
|---|---|
true (default) |
The ability fires only when the event's source matches the owning card_id. Events with source=None (turn events) are never filtered. |
false |
The ability fires for any event of the matching type, regardless of source. Useful for global-scope triggers like "whenever a creature dies". |
trigger_owner
Filters triggered abilities by the zone owner of the event. Works together
with trigger_self for fine-grained control:
| Value | Behaviour |
|---|---|
any (default) |
No filtering — triggers for events in any player's zone. |
owner |
Triggers only when zone_owner matches the card's controller. "When a creature enters YOUR battlefield…" |
opponent |
Triggers only when zone_owner does not match the card's controller. "When an opponent's creature dies…" |
Zone owner info (zone_owner) is automatically included in the context of
every zone-related event (ON_CARD_ENTER_BATTLEFIELD, ON_CARD_DEATH, etc.).
# When an opponent's creature dies, draw a card
alterations:
- effect: draw_card
target: owner
start_trigger: on_card_death
trigger_self: false
trigger_owner: opponent
params:
count: 1
trigger_card_id
Filters triggered abilities by the definition ID (card_id field in YAML)
of the event's source card. When set, the ability only fires if the card that
triggered the event has the specified card_id.
Use together with trigger_self: false to create "tribal" triggers:
# When a Goblin Warrior enters the battlefield, draw a card
alterations:
- effect: draw_card
target: owner
start_trigger: on_card_enter_battlefield
trigger_self: false
trigger_card_id: goblin_01
params:
count: 1
# When opponent's Giant dies, heal 5
alterations:
- effect: heal_player
target: owner
start_trigger: on_card_death
trigger_self: false
trigger_owner: opponent
trigger_card_id: giant_01
params:
amount: 5
Events with source=None (turn events) are never matched by trigger_card_id.
end_trigger
When an alteration specifies both start_trigger and end_trigger, the engine
automatically registers a second listener that reverses the effect by
negating numeric params when the end event fires:
# Temporary +1/+1 until end of turn:
alterations:
- effect: buff_card
start_trigger: on_turn_start
end_trigger: on_turn_end
target: self
params:
attack_increase: 1
defense_increase: 1
At on_turn_end, the engine applies buff_card(attack_increase=-1, defense_increase=-1).
This works for any effect with numeric params (damage_card, heal_player,
draw_card, etc.).
turn_duration
Sets a turn-based expiration for an effect. The engine tracks the buff
internally and reverses it automatically when current_turn reaches
expires_on_turn. turn_duration: 1 means the effect is reversed at the
next turn start.
# +2/+1 until the next turn start
alterations:
- effect: buff_card
start_trigger: on_card_enter_battlefield
target: self
turn_duration: 1
params:
attack_increase: 2
defense_increase: 1
Effect catalogue
| Effect name | Target type | Parameters | Description |
|---|---|---|---|
damage_card |
Card | amount: int |
Deals damage to a creature, reducing its defense. |
damage_player |
Player | amount: int |
Deals damage to a player. |
heal_player |
Player | amount: int |
Restores health to a player. |
buff_card |
Card | attack_increase, defense_increase (default 0) |
Increases a creature's stats. |
buff_all |
Player | attack_increase, defense_increase (default 0) |
Buffs every creature on that player's battlefield. |
draw_card |
Player | count: int (default 1) |
Draws cards from the top of the deck. |
destroy_card |
Card | — | Sets a creature's health to 0. |
spend_mana |
Player | mana_requirement: dict |
Deducts mana from the player's pool. |
gain_mana |
Player | amounts: dict[ManaType, int] |
Adds temporary mana to a player's pool (does not increase mana_sources). |
spawn_creature |
Player | template: CardDefinition + count: int (default 1) |
Creates copies of a creature on the battlefield. |
recruit |
Player | card_definition_id: str, from_zone: ZoneType |
Moves a matching card from hand / deck / graveyard to the battlefield. |
return_to_hand |
Card | — | Removes a card from the battlefield and puts it back in the owner's hand. |
mill |
Player | count: int (default 1) |
Moves cards from the top of the deck directly into the graveyard. |
random_discard |
Player | count: int (default 1) |
Randomly discards cards from the player's hand into their graveyard. |
set_stats |
Card | attack: int, defense: int |
Sets a creature's attack and/or defense to absolute values. |
recycle_graveyard |
Player | — | Shuffles all cards from the graveyard back into the deck. |
move_card |
Card | from_zone: ZoneType, to_zone: ZoneType |
Moves a specific card from one zone to another within its owner. Example: from_zone: hand, to_zone: graveyard. |
Example: triggered ability
abilities:
- name: Healing Aura
description: When this enters, heal your hero for 3.
ability_type: triggered
mana_requirement: {}
alterations:
- effect: heal_player
target: owner
start_trigger: on_card_enter_battlefield
params:
amount: 3
Example: activated ability
abilities:
- name: Fire Blast
description: Pay 2 fire → deal 2 damage to opponent.
ability_type: activated
mana_requirement:
fire: 2
alterations:
- effect: damage_player
target: opponent
params:
amount: 2
Dynamic parameter references
Params can reference the source card's current stats at runtime using:
| Syntax | Resolves to |
|---|---|
$attack |
The source card's current attack value. |
$defense |
The source card's current defense value. |
abilities:
- name: Life Drain
description: When this enters, heal equal to its attack.
ability_type: triggered
mana_requirement: {}
alterations:
- effect: heal_player
target: owner
start_trigger: on_card_enter
params:
amount: $attack
PendingTrigger — player-chosen targets
When a triggered ability has target: null or target: choose, the engine
cannot resolve the target automatically. Instead of dropping the trigger, it
defers it by adding a PendingTrigger to GameState.pending_triggers.
The game server must:
- After each action, check if
state.pending_triggersis non-empty. - Prompt the player to select a target (creature or player).
- Call
game.resolve_pending_trigger(trigger_index, chosen_target)to apply the effect and register theend_triggerlistener (if present).
The engine handles the full lifecycle — start, resolution, and end reversal — automatically once the target is chosen.
# Server-side flow
state = game.apply_action(PlayCardAction(), player_id=p1, card_id=card)
if state.pending_triggers:
# Send trigger info to frontend for target selection
trigger = state.pending_triggers[0]
emit("choose_target", {
"source_card_id": trigger.source_card_id,
"trigger_index": 0,
})
# After player picks target "g02":
state = game.resolve_pending_trigger(0, chosen_target=CardID("g02"))
# Engine applies effect and registers end_trigger listener automatically
Example: spawn tokens
# Using an inline template
- effect: spawn_creature
target: owner
start_trigger: on_card_enter_battlefield
params:
template:
card_id: token_wasp
name: Wasp
type: creature
defense: 1
attack: 1
count: 2
# Using a catalog reference (requires loading with a catalog)
- effect: spawn_creature
target: owner
start_trigger: on_card_death
params:
template_card_id: goblin_01
count: 1
Example: recruit from graveyard
- effect: recruit
target: owner
start_trigger: on_card_enter_battlefield
params:
card_definition_id: goblin_01
from_zone: graveyard
6. Triggers
Triggered abilities fire when a matching event is emitted. The table below lists every event in the engine.
| EventType (YAML value) | Emitted when … |
|---|---|
on_card_enter_battlefield |
A creature is placed on the battlefield. |
on_card_death |
A creature's health drops to 0 (it is moved to the graveyard). |
on_card_receive_damage |
A creature takes damage. |
on_card_attack |
A creature is declared as an attacker. |
on_card_defense |
A creature is declared as a blocker. |
on_card_healed |
A creature is healed. |
on_card_draw |
A player draws one or more cards. |
on_card_enter_graveyard |
Any card enters the graveyard (creatures, spells). |
on_card_leave_battlefield |
A card is removed from the battlefield for any reason (death, bounce). |
on_card_leave_graveyard |
A card is removed from the graveyard (e.g., recruited to the battlefield). |
on_player_receive_damage |
A player takes damage. |
on_player_death |
A player's health drops to 0. |
on_player_mana_spent |
A player spends mana. |
on_player_mana_gained |
A player gains mana. |
on_player_healed |
A player is healed. |
on_turn_start |
A player's turn begins (after flags are reset). |
on_turn_end |
A player's turn ends. |
on_attack_declared |
An attack is declared (before blockers are chosen). |
on_defense_declared |
A blocker is assigned to an attacker. |
on_combat_resolved |
All pending combats are resolved for the turn. |
on_ability_activated |
A player activates an activated ability. |
Event order during an attack
sequenceDiagram
participant Attacker
participant Blocker
participant Engine
Attacker->>Engine: DeclareAttackAction
Engine-->>Engine: emit ON_ATTACK_DECLARED
Blocker->>Engine: DeclareDefenseAction
Engine-->>Engine: emit ON_DEFENSE_DECLARED
Engine->>Engine: resolve_combat()
Engine-->>Engine: emit ON_CARD_RECEIVE_DAMAGE
Engine-->>Engine: emit ON_CARD_LEAVE_BATTLEFIELD (if any)
Engine-->>Engine: emit ON_CARD_DEATH (if any)
Engine-->>Engine: emit ON_PLAYER_RECEIVE_DAMAGE (spillover)
Engine-->>Engine: emit ON_COMBAT_RESOLVED
7. Combat System
Turn structure
flowchart TD
Start([start]) --> DRAW[DRAW: auto-refresh mana, draw card]
DRAW --> MAIN[MAIN: play cards, activate abilities]
MAIN --> ATTACK[ATTACK: declare attackers]
ATTACK --> DEFENSE[DEFENSE: declare blockers]
DEFENSE --> COMBAT[COMBAT: auto-resolve damage]
COMBAT --> MAIN2[MAIN_POSTCOMBAT: play cards]
MAIN2 --> END[END: auto-switch player]
END --> DRAW
Direct attack (no blockers)
When no blockers are assigned, the attacker's full attack value hits the
defending player directly.
game.apply_action(DeclareAttackAction(),
player_id=alice.uuid, attacker_id=goblin_id)
game.resolve_combat()
# Bob loses attack value in health
Blocked attack (damage exchange)
Both the attacker and the blocker deal damage to each other simultaneously.
Damage is applied directly to each card's defense. A card dies when its
defense reaches 0.
game.apply_action(DeclareDefenseAction(),
player_id=bob.uuid, blocker_id=giant_id, combat_index=0)
game.resolve_combat()
Multi-blocking
When multiple blockers are assigned to one attacker, the attacker distributes its damage among blockers in the order chosen by the attacker. It must assign lethal damage to each blocker before moving to the next. Every blocker deals its full attack back to the attacker.
Spillover (trample)
A creature with the Trample technique that is blocked may assign excess damage (beyond what is lethal to all blockers) to the defending player. Without Trample, excess damage is lost.
Deathtouch
A creature dealt any amount of damage (>0) by a creature with Deathtouch is destroyed, regardless of its defense. Deathtouch also reduces the lethal damage threshold to 1 for damage-assignment purposes (enabling the deathtouch + trample combo).
Flying & Reach
Creatures with Flying can only be blocked by other creatures with Flying or by creatures with Reach. Reach does not grant Flying — it only allows the creature to block Flying attackers.
Death & graveyard
Creatures that reach 0 health are moved from the battlefield to their owner's
graveyard, and an on_card_death event is emitted.
Attack / block restrictions
Summoning sickness: a creature cannot attack or use activated abilities
on the turn it enters the battlefield. The summoning_sick flag is set when
the creature enters and cleared at the start of its controller's next turn.
Creatures with the Haste technique ignore summoning sickness.
A creature that attacked during its controller's turn cannot block until the
beginning of that controller's next turn. The attacked_this_turn flag
is set by DeclareAttackAction and reset by _handle_draw at turn start.
DeclareDefenseAction rejects any blocker with attacked_this_turn=True.
8. Techniques
Techniques are passive keywords on creatures that modify game rules. They
are defined as a set[Technique] in the card's YAML or CardDefinition.
Available techniques
| Technique | YAML value | Effect |
|---|---|---|
| Haste | haste |
Ignores summoning sickness. Can attack and use activated abilities the turn it enters. |
| Flying | flying |
Can only be blocked by creatures with Flying or Reach. |
| Reach | reach |
Can block creatures with Flying (does not grant Flying itself). |
| Trample | trample |
Excess combat damage beyond blockers' lethal spills over to the defending player. |
| Deathtouch | deathtouch |
Any amount of damage (>0) dealt to a creature is lethal. Reduces the lethal threshold to 1 for damage assignment. |
YAML syntax
card_id: sky_knight_01
name: Sky Knight
type: creature
mana:
air: 2
attack: 3
defense: 2
techniques:
- flying
- haste
Helper functions
The ptg.techniques package exports helpers for checking techniques
programmatically:
from ptg.techniques import has_flying, has_reach, can_block
if can_block(blocker.card_definition, attacker.card_definition):
game.apply_action(DeclareDefenseAction(), ...)
9. Group (Tribes)
Cards can belong to a group via the group field (e.g., vampire,
witch, soldier). Group information enables two features:
9.1 Conditional triggers by group
Use require_group on an alteration to make a triggered ability fire
only when a card of that group is present in a specific zone:
abilities:
- name: Pack Leader
ability_type: triggered
alterations:
- effect: buff_card
target: self
start_trigger: on_card_enter_battlefield
require_group: witch
require_in_zone: battlefield
require_zone_owner: owner
params:
attack_increase: 1
Works alongside require_card_id, with AND semantics (if both are
specified, both must be present). Uses the same require_in_zone
and require_zone_owner fields.
9.2 Mass effects by group
Use group on an alteration to apply the effect to all cards
of that group in a specific zone, instead of a single target:
abilities:
- name: Witch Hunt
ability_type: activated
alterations:
- effect: destroy_card
target: opponent # whose zone to search
group: witch # which group to affect
from_zone: battlefield # optional, defaults to battlefield
The target must resolve to a player ID (owner or opponent).
If it resolves to a card ID (e.g., self), the engine finds that
card's owner automatically.
from_zone defaults to battlefield but can be set to graveyard,
hand, or deck to target cards in other zones.
9.3 YAML fields reference
| Field | Location | Purpose |
|---|---|---|
group |
Card definition | The card's tribal identifier. |
group |
Alteration | Apply the effect to all cards of this group. |
require_group |
Alteration | Trigger conditionally on presence of this group. |
from_zone |
Alteration | Zone to search when using group or require_group. |
9.4 Example card
card_id: witch_coven_01
name: Witch Coven
type: creature
group: witch
mana:
any: 3
attack: 2
defense: 3
abilities:
- name: Sisterhood
description: When this enters, if you control another Witch, gain +1/+1.
ability_type: triggered
alterations:
- effect: buff_card
target: self
start_trigger: on_card_enter_battlefield
require_group: witch
require_in_zone: battlefield
require_zone_owner: owner
params:
attack_increase: 1
defense_increase: 1
10. Loading Cards & Building Decks
Card YAML files
Place one .yaml file per card in a directory:
cards/
goblin.yaml
fireball.yaml
fire_mana.yaml
...
Deck YAML files (optional)
# decks/aggro.yaml
cards:
goblin_01: 3
fireball_01: 2
fire_mana_01: 5
Loading workflow
from ptg.api import (
load_cards_from_dir, build_catalog, load_deck, build_deck,
Player, Game,
)
# 1. Load every card definition
all_cards = load_cards_from_dir("cards/")
# 2. Index by card_id for fast lookup
catalog = build_catalog(all_cards)
# 3. Build decks
alice_deck = load_deck(catalog, "decks/alice_aggro.yaml")
bob_deck = build_deck(catalog, {"giant_01": 3, "fire_mana_01": 5, "heal_01": 2})
# 4. Create players
alice = Player("Alice", alice_deck, initial_health=20, hand_size=4)
bob = Player("Bob", bob_deck, initial_health=20, hand_size=4)
game = Game([alice, bob])
game.start()
Using a catalog for template_card_id
When a card references another card via template_card_id, pass the catalog
to the loader:
# cards/hive_mind.yaml uses template_card_id: wasp_token_01
catalog = build_catalog(load_cards_from_dir("cards/"))
hive = load_card("cards/hive_mind.yaml", catalog=catalog)
11. Public API Reference
from ptg.api import (
# ── Core types ───────────────────────────────────────────
CardType, ManaType, EventType, AbilityType, TargetSpec,
Technique,
ManaRequirement, ManaPool,
# ── Engine ───────────────────────────────────────────────
Game, GameState, Player,
CardDefinition, CardAbility, AlterationData,
PendingTrigger,
# ── Actions ──────────────────────────────────────────────
PlayCardAction, DeclareAttackAction,
DeclareDefenseAction, OrderBlockersAction,
ActivateAbilityAction,
# ── I/O ──────────────────────────────────────────────────
load_card, load_cards_from_dir,
build_catalog, build_deck, load_deck,
)
Game methods
The recommended way to control a turn is advance_phase(). begin_turn()
and end_turn() are available for fine-grained manual control.
| Method | Returns | Description |
|---|---|---|
start() |
GameState |
Initialises both players, draws starting hands, sets phase to DRAW. |
advance_phase() |
GameState |
Moves to the next phase. Executes automatic logic for DRAW / COMBAT / END. |
begin_turn() |
GameState |
Manual jump to DRAW phase (legacy). |
end_turn() |
GameState |
Manual jump to END phase (legacy). |
apply_action(action, **kwargs) |
GameState |
Runs any action through the engine. |
resolve_combat() |
GameState |
Processes all pending combats (called automatically in COMBAT phase). |
resolve_pending_trigger(index, chosen_target) |
GameState |
Applies a deferred triggered ability to the player's chosen target and registers its end_trigger listener. |
check_victory() |
Player | None |
Returns the winner, or None if the game continues. |
| Property | Type | Description |
|---|---|---|
current_phase |
PhaseType | None |
Current phase of the active player's turn. |
playable_phases |
tuple[PhaseType, ...] |
Phases where the active player may play cards or activate abilities. |
Action parameters
| Action | kwargs |
|---|---|
PlayCardAction |
player_id, card_id |
DeclareAttackAction |
player_id, attacker_id |
DeclareDefenseAction |
player_id, blocker_id, combat_index |
OrderBlockersAction |
player_id, combat_index, ordered_blocker_ids |
ActivateAbilityAction |
player_id, card_id, ability_index, target_id (optional) |
I/O functions
| Function | Signature |
|---|---|
load_card |
(path, catalog=None) → CardDefinition |
load_cards_from_dir |
(dir_path, catalog=None) → list[CardDefinition] |
build_catalog |
(cards) → dict[str, CardDefinition] |
build_deck |
(catalog, spec) → list[CardDefinition] |
load_deck |
(catalog, path) → list[CardDefinition] |
12. Appendix: Complete Card Examples
Creature with no abilities
card_id: giant_01
name: Clumsy Giant
type: creature
mana:
earth: 2
defense: 5
attack: 3
defense: 1
Creature with a triggered ability
card_id: priest_01
name: Healing Priest
description: When this enters, heal your hero for 3.
type: creature
mana:
any: 2
defense: 3
attack: 1
defense: 1
abilities:
- name: Healing Aura
description: Heal your hero for 3.
ability_type: triggered
mana_requirement: {}
alterations:
- effect: heal_player
target: owner
start_trigger: on_card_enter_battlefield
params:
amount: 3
Creature with an activated ability
card_id: pyromancer_01
name: Pyromancer
type: creature
mana:
fire: 2
defense: 3
attack: 2
defense: 0
abilities:
- name: Fire Blast
description: Pay 2 fire → deal 2 damage to opponent.
ability_type: activated
mana_requirement:
fire: 2
alterations:
- effect: damage_player
target: opponent
params:
amount: 2
Creature that spawns tokens (inline template)
card_id: hive_mind_01
name: Hive Mind
description: When this enters, create two 1/1 Wasps.
type: creature
mana:
any: 3
defense: 2
attack: 1
defense: 0
abilities:
- name: Swarm
description: Spawn two Wasp tokens.
ability_type: triggered
mana_requirement: {}
alterations:
- effect: spawn_creature
target: owner
start_trigger: on_card_enter_battlefield
params:
template:
card_id: token_wasp
name: Wasp
type: creature
defense: 1
attack: 1
count: 2
Creature that recruits from graveyard
card_id: necromancer_01
name: Necromancer
description: When this enters, return a Goblin Warrior from your
graveyard to the battlefield.
type: creature
mana:
any: 4
defense: 3
attack: 2
defense: 1
abilities:
- name: Dark Ritual
description: Recruit a Goblin Warrior.
ability_type: triggered
mana_requirement: {}
alterations:
- effect: recruit
target: owner
start_trigger: on_card_enter_battlefield
params:
card_definition_id: goblin_01
from_zone: graveyard
Creature that mills the opponent
card_id: mill_imp_01
name: Mill Imp
description: When this enters, mill 3 cards from your opponent's deck.
type: creature
mana:
any: 2
defense: 2
attack: 1
defense: 0
abilities:
- name: Mind Rot
description: Mill 3 cards.
ability_type: triggered
mana_requirement: {}
alterations:
- effect: mill
target: opponent
start_trigger: on_card_enter_battlefield
params:
count: 3
Creature that reacts to cards leaving the graveyard
card_id: graveyard_watcher_01
name: Graveyard Watcher
description: Whenever a card leaves your graveyard, draw a card.
type: creature
mana:
any: 3
defense: 3
attack: 2
defense: 1
abilities:
- name: Soul Drain
description: Draw a card.
ability_type: triggered
mana_requirement: {}
alterations:
- effect: draw_card
target: owner
start_trigger: on_card_leave_graveyard
params:
count: 1
Spell
card_id: fireball_01
name: Fireball
description: Deal 3 damage to any target.
type: spell
mana:
fire: 2
abilities:
- name: Explosion
description: Deal 3 damage.
ability_type: activated
mana_requirement: {}
alterations:
- effect: damage_card
params:
amount: 3
Creature that generates mana
card_id: dark_ritualist_01
name: Dark Ritualist
description: When this enters, gain 2 fire mana until end of turn.
type: creature
mana:
any: 2
defense: 2
attack: 1
defense: 0
abilities:
- name: Dark Ritual
description: Gain 2 fire mana this turn.
ability_type: triggered
mana_requirement: {}
alterations:
- effect: gain_mana
target: owner
start_trigger: on_card_enter
params:
amounts:
fire: 2
Creature that sets a creature's stats
card_id: polymorphist_01
name: Polymorphist
description: When this enters, set another creature's stats to 1/1.
type: creature
mana:
any: 3
defense: 2
attack: 2
defense: 1
abilities:
- name: Polymorph
description: Set target creature to 1/1.
ability_type: triggered
mana_requirement: {}
alterations:
- effect: set_stats
target: null
start_trigger: on_card_enter
params:
attack: 1
defense: 0
Creature that discards from opponent's hand
card_id: mind_ripper_01
name: Mind Ripper
description: When this enters, your opponent discards a card at random.
type: creature
mana:
any: 3
defense: 3
attack: 2
defense: 1
abilities:
- name: Mind Rip
description: Opponent discards a card at random.
ability_type: triggered
mana_requirement: {}
alterations:
- effect: random_discard
target: opponent
start_trigger: on_card_enter
params:
count: 1
Spell that recycles the graveyard
card_id: recycle_01
name: Recycle
description: Shuffle your graveyard into your deck.
type: spell
mana:
any: 2
abilities:
- name: Recycle
description: Shuffle graveyard into deck.
ability_type: activated
mana_requirement: {}
alterations:
- effect: recycle_graveyard
target: owner
Mana card
card_id: fire_mana_01
name: Fire Mana
description: Provides 1 fire mana.
type: mana
mana:
fire: 1
Deck YAML
# decks/midrange.yaml
cards:
goblin_01: 3
giant_01: 2
fireball_01: 2
pyromancer_01: 2
fire_mana_01: 5
earth_mana_01: 6
Creature with group and conditional trigger
card_id: witch_coven_01
name: Witch Coven
type: creature
group: witch
mana:
any: 3
defense: 3
attack: 2
abilities:
- name: Sisterhood
description: When this enters, if you control another Witch, gain +1/+1.
ability_type: triggered
mana_requirement: {}
alterations:
- effect: buff_card
target: self
start_trigger: on_card_enter_battlefield
require_group: witch
require_in_zone: battlefield
require_zone_owner: owner
params:
attack_increase: 1
defense_increase: 1
Spell that destroys all enemies of a group
card_id: witch_hunt_01
name: Witch Hunt
description: Destroy all enemy Witches.
type: spell
mana:
fire: 3
abilities:
- name: Purge
description: Destroy all enemy Witches.
ability_type: activated
mana_requirement: {}
alterations:
- effect: destroy_card
target: opponent
group: witch
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file ptg-0.6.1.tar.gz.
File metadata
- Download URL: ptg-0.6.1.tar.gz
- Upload date:
- Size: 77.0 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.13.11
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
830937a03b000758466327f8260a6be8b97f6a8e79d2e79515cb884c56e46d24
|
|
| MD5 |
e6a71c08aac321be4a53e71c5f024e09
|
|
| BLAKE2b-256 |
e8df2bea0b287b4c210bf86e671ba99b024e40e6d241599c604904d5f9caaf9c
|
File details
Details for the file ptg-0.6.1-py3-none-any.whl.
File metadata
- Download URL: ptg-0.6.1-py3-none-any.whl
- Upload date:
- Size: 60.0 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.13.11
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
87c9eeac7b4f9f6598d57e72d011b621c22870bb03f878994550c621a1ca58e2
|
|
| MD5 |
59bab16004ab901772b243d1d96f1e81
|
|
| BLAKE2b-256 |
a571801737f1cc6f8698e20bb8c82a69d85f659dcdace97f88f393e16bb00334
|