Skip to main content

manabrew_protocol/game/
mod.rs

1use std::collections::{BTreeMap, HashMap};
2
3use serde::{Deserialize, Serialize};
4use ts_rs::TS;
5
6use crate::prompts::common::TargetRef;
7
8#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize, TS)]
9#[ts(export, export_to = "game/index.ts")]
10pub enum ManaColor {
11    #[serde(rename = "W")]
12    White,
13    #[serde(rename = "U")]
14    Blue,
15    #[serde(rename = "B")]
16    Black,
17    #[serde(rename = "R")]
18    Red,
19    #[serde(rename = "G")]
20    Green,
21    #[serde(rename = "C")]
22    Colorless,
23}
24
25#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, TS)]
26#[serde(rename_all = "camelCase")]
27#[ts(export, export_to = "game/index.ts")]
28pub struct Mana {
29    pub color: ManaColor,
30    pub amount: i32,
31}
32
33#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize, TS)]
34#[serde(rename_all = "camelCase")]
35#[ts(export, export_to = "game/index.ts")]
36pub enum PlayerCounterKind {
37    Poison,
38    Energy,
39    Experience,
40    Radiation,
41    Ticket,
42}
43
44#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, TS)]
45#[serde(rename_all = "camelCase")]
46#[ts(export, export_to = "game/index.ts")]
47pub enum ZoneKind {
48    Battlefield,
49    Hand,
50    Library,
51    Graveyard,
52    Exile,
53    Command,
54}
55
56#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default, Serialize, Deserialize, TS)]
57#[serde(rename_all = "camelCase")]
58#[ts(export, export_to = "game/index.ts")]
59pub enum StepKind {
60    #[default]
61    Untap,
62    Upkeep,
63    Draw,
64    Main1,
65    CombatBegin,
66    CombatDeclareAttackers,
67    CombatDeclareBlockers,
68    CombatFirstStrikeDamage,
69    CombatDamage,
70    CombatEnd,
71    Main2,
72    EndOfTurn,
73    Cleanup,
74}
75
76#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default, Serialize, Deserialize, TS)]
77#[serde(rename_all = "camelCase")]
78#[ts(export, export_to = "game/index.ts")]
79pub enum DayTime {
80    #[default]
81    Neither,
82    Day,
83    Night,
84}
85
86#[derive(Debug, Clone, Serialize, Deserialize, TS)]
87#[serde(
88    tag = "visibility",
89    rename_all = "camelCase",
90    rename_all_fields = "camelCase"
91)]
92#[ts(export, export_to = "game/index.ts")]
93pub enum CardView {
94    Visible(CardDto),
95    Hidden { id: String },
96}
97
98// One entry per (zone, owner) pair; battlefield cards are bucketed by controller.
99#[derive(Debug, Clone, Serialize, Deserialize, TS)]
100#[serde(rename_all = "camelCase")]
101#[ts(export, export_to = "game/index.ts")]
102pub struct ZoneDto {
103    pub zone: ZoneKind,
104    pub owner_id: String,
105    // Ordered top-first where order is public knowledge
106    // count can be > cards.len if hidden cards are present (library)
107    pub cards: Vec<CardView>,
108    pub count: usize,
109}
110
111#[derive(Debug, Clone, Default, Serialize, Deserialize, TS)]
112#[serde(rename_all = "camelCase")]
113#[ts(export, export_to = "game/index.ts")]
114pub struct GameViewDto {
115    pub game_id: String,
116    pub turn: u32,
117    pub step: StepKind,
118    pub combat_assignments: Vec<CombatAssignmentDto>,
119    pub active_player_id: String,
120    pub priority_player_id: String,
121    pub players: Vec<PlayerDto>,
122    pub zones: Vec<ZoneDto>,
123    pub stack: Vec<StackObjectDto>,
124    pub game_over: bool,
125    pub winner_id: Option<String>,
126    pub monarch_id: Option<String>,
127    pub initiative_holder_id: Option<String>,
128    pub day_time: DayTime,
129}
130
131#[derive(Debug, Clone, Serialize, Deserialize, TS)]
132#[serde(rename_all = "camelCase")]
133#[ts(export, export_to = "game/index.ts")]
134pub struct CombatAssignmentDto {
135    pub blocker_id: String,
136    pub attacker_id: String,
137}
138
139#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize, TS)]
140#[serde(rename_all = "camelCase")]
141#[ts(export, export_to = "game/index.ts")]
142pub enum PlayerStatus {
143    #[default]
144    Playing,
145    Lost,
146    Conceded,
147}
148
149#[derive(Debug, Clone, Serialize, Deserialize, TS)]
150#[serde(rename_all = "camelCase")]
151#[ts(export, export_to = "game/index.ts")]
152pub struct PlayerDto {
153    pub id: String,
154    pub name: String,
155    pub status: PlayerStatus,
156    pub is_human: bool,
157    pub life: i32,
158    pub counters: BTreeMap<PlayerCounterKind, u32>,
159    pub mana_pool: BTreeMap<ManaColor, u32>,
160    #[ts(type = "Record<string, number>")]
161    pub commander_damage: HashMap<String, i32>,
162    pub has_city_blessing: bool,
163    pub ring_level: i32,
164    pub speed: i32,
165}
166
167#[derive(Debug, Clone, Default, Serialize, Deserialize, TS)]
168#[serde(rename_all = "camelCase", default)]
169#[ts(export, export_to = "game/index.ts")]
170pub struct CardIdentity {
171    pub name: String,
172    pub set_code: String,
173    pub card_number: String,
174    pub is_token: bool,
175}
176
177#[derive(Debug, Clone, Default, Serialize, Deserialize, TS)]
178#[serde(rename_all = "camelCase", default)]
179#[ts(export, export_to = "game/index.ts")]
180pub struct CardDto {
181    pub id: String,
182    pub identity: CardIdentity,
183    pub color: String,
184    pub mana_cost: String,
185    pub cmc: i32,
186    pub types: Vec<String>,
187    pub subtypes: Vec<String>,
188    pub supertypes: Vec<String>,
189    pub power: Option<String>,
190    pub toughness: Option<String>,
191    #[serde(skip_serializing_if = "Option::is_none")]
192    #[ts(optional)]
193    pub base_power: Option<i32>,
194    #[serde(skip_serializing_if = "Option::is_none")]
195    #[ts(optional)]
196    pub base_toughness: Option<i32>,
197    pub text: String,
198    pub controller_id: String,
199    pub owner_id: String,
200    pub tapped: bool,
201    #[serde(default, skip_serializing_if = "std::ops::Not::not")]
202    pub is_crewed: bool,
203    #[serde(default, skip_serializing_if = "std::ops::Not::not")]
204    pub is_attacking: bool,
205    #[serde(default, skip_serializing_if = "Option::is_none")]
206    #[ts(optional)]
207    pub attacking_player_id: Option<String>,
208    #[serde(default, skip_serializing_if = "Option::is_none")]
209    #[ts(optional)]
210    pub attack_target_id: Option<String>,
211    pub keywords: Vec<String>,
212    // Keyed by the engine's canonical `CounterType` display form ("P1P1",
213    // "Loyalty", one-off counter names uppercase); both producers must match it.
214    #[ts(type = "Record<string, number>")]
215    pub counters: BTreeMap<String, u32>,
216    pub damage: i32,
217    pub summoning_sick: bool,
218    #[serde(default, skip_serializing_if = "std::ops::Not::not")]
219    pub is_copy: bool,
220    pub is_double_faced: bool,
221    pub is_transformed: bool,
222    pub is_face_down: bool,
223    pub is_bestowed: bool,
224    pub phased_out: bool,
225    pub exerted: bool,
226    #[serde(default, skip_serializing_if = "std::ops::Not::not")]
227    pub is_ring_bearer: bool,
228    #[serde(skip_serializing_if = "Option::is_none")]
229    #[ts(optional)]
230    pub attached_to: Option<String>,
231    #[serde(default, skip_serializing_if = "Vec::is_empty")]
232    pub attachment_ids: Vec<String>,
233    // Mutate pile: the card ids merged under this top card.
234    #[serde(default, skip_serializing_if = "Vec::is_empty")]
235    pub merged_card_ids: Vec<String>,
236    #[serde(skip_serializing_if = "Option::is_none")]
237    #[ts(optional)]
238    pub flashback_cost: Option<String>,
239    #[serde(skip_serializing_if = "Option::is_none")]
240    #[ts(optional)]
241    pub kicker_cost: Option<String>,
242    #[serde(skip_serializing_if = "Option::is_none")]
243    #[ts(optional)]
244    pub effective_mana_cost: Option<String>,
245    #[serde(skip_serializing_if = "Option::is_none")]
246    #[ts(optional)]
247    pub madness_cost: Option<String>,
248    #[serde(default, skip_serializing_if = "std::ops::Not::not")]
249    pub is_madness_exiled: bool,
250    #[serde(default, skip_serializing_if = "std::ops::Not::not")]
251    pub is_plotted: bool,
252    #[serde(default, skip_serializing_if = "std::ops::Not::not")]
253    pub is_warp_exiled: bool,
254    #[serde(default, skip_serializing_if = "std::ops::Not::not")]
255    pub foil: bool,
256    #[serde(default, skip_serializing_if = "std::ops::Not::not")]
257    pub would_die_in_combat: bool,
258}
259
260#[derive(Debug, Clone, Default, Serialize, Deserialize, TS)]
261#[serde(rename_all = "camelCase", default)]
262#[ts(export, export_to = "game/index.ts")]
263pub struct StackObjectDto {
264    pub id: String,
265    pub source_id: String,
266    pub controller_id: String,
267    pub identity: CardIdentity,
268    pub text: String,
269    pub is_permanent_spell: bool,
270    pub is_casting: bool,
271    pub targets: Vec<TargetRef>,
272}
273
274#[derive(
275    Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize, TS, strum_macros::Display,
276)]
277#[serde(rename_all = "camelCase")]
278#[ts(export, export_to = "game/index.ts")]
279pub enum TargetingIntent {
280    #[default]
281    Damage,
282    Destroy,
283    Sacrifice,
284    Exile,
285    Bounce,
286    Mill,
287    Discard,
288    Counter,
289    Tap,
290    Untap,
291    Copy,
292    Buff,
293    Debuff,
294    Heal,
295    LoseLife,
296    Reveal,
297    Draw,
298    GainControl,
299    Fight,
300    Attach,
301    Attack,
302    Block,
303    Hostile,
304    Friendly,
305}
306
307#[derive(Debug, Clone, Default, Serialize, Deserialize, TS)]
308#[serde(rename_all = "camelCase")]
309#[ts(export, export_to = "game/index.ts")]
310pub struct PlaymatSettings {
311    #[serde(default, skip_serializing_if = "Option::is_none")]
312    #[ts(optional)]
313    pub opacity: Option<f32>,
314    #[serde(default, skip_serializing_if = "Option::is_none")]
315    #[ts(optional)]
316    pub texture: Option<f32>,
317    #[serde(default, skip_serializing_if = "Option::is_none")]
318    #[ts(optional)]
319    pub border_width: Option<f32>,
320    #[serde(default, skip_serializing_if = "Option::is_none")]
321    #[ts(optional)]
322    pub border_color: Option<String>,
323    #[serde(default, skip_serializing_if = "Option::is_none")]
324    #[ts(optional)]
325    pub fit: Option<String>,
326    #[serde(default, skip_serializing_if = "Option::is_none")]
327    #[ts(optional)]
328    pub offset_x: Option<f32>,
329    #[serde(default, skip_serializing_if = "Option::is_none")]
330    #[ts(optional)]
331    pub offset_y: Option<f32>,
332    #[serde(default, skip_serializing_if = "Option::is_none")]
333    #[ts(optional)]
334    pub zoom: Option<f32>,
335    #[serde(default, skip_serializing_if = "Option::is_none")]
336    #[ts(optional)]
337    pub blur: Option<f32>,
338    #[serde(default, skip_serializing_if = "Option::is_none")]
339    #[ts(optional)]
340    pub brightness: Option<f32>,
341    #[serde(default, skip_serializing_if = "Option::is_none")]
342    #[ts(optional)]
343    pub color: Option<String>,
344}