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}