weiss_core/
state.rs

1use crate::db::CardId;
2use crate::effects::{EffectId, EffectPayload, ReplacementSpec};
3use crate::util::Rng64;
4use serde::{Deserialize, Serialize};
5use std::collections::VecDeque;
6
7pub type CardInstanceId = u32;
8
9#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
10pub struct CardInstance {
11    pub id: CardId,
12    pub instance_id: CardInstanceId,
13    pub owner: u8,
14    pub controller: u8,
15}
16
17pub const REVEAL_HISTORY_LEN: usize = 8;
18
19#[derive(Clone, Debug, Hash, Serialize, Deserialize)]
20pub struct RevealHistory {
21    entries: [CardId; REVEAL_HISTORY_LEN],
22    len: u8,
23    head: u8,
24}
25
26impl RevealHistory {
27    pub fn new() -> Self {
28        Self {
29            entries: [0; REVEAL_HISTORY_LEN],
30            len: 0,
31            head: 0,
32        }
33    }
34
35    pub fn push(&mut self, card: CardId) {
36        if REVEAL_HISTORY_LEN == 0 {
37            return;
38        }
39        let head = self.head as usize;
40        self.entries[head] = card;
41        if (self.len as usize) < REVEAL_HISTORY_LEN {
42            self.len = self.len.saturating_add(1);
43        }
44        self.head = ((head + 1) % REVEAL_HISTORY_LEN) as u8;
45    }
46
47    pub fn write_chronological(&self, out: &mut [i32]) {
48        out.fill(0);
49        let len = self.len as usize;
50        if len == 0 || REVEAL_HISTORY_LEN == 0 {
51            return;
52        }
53        let start = if len < REVEAL_HISTORY_LEN {
54            0
55        } else {
56            self.head as usize
57        };
58        for idx in 0..len.min(out.len()) {
59            let entry_idx = if len < REVEAL_HISTORY_LEN {
60                idx
61            } else {
62                (start + idx) % REVEAL_HISTORY_LEN
63            };
64            out[idx] = self.entries[entry_idx] as i32;
65        }
66    }
67}
68
69impl Default for RevealHistory {
70    fn default() -> Self {
71        Self::new()
72    }
73}
74
75impl CardInstance {
76    pub fn new(id: CardId, owner: u8, instance_id: CardInstanceId) -> Self {
77        Self {
78            id,
79            instance_id,
80            owner,
81            controller: owner,
82        }
83    }
84}
85
86#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
87pub enum Phase {
88    Mulligan,
89    Stand,
90    Draw,
91    Clock,
92    Main,
93    Climax,
94    Attack,
95    End,
96}
97
98#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
99pub enum TimingWindow {
100    MainWindow,
101    ClimaxWindow,
102    AttackDeclarationWindow,
103    TriggerResolutionWindow,
104    CounterWindow,
105    DamageResolutionWindow,
106    EncoreWindow,
107    EndPhaseWindow,
108}
109
110#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
111pub enum StageStatus {
112    Stand,
113    Rest,
114    Reverse,
115}
116
117#[derive(Clone, Debug, Hash, Serialize, Deserialize)]
118pub struct StageSlot {
119    pub card: Option<CardInstance>,
120    pub status: StageStatus,
121    pub power_mod_battle: i32,
122    pub power_mod_turn: i32,
123    pub has_attacked: bool,
124    pub cannot_attack: bool,
125    pub attack_cost: u8,
126}
127
128impl StageSlot {
129    pub fn empty() -> Self {
130        Self {
131            card: None,
132            status: StageStatus::Stand,
133            power_mod_battle: 0,
134            power_mod_turn: 0,
135            has_attacked: false,
136            cannot_attack: false,
137            attack_cost: 0,
138        }
139    }
140
141    pub fn is_empty(&self) -> bool {
142        self.card.is_none()
143    }
144}
145
146#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
147pub enum AttackType {
148    Frontal,
149    Side,
150    Direct,
151}
152
153#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
154pub enum AttackStep {
155    Trigger,
156    Counter,
157    Damage,
158    Battle,
159    Encore,
160}
161
162#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
163pub enum DamageType {
164    Battle,
165    Effect,
166}
167
168#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
169pub enum DamageModifierKind {
170    AddAmount { delta: i32 },
171    SetCancelable { cancelable: bool },
172    CancelNext,
173    SetAmount { amount: i32 },
174}
175
176#[derive(Clone, Debug, Hash, Serialize, Deserialize)]
177pub struct DamageModifier {
178    pub kind: DamageModifierKind,
179    pub priority: i16,
180    pub insertion: u32,
181    pub source_id: u32,
182    pub remaining: i32,
183    pub used: bool,
184}
185
186#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
187pub enum TriggerEffect {
188    Soul,
189    Draw,
190    Shot,
191    Bounce,
192    Treasure,
193    Gate,
194    Standby,
195    AutoAbility { ability_index: u8 },
196}
197
198#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
199pub enum TargetZone {
200    Stage,
201    Hand,
202    DeckTop,
203    Clock,
204    Level,
205    Stock,
206    Memory,
207    WaitingRoom,
208    Climax,
209    Resolution,
210}
211
212#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
213pub enum TargetSide {
214    SelfSide,
215    Opponent,
216}
217
218#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
219pub enum TargetSlotFilter {
220    Any,
221    FrontRow,
222    BackRow,
223    SpecificSlot(u8),
224}
225
226#[derive(Clone, Debug, Hash, Serialize, Deserialize)]
227pub struct TargetSpec {
228    pub zone: TargetZone,
229    pub side: TargetSide,
230    pub slot_filter: TargetSlotFilter,
231    pub card_type: Option<crate::db::CardType>,
232    #[serde(default)]
233    pub card_trait: Option<u16>,
234    #[serde(default)]
235    pub level_max: Option<u8>,
236    #[serde(default)]
237    pub cost_max: Option<u8>,
238    pub count: u8,
239    #[serde(default)]
240    pub limit: Option<u8>,
241    #[serde(default)]
242    pub source_only: bool,
243    #[serde(default)]
244    pub reveal_to_controller: bool,
245}
246
247#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
248pub struct TargetRef {
249    pub player: u8,
250    pub zone: TargetZone,
251    pub index: u8,
252    pub card_id: CardId,
253    pub instance_id: CardInstanceId,
254}
255
256#[derive(Clone, Debug, Hash, Serialize, Deserialize)]
257pub enum PendingTargetEffect {
258    EffectPending {
259        instance_id: u32,
260        payload: EffectPayload,
261    },
262}
263
264#[derive(Clone, Debug, Hash, Serialize, Deserialize)]
265pub struct TargetSelectionState {
266    pub controller: u8,
267    pub source_id: CardId,
268    pub spec: TargetSpec,
269    pub remaining: u8,
270    pub selected: Vec<TargetRef>,
271    #[serde(default)]
272    pub candidates: Vec<TargetRef>,
273    pub effect: PendingTargetEffect,
274    pub allow_skip: bool,
275}
276
277#[derive(Clone, Debug, Hash, Serialize, Deserialize)]
278pub struct StackItem {
279    pub id: u32,
280    pub controller: u8,
281    pub source_id: CardId,
282    pub effect_id: EffectId,
283    pub payload: EffectPayload,
284}
285
286#[derive(Clone, Debug, Hash, Serialize, Deserialize)]
287pub struct PriorityState {
288    pub holder: u8,
289    pub passes: u8,
290    pub window: TimingWindow,
291    pub used_act_mask: u32,
292}
293
294#[derive(Clone, Debug, Hash, Serialize, Deserialize)]
295pub struct StackOrderState {
296    pub group_id: u32,
297    pub controller: u8,
298    pub items: Vec<StackItem>,
299}
300
301#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
302pub enum ChoiceReason {
303    TriggerStandbySelect,
304    TriggerTreasureSelect,
305    StackOrderSelect,
306    PriorityActionSelect,
307    CostPayment,
308    TargetSelect,
309    EndPhaseDiscard,
310}
311
312#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
313pub enum CostStepKind {
314    RestOther,
315    DiscardFromHand,
316    ClockFromHand,
317    ClockFromDeckTop,
318    RevealFromHand,
319}
320
321#[derive(Clone, Debug, Hash, Serialize, Deserialize)]
322pub struct CostPaymentState {
323    pub controller: u8,
324    pub source_id: CardId,
325    pub source_instance_id: CardInstanceId,
326    pub source_slot: Option<u8>,
327    pub ability_index: u8,
328    pub remaining: crate::db::AbilityCost,
329    pub current_step: Option<CostStepKind>,
330}
331
332#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
333pub enum ChoiceZone {
334    WaitingRoom,
335    Stage,
336    Hand,
337    DeckTop,
338    Clock,
339    Level,
340    Stock,
341    Memory,
342    Climax,
343    Resolution,
344    Stack,
345    PriorityCounter,
346    PriorityAct,
347    PriorityPass,
348    Skip,
349}
350
351#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
352pub struct ChoiceOptionRef {
353    pub card_id: CardId,
354    pub instance_id: CardInstanceId,
355    pub zone: ChoiceZone,
356    pub index: Option<u8>,
357    pub target_slot: Option<u8>,
358}
359
360#[derive(Clone, Debug, Hash, Serialize, Deserialize)]
361pub struct ChoiceState {
362    pub id: u32,
363    pub reason: ChoiceReason,
364    pub player: u8,
365    pub options: Vec<ChoiceOptionRef>,
366    pub total_candidates: u16,
367    pub page_start: u16,
368    pub pending_trigger: Option<PendingTrigger>,
369}
370
371#[derive(Clone, Debug, Hash, Serialize, Deserialize)]
372pub struct AttackContext {
373    pub attacker_slot: u8,
374    pub defender_slot: Option<u8>,
375    pub attack_type: AttackType,
376    pub trigger_card: Option<CardId>,
377    pub trigger_instance_id: Option<CardInstanceId>,
378    pub damage: i32,
379    pub counter_allowed: bool,
380    pub counter_played: bool,
381    pub counter_power: i32,
382    pub damage_modifiers: Vec<DamageModifier>,
383    pub next_modifier_id: u32,
384    pub last_damage_event_id: Option<u32>,
385    pub auto_damage_enqueued: bool,
386    pub battle_damage_applied: bool,
387    pub step: AttackStep,
388    pub decl_window_done: bool,
389    pub trigger_window_done: bool,
390    pub damage_window_done: bool,
391}
392
393#[derive(Clone, Debug, Hash, Serialize, Deserialize)]
394pub struct PendingTrigger {
395    pub id: u32,
396    pub group_id: u32,
397    pub player: u8,
398    pub source_card: CardId,
399    pub effect: TriggerEffect,
400    pub effect_id: Option<EffectId>,
401}
402
403#[derive(Clone, Debug, Hash, Serialize, Deserialize)]
404pub struct TriggerOrderState {
405    pub group_id: u32,
406    pub player: u8,
407    pub choices: Vec<u32>,
408}
409
410#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
411pub struct DerivedAttackSlot {
412    pub cannot_attack: bool,
413    pub attack_cost: u8,
414}
415
416impl DerivedAttackSlot {
417    pub fn empty() -> Self {
418        Self {
419            cannot_attack: false,
420            attack_cost: 0,
421        }
422    }
423}
424
425#[derive(Clone, Debug, Hash, Serialize, Deserialize)]
426pub struct DerivedAttackState {
427    pub per_player: [[DerivedAttackSlot; 5]; 2],
428}
429
430impl DerivedAttackState {
431    pub fn new() -> Self {
432        Self {
433            per_player: [[DerivedAttackSlot::empty(); 5]; 2],
434        }
435    }
436}
437
438impl Default for DerivedAttackState {
439    fn default() -> Self {
440        Self::new()
441    }
442}
443
444#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
445pub struct EncoreRequest {
446    pub player: u8,
447    pub slot: u8,
448}
449
450#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
451pub enum TerminalResult {
452    Win { winner: u8 },
453    Draw,
454    Timeout,
455}
456
457#[derive(Clone, Debug, Hash)]
458pub struct PlayerState {
459    pub deck: Vec<CardInstance>,
460    pub hand: Vec<CardInstance>,
461    pub waiting_room: Vec<CardInstance>,
462    pub clock: Vec<CardInstance>,
463    pub level: Vec<CardInstance>,
464    pub stock: Vec<CardInstance>,
465    pub memory: Vec<CardInstance>,
466    pub climax: Vec<CardInstance>,
467    pub resolution: Vec<CardInstance>,
468    pub stage: [StageSlot; 5],
469}
470
471impl PlayerState {
472    pub fn new(deck: Vec<CardInstance>) -> Self {
473        Self {
474            deck,
475            hand: Vec::new(),
476            waiting_room: Vec::new(),
477            clock: Vec::new(),
478            level: Vec::new(),
479            stock: Vec::new(),
480            memory: Vec::new(),
481            climax: Vec::new(),
482            resolution: Vec::new(),
483            stage: [
484                StageSlot::empty(),
485                StageSlot::empty(),
486                StageSlot::empty(),
487                StageSlot::empty(),
488                StageSlot::empty(),
489            ],
490        }
491    }
492}
493
494#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
495pub enum ModifierKind {
496    Power,
497    AttackCost,
498    CannotAttack,
499}
500
501#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
502pub enum ModifierDuration {
503    UntilEndOfTurn,
504    WhileOnStage,
505}
506
507#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash, Serialize, Deserialize)]
508pub enum ModifierLayer {
509    Continuous,
510    #[default]
511    Effect,
512}
513
514#[derive(Clone, Debug, Hash, Serialize, Deserialize)]
515pub struct ModifierInstance {
516    pub id: u32,
517    pub source: CardId,
518    #[serde(default)]
519    pub source_slot: Option<u8>,
520    pub target_player: u8,
521    pub target_slot: u8,
522    pub target_card: CardId,
523    pub kind: ModifierKind,
524    pub magnitude: i32,
525    pub duration: ModifierDuration,
526    #[serde(default)]
527    pub layer: ModifierLayer,
528    pub insertion: u32,
529}
530
531#[derive(Clone, Debug, Hash)]
532pub struct TurnState {
533    pub active_player: u8,
534    pub starting_player: u8,
535    pub turn_number: u32,
536    pub phase: Phase,
537    pub mulligan_done: [bool; 2],
538    pub mulligan_selected: [u64; 2],
539    pub main_passed: bool,
540    pub decision_count: u32,
541    pub tick_count: u32,
542    pub attack: Option<AttackContext>,
543    pub attack_subphase_count: u8,
544    pub pending_level_up: Option<u8>,
545    pub encore_queue: Vec<EncoreRequest>,
546    pub encore_step_player: Option<u8>,
547    pub pending_triggers: Vec<PendingTrigger>,
548    pub pending_triggers_sorted: bool,
549    pub active_window: Option<TimingWindow>,
550    pub end_phase_window_done: bool,
551    pub end_phase_discard_done: bool,
552    pub end_phase_climax_done: bool,
553    pub end_phase_cleanup_done: bool,
554    pub encore_window_done: bool,
555    pub pending_losses: [bool; 2],
556    pub damage_resolution_target: Option<u8>,
557    pub cost_payment_depth: u8,
558    pub pending_resolution_cleanup: Vec<(u8, CardInstanceId)>,
559    pub phase_step: u8,
560    pub attack_phase_begin_done: bool,
561    pub attack_decl_check_done: bool,
562    pub encore_begin_done: bool,
563    pub trigger_order: Option<TriggerOrderState>,
564    pub choice: Option<ChoiceState>,
565    pub target_selection: Option<TargetSelectionState>,
566    pub pending_cost: Option<CostPaymentState>,
567    pub priority: Option<PriorityState>,
568    pub stack: Vec<StackItem>,
569    pub pending_stack_groups: VecDeque<StackOrderState>,
570    pub stack_order: Option<StackOrderState>,
571    pub derived_attack: Option<DerivedAttackState>,
572    pub next_trigger_id: u32,
573    pub next_trigger_group_id: u32,
574    pub next_choice_id: u32,
575    pub next_stack_group_id: u32,
576    pub next_damage_event_id: u32,
577    pub next_effect_instance_id: u32,
578    pub end_phase_pending: bool,
579}
580
581#[derive(Clone, Debug, Hash)]
582pub struct GameState {
583    pub players: [PlayerState; 2],
584    pub reveal_history: [RevealHistory; 2],
585    pub turn: TurnState,
586    pub rng: Rng64,
587    pub modifiers: Vec<ModifierInstance>,
588    pub next_modifier_id: u32,
589    pub replacements: Vec<ReplacementSpec>,
590    pub next_replacement_insertion: u32,
591    pub terminal: Option<TerminalResult>,
592}
593
594impl GameState {
595    pub fn new(deck_a: Vec<CardId>, deck_b: Vec<CardId>, seed: u64, starting_player: u8) -> Self {
596        assert!(
597            deck_a.len() == crate::encode::MAX_DECK,
598            "Deck A must contain exactly {} cards",
599            crate::encode::MAX_DECK
600        );
601        assert!(
602            deck_b.len() == crate::encode::MAX_DECK,
603            "Deck B must contain exactly {} cards",
604            crate::encode::MAX_DECK
605        );
606        let rng = Rng64::new(seed);
607        let mut next_instance_id: CardInstanceId = 1;
608        let deck_a = Self::build_deck(deck_a, 0, &mut next_instance_id);
609        let deck_b = Self::build_deck(deck_b, 1, &mut next_instance_id);
610        Self {
611            players: [PlayerState::new(deck_a), PlayerState::new(deck_b)],
612            reveal_history: [RevealHistory::new(), RevealHistory::new()],
613            turn: TurnState {
614                active_player: starting_player,
615                starting_player,
616                turn_number: 0,
617                phase: Phase::Mulligan,
618                mulligan_done: [false; 2],
619                mulligan_selected: [0; 2],
620                main_passed: false,
621                decision_count: 0,
622                tick_count: 0,
623                attack: None,
624                attack_subphase_count: 0,
625                pending_level_up: None,
626                encore_queue: Vec::new(),
627                encore_step_player: None,
628                pending_triggers: Vec::new(),
629                pending_triggers_sorted: true,
630                trigger_order: None,
631                choice: None,
632                target_selection: None,
633                pending_cost: None,
634                priority: None,
635                stack: Vec::new(),
636                pending_stack_groups: VecDeque::new(),
637                stack_order: None,
638                derived_attack: None,
639                next_trigger_id: 1,
640                next_trigger_group_id: 1,
641                next_choice_id: 1,
642                next_stack_group_id: 1,
643                next_damage_event_id: 1,
644                next_effect_instance_id: 1,
645                active_window: None,
646                end_phase_window_done: false,
647                end_phase_discard_done: false,
648                end_phase_climax_done: false,
649                end_phase_cleanup_done: false,
650                encore_window_done: false,
651                pending_losses: [false; 2],
652                damage_resolution_target: None,
653                cost_payment_depth: 0,
654                pending_resolution_cleanup: Vec::new(),
655                phase_step: 0,
656                attack_phase_begin_done: false,
657                attack_decl_check_done: false,
658                encore_begin_done: false,
659                end_phase_pending: false,
660            },
661            rng,
662            modifiers: Vec::new(),
663            next_modifier_id: 1,
664            replacements: Vec::new(),
665            next_replacement_insertion: 1,
666            terminal: None,
667        }
668    }
669
670    fn build_deck(
671        deck: Vec<CardId>,
672        owner: u8,
673        next_instance_id: &mut CardInstanceId,
674    ) -> Vec<CardInstance> {
675        deck.into_iter()
676            .map(|id| {
677                let instance_id = *next_instance_id;
678                *next_instance_id = next_instance_id.wrapping_add(1);
679                CardInstance::new(id, owner, instance_id)
680            })
681            .collect()
682    }
683}