Skip to main content

manabrew_protocol/game/
mod.rs

1use std::collections::HashMap;
2
3use serde::{Deserialize, Serialize};
4use ts_rs::TS;
5
6use crate::prompts::common::TargetRef;
7
8#[derive(Debug, Clone, Default, Serialize, Deserialize, TS)]
9#[serde(rename_all = "camelCase")]
10#[ts(export, export_to = "game/index.ts")]
11pub struct GameViewDto {
12    pub game_id: String,
13    pub turn: u32,
14    pub step: String,
15    pub combat_assignments: Vec<CombatAssignmentDto>,
16    pub active_player_id: String,
17    pub priority_player_id: String,
18    pub players: Vec<PlayerDto>,
19    pub battlefield: Vec<CardDto>,
20    pub stack: Vec<StackObjectDto>,
21    pub game_over: bool,
22    pub winner_id: Option<String>,
23    pub monarch_id: Option<String>,
24    pub initiative_holder_id: Option<String>,
25}
26
27impl GameViewDto {
28    pub fn empty(game_id: String) -> Self {
29        Self {
30            game_id,
31            step: "main1".into(),
32            ..Default::default()
33        }
34    }
35
36    pub fn player(&self, id: &str) -> Option<&PlayerDto> {
37        self.players.iter().find(|p| p.id == id)
38    }
39
40    pub fn all_zone_cards(&self) -> impl Iterator<Item = &CardDto> {
41        self.battlefield
42            .iter()
43            .chain(self.players.iter().flat_map(|p| {
44                p.hand
45                    .iter()
46                    .chain(p.graveyard.iter())
47                    .chain(p.exile.iter())
48                    .chain(p.command_zone.iter())
49            }))
50    }
51}
52
53#[derive(Debug, Clone, Serialize, Deserialize, TS)]
54#[serde(rename_all = "camelCase")]
55#[ts(export, export_to = "game/index.ts")]
56pub struct CombatAssignmentDto {
57    pub blocker_id: String,
58    pub attacker_id: String,
59}
60
61#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize, TS)]
62#[serde(rename_all = "camelCase")]
63#[ts(export, export_to = "game/index.ts")]
64pub enum PlayerStatus {
65    #[default]
66    Playing,
67    Lost,
68    Conceded,
69}
70
71#[derive(Debug, Clone, Serialize, Deserialize, TS)]
72#[serde(rename_all = "camelCase")]
73#[ts(export, export_to = "game/index.ts")]
74pub struct PlayerDto {
75    pub id: String,
76    pub name: String,
77    pub status: PlayerStatus,
78    pub is_human: bool,
79    pub life: i32,
80    pub poison: i32,
81    pub hand: Vec<CardDto>,
82    pub graveyard: Vec<CardDto>,
83    pub exile: Vec<CardDto>,
84    pub command_zone: Vec<CardDto>,
85    pub library_count: usize,
86    #[ts(type = "Record<string, number>")]
87    pub mana_pool: HashMap<String, i32>,
88    #[ts(type = "Record<string, number>")]
89    pub commander_damage: HashMap<String, i32>,
90    pub energy_counters: i32,
91    pub radiation_counters: i32,
92    pub has_city_blessing: bool,
93    pub ring_level: i32,
94    pub speed: i32,
95    pub experience_counters: i32,
96    pub ticket_counters: i32,
97}
98
99#[derive(Debug, Clone, Default, Serialize, Deserialize, TS)]
100#[serde(rename_all = "camelCase", default)]
101#[ts(export, export_to = "game/index.ts")]
102pub struct CardIdentity {
103    pub name: String,
104    pub set_code: String,
105    pub card_number: String,
106    pub is_token: bool,
107}
108
109#[derive(Debug, Clone, Default, Serialize, Deserialize, TS)]
110#[serde(rename_all = "camelCase", default)]
111#[ts(export, export_to = "game/index.ts")]
112pub struct CardDto {
113    pub id: String,
114    pub identity: CardIdentity,
115    pub color: String,
116    pub mana_cost: String,
117    pub cmc: i32,
118    pub types: Vec<String>,
119    pub subtypes: Vec<String>,
120    pub supertypes: Vec<String>,
121    pub power: Option<String>,
122    pub toughness: Option<String>,
123    #[serde(skip_serializing_if = "Option::is_none")]
124    #[ts(optional)]
125    pub base_power: Option<i32>,
126    #[serde(skip_serializing_if = "Option::is_none")]
127    #[ts(optional)]
128    pub base_toughness: Option<i32>,
129    pub text: String,
130    pub controller_id: String,
131    pub owner_id: String,
132    pub zone_id: String,
133    pub tapped: bool,
134    #[serde(default, skip_serializing_if = "std::ops::Not::not")]
135    pub is_crewed: bool,
136    #[serde(default, skip_serializing_if = "std::ops::Not::not")]
137    pub is_attacking: bool,
138    #[serde(default, skip_serializing_if = "Option::is_none")]
139    #[ts(optional)]
140    pub attacking_player_id: Option<String>,
141    #[serde(default, skip_serializing_if = "Option::is_none")]
142    #[ts(optional)]
143    pub attack_target_id: Option<String>,
144    pub keywords: Vec<String>,
145    #[ts(type = "Record<string, number>")]
146    pub counters: HashMap<String, i32>,
147    pub damage: i32,
148    pub summoning_sick: bool,
149    #[serde(default, skip_serializing_if = "std::ops::Not::not")]
150    pub is_copy: bool,
151    pub is_double_faced: bool,
152    pub is_transformed: bool,
153    pub is_face_down: bool,
154    pub is_bestowed: bool,
155    pub phased_out: bool,
156    pub exerted: bool,
157    #[serde(default, skip_serializing_if = "std::ops::Not::not")]
158    pub is_ring_bearer: bool,
159    #[serde(skip_serializing_if = "Option::is_none")]
160    #[ts(optional)]
161    pub attached_to: Option<String>,
162    #[serde(default, skip_serializing_if = "Vec::is_empty")]
163    pub attachment_ids: Vec<String>,
164    #[serde(skip_serializing_if = "Option::is_none")]
165    #[ts(optional)]
166    pub flashback_cost: Option<String>,
167    #[serde(skip_serializing_if = "Option::is_none")]
168    #[ts(optional)]
169    pub kicker_cost: Option<String>,
170    #[serde(skip_serializing_if = "Option::is_none")]
171    #[ts(optional)]
172    pub effective_mana_cost: Option<String>,
173    #[serde(skip_serializing_if = "Option::is_none")]
174    #[ts(optional)]
175    pub madness_cost: Option<String>,
176    #[serde(default, skip_serializing_if = "std::ops::Not::not")]
177    pub is_madness_exiled: bool,
178    #[serde(default, skip_serializing_if = "std::ops::Not::not")]
179    pub is_plotted: bool,
180    #[serde(default, skip_serializing_if = "std::ops::Not::not")]
181    pub is_warp_exiled: bool,
182    #[serde(default, skip_serializing_if = "std::ops::Not::not")]
183    pub foil: bool,
184    #[serde(default, skip_serializing_if = "std::ops::Not::not")]
185    pub would_die_in_combat: bool,
186}
187
188#[derive(Debug, Clone, Default, Serialize, Deserialize, TS)]
189#[serde(rename_all = "camelCase", default)]
190#[ts(export, export_to = "game/index.ts")]
191pub struct StackObjectDto {
192    pub id: String,
193    pub source_id: String,
194    pub controller_id: String,
195    pub identity: CardIdentity,
196    pub text: String,
197    pub is_permanent_spell: bool,
198    pub is_casting: bool,
199    pub targets: Vec<TargetRef>,
200}
201
202#[derive(
203    Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize, TS, strum_macros::Display,
204)]
205#[serde(rename_all = "camelCase")]
206#[ts(export, export_to = "game/index.ts")]
207pub enum TargetingIntent {
208    #[default]
209    Damage,
210    Destroy,
211    Sacrifice,
212    Exile,
213    Bounce,
214    Mill,
215    Discard,
216    Counter,
217    Tap,
218    Untap,
219    Copy,
220    Buff,
221    Debuff,
222    Heal,
223    LoseLife,
224    Reveal,
225    Draw,
226    GainControl,
227    Fight,
228    Attach,
229    Attack,
230    Block,
231    Hostile,
232    Friendly,
233}
234
235impl TargetingIntent {
236    pub fn prefers_arrow(self) -> bool {
237        matches!(self, TargetingIntent::Attack | TargetingIntent::Block)
238    }
239
240    pub fn is_hostile(self) -> bool {
241        matches!(
242            self,
243            TargetingIntent::Damage
244                | TargetingIntent::Destroy
245                | TargetingIntent::Sacrifice
246                | TargetingIntent::Exile
247                | TargetingIntent::Bounce
248                | TargetingIntent::Mill
249                | TargetingIntent::Discard
250                | TargetingIntent::Counter
251                | TargetingIntent::Tap
252                | TargetingIntent::Debuff
253                | TargetingIntent::LoseLife
254                | TargetingIntent::GainControl
255                | TargetingIntent::Fight
256                | TargetingIntent::Hostile
257        )
258    }
259}
260
261#[derive(Debug, Clone, Default, Serialize, Deserialize, TS)]
262#[serde(rename_all = "camelCase")]
263#[ts(export, export_to = "game/index.ts")]
264pub struct PlaymatSettings {
265    #[serde(default, skip_serializing_if = "Option::is_none")]
266    #[ts(optional)]
267    pub opacity: Option<f32>,
268    #[serde(default, skip_serializing_if = "Option::is_none")]
269    #[ts(optional)]
270    pub texture: Option<f32>,
271    #[serde(default, skip_serializing_if = "Option::is_none")]
272    #[ts(optional)]
273    pub border_width: Option<f32>,
274    #[serde(default, skip_serializing_if = "Option::is_none")]
275    #[ts(optional)]
276    pub border_color: Option<String>,
277    #[serde(default, skip_serializing_if = "Option::is_none")]
278    #[ts(optional)]
279    pub fit: Option<String>,
280    #[serde(default, skip_serializing_if = "Option::is_none")]
281    #[ts(optional)]
282    pub offset_x: Option<f32>,
283    #[serde(default, skip_serializing_if = "Option::is_none")]
284    #[ts(optional)]
285    pub offset_y: Option<f32>,
286    #[serde(default, skip_serializing_if = "Option::is_none")]
287    #[ts(optional)]
288    pub zoom: Option<f32>,
289    #[serde(default, skip_serializing_if = "Option::is_none")]
290    #[ts(optional)]
291    pub blur: Option<f32>,
292    #[serde(default, skip_serializing_if = "Option::is_none")]
293    #[ts(optional)]
294    pub brightness: Option<f32>,
295    #[serde(default, skip_serializing_if = "Option::is_none")]
296    #[ts(optional)]
297    pub color: Option<String>,
298}