weiss_core/encode/
action_ids.rs

1use serde::Serialize;
2
3use crate::legal::ActionDesc;
4use crate::state::AttackType;
5
6use super::constants::*;
7
8/// Parameter value for an action id description.
9#[derive(Clone, Debug, Serialize, PartialEq, Eq)]
10#[serde(untagged)]
11pub enum ActionParamValue {
12    /// Integer parameter.
13    Int(i32),
14    /// String parameter.
15    Str(&'static str),
16}
17
18/// Named parameter for an action id description.
19#[derive(Clone, Debug, Serialize, PartialEq, Eq)]
20pub struct ActionParam {
21    /// Parameter name.
22    pub name: &'static str,
23    /// Parameter value.
24    pub value: ActionParamValue,
25}
26
27/// Human-readable description of an action id.
28#[derive(Clone, Debug, Serialize, PartialEq, Eq)]
29pub struct ActionIdDesc {
30    /// Action family name.
31    pub family: &'static str,
32    /// Parameters associated with the action.
33    pub params: Vec<ActionParam>,
34}
35
36#[derive(Clone, Copy, Debug, PartialEq, Eq)]
37#[repr(u8)]
38enum ActionFamily {
39    MulliganConfirm,
40    MulliganSelect,
41    Pass,
42    ClockFromHand,
43    MainPlayCharacter,
44    MainPlayEvent,
45    MainMove,
46    ClimaxPlay,
47    Attack,
48    LevelUp,
49    EncorePay,
50    EncoreDecline,
51    TriggerOrder,
52    ChoiceSelect,
53    ChoicePrevPage,
54    ChoiceNextPage,
55    Concede,
56}
57
58#[derive(Clone, Copy, Debug, PartialEq, Eq)]
59enum ActionKey {
60    MulliganConfirm,
61    MulliganSelect {
62        hand_index: usize,
63    },
64    Pass,
65    ClockFromHand {
66        hand_index: usize,
67    },
68    MainPlayCharacter {
69        hand_index: usize,
70        stage_slot: usize,
71    },
72    MainPlayEvent {
73        hand_index: usize,
74    },
75    MainMove {
76        from_slot: usize,
77        to_slot: usize,
78    },
79    ClimaxPlay {
80        hand_index: usize,
81    },
82    Attack {
83        slot: usize,
84        attack_type_code: usize,
85    },
86    LevelUp {
87        index: usize,
88    },
89    EncorePay {
90        slot: usize,
91    },
92    EncoreDecline {
93        slot: usize,
94    },
95    TriggerOrder {
96        index: usize,
97    },
98    ChoiceSelect {
99        index: usize,
100    },
101    ChoicePrevPage,
102    ChoiceNextPage,
103    Concede,
104}
105
106const ACTION_FAMILY_ORDER: [ActionFamily; 17] = [
107    ActionFamily::MulliganConfirm,
108    ActionFamily::MulliganSelect,
109    ActionFamily::Pass,
110    ActionFamily::ClockFromHand,
111    ActionFamily::MainPlayCharacter,
112    ActionFamily::MainPlayEvent,
113    ActionFamily::MainMove,
114    ActionFamily::ClimaxPlay,
115    ActionFamily::Attack,
116    ActionFamily::LevelUp,
117    ActionFamily::EncorePay,
118    ActionFamily::EncoreDecline,
119    ActionFamily::TriggerOrder,
120    ActionFamily::ChoiceSelect,
121    ActionFamily::ChoicePrevPage,
122    ActionFamily::ChoiceNextPage,
123    ActionFamily::Concede,
124];
125
126const ACTION_FAMILY_BASES: [usize; ACTION_FAMILY_ORDER.len()] = [
127    MULLIGAN_CONFIRM_ID,
128    MULLIGAN_SELECT_BASE,
129    PASS_ACTION_ID,
130    CLOCK_HAND_BASE,
131    MAIN_PLAY_CHAR_BASE,
132    MAIN_PLAY_EVENT_BASE,
133    MAIN_MOVE_BASE,
134    CLIMAX_PLAY_BASE,
135    ATTACK_BASE,
136    LEVEL_UP_BASE,
137    ENCORE_PAY_BASE,
138    ENCORE_DECLINE_BASE,
139    TRIGGER_ORDER_BASE,
140    CHOICE_BASE,
141    CHOICE_PREV_ID,
142    CHOICE_NEXT_ID,
143    CONCEDE_ID,
144];
145
146const ACTION_FAMILY_COUNTS: [usize; ACTION_FAMILY_ORDER.len()] = [
147    1,
148    MULLIGAN_SELECT_COUNT,
149    1,
150    CLOCK_HAND_COUNT,
151    MAIN_PLAY_CHAR_COUNT,
152    MAIN_PLAY_EVENT_COUNT,
153    MAIN_MOVE_COUNT,
154    CLIMAX_PLAY_COUNT,
155    ATTACK_COUNT,
156    LEVEL_UP_COUNT,
157    ENCORE_PAY_COUNT,
158    ENCORE_DECLINE_COUNT,
159    TRIGGER_ORDER_COUNT,
160    CHOICE_COUNT,
161    1,
162    1,
163    1,
164];
165
166#[inline]
167const fn action_family_idx(family: ActionFamily) -> usize {
168    family as usize
169}
170
171#[inline]
172fn action_family_base(family: ActionFamily) -> usize {
173    ACTION_FAMILY_BASES[action_family_idx(family)]
174}
175
176#[inline]
177fn action_family_count(family: ActionFamily) -> usize {
178    ACTION_FAMILY_COUNTS[action_family_idx(family)]
179}
180
181#[inline]
182fn action_family_offset_for_id(id: usize) -> Option<(ActionFamily, usize)> {
183    if id >= ACTION_SPACE_SIZE {
184        return None;
185    }
186    for family in ACTION_FAMILY_ORDER {
187        let base = action_family_base(family);
188        if id < base {
189            break;
190        }
191        let offset = id - base;
192        if offset < action_family_count(family) {
193            return Some((family, offset));
194        }
195    }
196    None
197}
198
199#[inline]
200fn action_key_from_family_offset(family: ActionFamily, offset: usize) -> ActionKey {
201    match family {
202        ActionFamily::MulliganConfirm => ActionKey::MulliganConfirm,
203        ActionFamily::MulliganSelect => ActionKey::MulliganSelect { hand_index: offset },
204        ActionFamily::Pass => ActionKey::Pass,
205        ActionFamily::ClockFromHand => ActionKey::ClockFromHand { hand_index: offset },
206        ActionFamily::MainPlayCharacter => ActionKey::MainPlayCharacter {
207            hand_index: offset / MAX_STAGE,
208            stage_slot: offset % MAX_STAGE,
209        },
210        ActionFamily::MainPlayEvent => ActionKey::MainPlayEvent { hand_index: offset },
211        ActionFamily::MainMove => {
212            let from_slot = offset / (MAX_STAGE - 1);
213            let to_index = offset % (MAX_STAGE - 1);
214            let to_slot = if to_index >= from_slot {
215                to_index + 1
216            } else {
217                to_index
218            };
219            ActionKey::MainMove { from_slot, to_slot }
220        }
221        ActionFamily::ClimaxPlay => ActionKey::ClimaxPlay { hand_index: offset },
222        ActionFamily::Attack => ActionKey::Attack {
223            slot: offset / 3,
224            attack_type_code: offset % 3,
225        },
226        ActionFamily::LevelUp => ActionKey::LevelUp { index: offset },
227        ActionFamily::EncorePay => ActionKey::EncorePay { slot: offset },
228        ActionFamily::EncoreDecline => ActionKey::EncoreDecline { slot: offset },
229        ActionFamily::TriggerOrder => ActionKey::TriggerOrder { index: offset },
230        ActionFamily::ChoiceSelect => ActionKey::ChoiceSelect { index: offset },
231        ActionFamily::ChoicePrevPage => ActionKey::ChoicePrevPage,
232        ActionFamily::ChoiceNextPage => ActionKey::ChoiceNextPage,
233        ActionFamily::Concede => ActionKey::Concede,
234    }
235}
236
237#[inline]
238fn action_key_for_id(id: usize) -> Option<ActionKey> {
239    let (family, offset) = action_family_offset_for_id(id)?;
240    Some(action_key_from_family_offset(family, offset))
241}
242
243#[inline]
244fn action_desc_for_key(action: ActionKey) -> ActionDesc {
245    match action {
246        ActionKey::MulliganConfirm => ActionDesc::MulliganConfirm,
247        ActionKey::MulliganSelect { hand_index } => ActionDesc::MulliganSelect {
248            hand_index: hand_index as u8,
249        },
250        ActionKey::Pass => ActionDesc::Pass,
251        ActionKey::ClockFromHand { hand_index } => ActionDesc::Clock {
252            hand_index: hand_index as u8,
253        },
254        ActionKey::MainPlayCharacter {
255            hand_index,
256            stage_slot,
257        } => ActionDesc::MainPlayCharacter {
258            hand_index: hand_index as u8,
259            stage_slot: stage_slot as u8,
260        },
261        ActionKey::MainPlayEvent { hand_index } => ActionDesc::MainPlayEvent {
262            hand_index: hand_index as u8,
263        },
264        ActionKey::MainMove { from_slot, to_slot } => ActionDesc::MainMove {
265            from_slot: from_slot as u8,
266            to_slot: to_slot as u8,
267        },
268        ActionKey::ClimaxPlay { hand_index } => ActionDesc::ClimaxPlay {
269            hand_index: hand_index as u8,
270        },
271        ActionKey::Attack {
272            slot,
273            attack_type_code,
274        } => ActionDesc::Attack {
275            slot: slot as u8,
276            attack_type: attack_type_from_code(attack_type_code),
277        },
278        ActionKey::LevelUp { index } => ActionDesc::LevelUp { index: index as u8 },
279        ActionKey::EncorePay { slot } => ActionDesc::EncorePay { slot: slot as u8 },
280        ActionKey::EncoreDecline { slot } => ActionDesc::EncoreDecline { slot: slot as u8 },
281        ActionKey::TriggerOrder { index } => ActionDesc::TriggerOrder { index: index as u8 },
282        ActionKey::ChoiceSelect { index } => ActionDesc::ChoiceSelect { index: index as u8 },
283        ActionKey::ChoicePrevPage => ActionDesc::ChoicePrevPage,
284        ActionKey::ChoiceNextPage => ActionDesc::ChoiceNextPage,
285        ActionKey::Concede => ActionDesc::Concede,
286    }
287}
288
289#[inline]
290fn action_id_desc_for_key(action: ActionKey) -> ActionIdDesc {
291    match action {
292        ActionKey::MulliganConfirm => ActionIdDesc {
293            family: "mulligan_confirm",
294            params: vec![],
295        },
296        ActionKey::MulliganSelect { hand_index } => ActionIdDesc {
297            family: "mulligan_select",
298            params: vec![ActionParam {
299                name: "hand_index",
300                value: ActionParamValue::Int(hand_index as i32),
301            }],
302        },
303        ActionKey::Pass => ActionIdDesc {
304            family: "pass",
305            params: vec![],
306        },
307        ActionKey::ClockFromHand { hand_index } => ActionIdDesc {
308            family: "clock_from_hand",
309            params: vec![ActionParam {
310                name: "hand_index",
311                value: ActionParamValue::Int(hand_index as i32),
312            }],
313        },
314        ActionKey::MainPlayCharacter {
315            hand_index,
316            stage_slot,
317        } => ActionIdDesc {
318            family: "main_play_character",
319            params: vec![
320                ActionParam {
321                    name: "hand_index",
322                    value: ActionParamValue::Int(hand_index as i32),
323                },
324                ActionParam {
325                    name: "stage_slot",
326                    value: ActionParamValue::Int(stage_slot as i32),
327                },
328            ],
329        },
330        ActionKey::MainPlayEvent { hand_index } => ActionIdDesc {
331            family: "main_play_event",
332            params: vec![ActionParam {
333                name: "hand_index",
334                value: ActionParamValue::Int(hand_index as i32),
335            }],
336        },
337        ActionKey::MainMove { from_slot, to_slot } => ActionIdDesc {
338            family: "main_move",
339            params: vec![
340                ActionParam {
341                    name: "from_slot",
342                    value: ActionParamValue::Int(from_slot as i32),
343                },
344                ActionParam {
345                    name: "to_slot",
346                    value: ActionParamValue::Int(to_slot as i32),
347                },
348            ],
349        },
350        ActionKey::ClimaxPlay { hand_index } => ActionIdDesc {
351            family: "climax_play",
352            params: vec![ActionParam {
353                name: "hand_index",
354                value: ActionParamValue::Int(hand_index as i32),
355            }],
356        },
357        ActionKey::Attack {
358            slot,
359            attack_type_code,
360        } => ActionIdDesc {
361            family: "attack",
362            params: vec![
363                ActionParam {
364                    name: "slot",
365                    value: ActionParamValue::Int(slot as i32),
366                },
367                ActionParam {
368                    name: "attack_type",
369                    value: ActionParamValue::Str(match attack_type_code {
370                        0 => "frontal",
371                        1 => "side",
372                        _ => "direct",
373                    }),
374                },
375            ],
376        },
377        ActionKey::LevelUp { index } => ActionIdDesc {
378            family: "level_up",
379            params: vec![ActionParam {
380                name: "index",
381                value: ActionParamValue::Int(index as i32),
382            }],
383        },
384        ActionKey::EncorePay { slot } => ActionIdDesc {
385            family: "encore_pay",
386            params: vec![ActionParam {
387                name: "slot",
388                value: ActionParamValue::Int(slot as i32),
389            }],
390        },
391        ActionKey::EncoreDecline { slot } => ActionIdDesc {
392            family: "encore_decline",
393            params: vec![ActionParam {
394                name: "slot",
395                value: ActionParamValue::Int(slot as i32),
396            }],
397        },
398        ActionKey::TriggerOrder { index } => ActionIdDesc {
399            family: "trigger_order",
400            params: vec![ActionParam {
401                name: "index",
402                value: ActionParamValue::Int(index as i32),
403            }],
404        },
405        ActionKey::ChoiceSelect { index } => ActionIdDesc {
406            family: "choice_select",
407            params: vec![ActionParam {
408                name: "index",
409                value: ActionParamValue::Int(index as i32),
410            }],
411        },
412        ActionKey::ChoicePrevPage => ActionIdDesc {
413            family: "choice_prev_page",
414            params: vec![],
415        },
416        ActionKey::ChoiceNextPage => ActionIdDesc {
417            family: "choice_next_page",
418            params: vec![],
419        },
420        ActionKey::Concede => ActionIdDesc {
421            family: "concede",
422            params: vec![],
423        },
424    }
425}
426
427/// Decode an action id into a human-readable description.
428pub fn decode_action_id(id: usize) -> Option<ActionIdDesc> {
429    let action = action_key_for_id(id)?;
430    Some(action_id_desc_for_key(action))
431}
432
433/// Decode an action id into a canonical action descriptor.
434pub fn action_desc_for_id(id: usize) -> Option<ActionDesc> {
435    let action = action_key_for_id(id)?;
436    Some(action_desc_for_key(action))
437}
438
439/// Encode a canonical action descriptor into an action id.
440pub fn action_id_for(action: &ActionDesc) -> Option<usize> {
441    match action {
442        ActionDesc::MulliganConfirm => Some(MULLIGAN_CONFIRM_ID),
443        ActionDesc::MulliganSelect { hand_index } => {
444            let hi = *hand_index as usize;
445            if hi < MULLIGAN_SELECT_COUNT {
446                Some(MULLIGAN_SELECT_BASE + hi)
447            } else {
448                None
449            }
450        }
451        ActionDesc::Pass => Some(PASS_ACTION_ID),
452        ActionDesc::Clock { hand_index } => {
453            let hi = *hand_index as usize;
454            if hi < MAX_HAND {
455                Some(CLOCK_HAND_BASE + hi)
456            } else {
457                None
458            }
459        }
460        ActionDesc::MainPlayCharacter {
461            hand_index,
462            stage_slot,
463        } => {
464            let hi = *hand_index as usize;
465            let ss = *stage_slot as usize;
466            if hi < MAX_HAND && ss < MAX_STAGE {
467                Some(MAIN_PLAY_CHAR_BASE + hi * MAX_STAGE + ss)
468            } else {
469                None
470            }
471        }
472        ActionDesc::MainPlayEvent { hand_index } => {
473            let hi = *hand_index as usize;
474            if hi < MAX_HAND {
475                Some(MAIN_PLAY_EVENT_BASE + hi)
476            } else {
477                None
478            }
479        }
480        ActionDesc::MainMove { from_slot, to_slot } => {
481            let fs = *from_slot as usize;
482            let ts = *to_slot as usize;
483            if fs < MAX_STAGE && ts < MAX_STAGE && fs != ts {
484                let to_index = if ts < fs { ts } else { ts - 1 };
485                Some(MAIN_MOVE_BASE + fs * (MAX_STAGE - 1) + to_index)
486            } else {
487                None
488            }
489        }
490        ActionDesc::MainActivateAbility { .. } => None,
491        ActionDesc::ClimaxPlay { hand_index } => {
492            let hi = *hand_index as usize;
493            if hi < MAX_HAND {
494                Some(CLIMAX_PLAY_BASE + hi)
495            } else {
496                None
497            }
498        }
499        ActionDesc::Attack { slot, attack_type } => {
500            let s = *slot as usize;
501            let t = attack_type_to_i32(*attack_type) as usize;
502            if s < ATTACK_SLOT_COUNT && t < 3 {
503                Some(ATTACK_BASE + s * 3 + t)
504            } else {
505                None
506            }
507        }
508        ActionDesc::CounterPlay { .. } => None,
509        ActionDesc::LevelUp { index } => {
510            let idx = *index as usize;
511            if idx < LEVEL_UP_COUNT {
512                Some(LEVEL_UP_BASE + idx)
513            } else {
514                None
515            }
516        }
517        ActionDesc::EncorePay { slot } => {
518            let s = *slot as usize;
519            if s < ENCORE_PAY_COUNT {
520                Some(ENCORE_PAY_BASE + s)
521            } else {
522                None
523            }
524        }
525        ActionDesc::EncoreDecline { slot } => {
526            let s = *slot as usize;
527            if s < ENCORE_DECLINE_COUNT {
528                Some(ENCORE_DECLINE_BASE + s)
529            } else {
530                None
531            }
532        }
533        ActionDesc::TriggerOrder { index } => {
534            let idx = *index as usize;
535            if idx < TRIGGER_ORDER_COUNT {
536                Some(TRIGGER_ORDER_BASE + idx)
537            } else {
538                None
539            }
540        }
541        ActionDesc::ChoiceSelect { index } => {
542            let idx = *index as usize;
543            if idx < CHOICE_COUNT {
544                Some(CHOICE_BASE + idx)
545            } else {
546                None
547            }
548        }
549        ActionDesc::ChoicePrevPage => Some(CHOICE_PREV_ID),
550        ActionDesc::ChoiceNextPage => Some(CHOICE_NEXT_ID),
551        ActionDesc::Concede => Some(CONCEDE_ID),
552    }
553}
554
555fn attack_type_to_i32(attack_type: AttackType) -> i32 {
556    match attack_type {
557        AttackType::Frontal => 0,
558        AttackType::Side => 1,
559        AttackType::Direct => 2,
560    }
561}
562
563#[inline]
564fn attack_type_from_code(code: usize) -> AttackType {
565    match code {
566        0 => AttackType::Frontal,
567        1 => AttackType::Side,
568        _ => AttackType::Direct,
569    }
570}