1use serde::Serialize;
2
3use crate::legal::ActionDesc;
4use crate::state::AttackType;
5
6use super::constants::*;
7
8#[derive(Clone, Debug, Serialize, PartialEq, Eq)]
10#[serde(untagged)]
11pub enum ActionParamValue {
12 Int(i32),
14 Str(&'static str),
16}
17
18#[derive(Clone, Debug, Serialize, PartialEq, Eq)]
20pub struct ActionParam {
21 pub name: &'static str,
23 pub value: ActionParamValue,
25}
26
27#[derive(Clone, Debug, Serialize, PartialEq, Eq)]
29pub struct ActionIdDesc {
30 pub family: &'static str,
32 pub params: Vec<ActionParam>,
34}
35
36#[derive(Clone, Debug, Serialize, PartialEq, Eq)]
38pub struct FactorizedActionDesc {
39 pub family: &'static str,
41 pub arg0: Option<u16>,
43 pub arg1: Option<u16>,
45 pub arg2: Option<u16>,
47}
48
49pub const ACTION_META_WIDTH: usize = 4;
51pub const ACTION_META_UNUSED: u16 = u16::MAX;
53
54#[derive(Clone, Copy, Debug, PartialEq, Eq)]
55#[repr(u8)]
56enum ActionFamily {
57 MulliganConfirm,
58 MulliganSelect,
59 Pass,
60 ClockFromHand,
61 MainPlayCharacter,
62 MainPlayEvent,
63 MainMove,
64 ClimaxPlay,
65 Attack,
66 LevelUp,
67 EncorePay,
68 EncoreDecline,
69 TriggerOrder,
70 ChoiceSelect,
71 ChoicePrevPage,
72 ChoiceNextPage,
73 Concede,
74}
75
76#[derive(Clone, Copy, Debug, PartialEq, Eq)]
77enum ActionKey {
78 MulliganConfirm,
79 MulliganSelect {
80 hand_index: usize,
81 },
82 Pass,
83 ClockFromHand {
84 hand_index: usize,
85 },
86 MainPlayCharacter {
87 hand_index: usize,
88 stage_slot: usize,
89 },
90 MainPlayEvent {
91 hand_index: usize,
92 },
93 MainMove {
94 from_slot: usize,
95 to_slot: usize,
96 },
97 ClimaxPlay {
98 hand_index: usize,
99 },
100 Attack {
101 slot: usize,
102 attack_type_code: usize,
103 },
104 LevelUp {
105 index: usize,
106 },
107 EncorePay {
108 slot: usize,
109 },
110 EncoreDecline {
111 slot: usize,
112 },
113 TriggerOrder {
114 index: usize,
115 },
116 ChoiceSelect {
117 index: usize,
118 },
119 ChoicePrevPage,
120 ChoiceNextPage,
121 Concede,
122}
123
124const ACTION_FAMILY_ORDER: [ActionFamily; 17] = [
125 ActionFamily::MulliganConfirm,
126 ActionFamily::MulliganSelect,
127 ActionFamily::Pass,
128 ActionFamily::ClockFromHand,
129 ActionFamily::MainPlayCharacter,
130 ActionFamily::MainPlayEvent,
131 ActionFamily::MainMove,
132 ActionFamily::ClimaxPlay,
133 ActionFamily::Attack,
134 ActionFamily::LevelUp,
135 ActionFamily::EncorePay,
136 ActionFamily::EncoreDecline,
137 ActionFamily::TriggerOrder,
138 ActionFamily::ChoiceSelect,
139 ActionFamily::ChoicePrevPage,
140 ActionFamily::ChoiceNextPage,
141 ActionFamily::Concede,
142];
143
144const ACTION_FAMILY_BASES: [usize; ACTION_FAMILY_ORDER.len()] = [
145 MULLIGAN_CONFIRM_ID,
146 MULLIGAN_SELECT_BASE,
147 PASS_ACTION_ID,
148 CLOCK_HAND_BASE,
149 MAIN_PLAY_CHAR_BASE,
150 MAIN_PLAY_EVENT_BASE,
151 MAIN_MOVE_BASE,
152 CLIMAX_PLAY_BASE,
153 ATTACK_BASE,
154 LEVEL_UP_BASE,
155 ENCORE_PAY_BASE,
156 ENCORE_DECLINE_BASE,
157 TRIGGER_ORDER_BASE,
158 CHOICE_BASE,
159 CHOICE_PREV_ID,
160 CHOICE_NEXT_ID,
161 CONCEDE_ID,
162];
163
164const ACTION_FAMILY_COUNTS: [usize; ACTION_FAMILY_ORDER.len()] = [
165 1,
166 MULLIGAN_SELECT_COUNT,
167 1,
168 CLOCK_HAND_COUNT,
169 MAIN_PLAY_CHAR_COUNT,
170 MAIN_PLAY_EVENT_COUNT,
171 MAIN_MOVE_COUNT,
172 CLIMAX_PLAY_COUNT,
173 ATTACK_COUNT,
174 LEVEL_UP_COUNT,
175 ENCORE_PAY_COUNT,
176 ENCORE_DECLINE_COUNT,
177 TRIGGER_ORDER_COUNT,
178 CHOICE_COUNT,
179 1,
180 1,
181 1,
182];
183
184#[inline]
185const fn action_family_idx(family: ActionFamily) -> usize {
186 family as usize
187}
188
189#[inline]
190fn action_family_base(family: ActionFamily) -> usize {
191 ACTION_FAMILY_BASES[action_family_idx(family)]
192}
193
194#[inline]
195fn action_family_count(family: ActionFamily) -> usize {
196 ACTION_FAMILY_COUNTS[action_family_idx(family)]
197}
198
199#[inline]
200fn action_family_offset_for_id(id: usize) -> Option<(ActionFamily, usize)> {
201 if id >= ACTION_SPACE_SIZE {
202 return None;
203 }
204 for family in ACTION_FAMILY_ORDER {
205 let base = action_family_base(family);
206 if id < base {
207 break;
208 }
209 let offset = id - base;
210 if offset < action_family_count(family) {
211 return Some((family, offset));
212 }
213 }
214 None
215}
216
217#[inline]
218fn action_key_from_family_offset(family: ActionFamily, offset: usize) -> ActionKey {
219 match family {
220 ActionFamily::MulliganConfirm => ActionKey::MulliganConfirm,
221 ActionFamily::MulliganSelect => ActionKey::MulliganSelect { hand_index: offset },
222 ActionFamily::Pass => ActionKey::Pass,
223 ActionFamily::ClockFromHand => ActionKey::ClockFromHand { hand_index: offset },
224 ActionFamily::MainPlayCharacter => ActionKey::MainPlayCharacter {
225 hand_index: offset / MAX_STAGE,
226 stage_slot: offset % MAX_STAGE,
227 },
228 ActionFamily::MainPlayEvent => ActionKey::MainPlayEvent { hand_index: offset },
229 ActionFamily::MainMove => {
230 let from_slot = offset / (MAX_STAGE - 1);
231 let to_index = offset % (MAX_STAGE - 1);
232 let to_slot = if to_index >= from_slot {
233 to_index + 1
234 } else {
235 to_index
236 };
237 ActionKey::MainMove { from_slot, to_slot }
238 }
239 ActionFamily::ClimaxPlay => ActionKey::ClimaxPlay { hand_index: offset },
240 ActionFamily::Attack => ActionKey::Attack {
241 slot: offset / 3,
242 attack_type_code: offset % 3,
243 },
244 ActionFamily::LevelUp => ActionKey::LevelUp { index: offset },
245 ActionFamily::EncorePay => ActionKey::EncorePay { slot: offset },
246 ActionFamily::EncoreDecline => ActionKey::EncoreDecline { slot: offset },
247 ActionFamily::TriggerOrder => ActionKey::TriggerOrder { index: offset },
248 ActionFamily::ChoiceSelect => ActionKey::ChoiceSelect { index: offset },
249 ActionFamily::ChoicePrevPage => ActionKey::ChoicePrevPage,
250 ActionFamily::ChoiceNextPage => ActionKey::ChoiceNextPage,
251 ActionFamily::Concede => ActionKey::Concede,
252 }
253}
254
255#[inline]
256fn action_key_for_id(id: usize) -> Option<ActionKey> {
257 let (family, offset) = action_family_offset_for_id(id)?;
258 Some(action_key_from_family_offset(family, offset))
259}
260
261#[inline]
262fn action_desc_for_key(action: ActionKey) -> ActionDesc {
263 match action {
264 ActionKey::MulliganConfirm => ActionDesc::MulliganConfirm,
265 ActionKey::MulliganSelect { hand_index } => ActionDesc::MulliganSelect {
266 hand_index: hand_index as u8,
267 },
268 ActionKey::Pass => ActionDesc::Pass,
269 ActionKey::ClockFromHand { hand_index } => ActionDesc::Clock {
270 hand_index: hand_index as u8,
271 },
272 ActionKey::MainPlayCharacter {
273 hand_index,
274 stage_slot,
275 } => ActionDesc::MainPlayCharacter {
276 hand_index: hand_index as u8,
277 stage_slot: stage_slot as u8,
278 },
279 ActionKey::MainPlayEvent { hand_index } => ActionDesc::MainPlayEvent {
280 hand_index: hand_index as u8,
281 },
282 ActionKey::MainMove { from_slot, to_slot } => ActionDesc::MainMove {
283 from_slot: from_slot as u8,
284 to_slot: to_slot as u8,
285 },
286 ActionKey::ClimaxPlay { hand_index } => ActionDesc::ClimaxPlay {
287 hand_index: hand_index as u8,
288 },
289 ActionKey::Attack {
290 slot,
291 attack_type_code,
292 } => ActionDesc::Attack {
293 slot: slot as u8,
294 attack_type: attack_type_from_code(attack_type_code),
295 },
296 ActionKey::LevelUp { index } => ActionDesc::LevelUp { index: index as u8 },
297 ActionKey::EncorePay { slot } => ActionDesc::EncorePay { slot: slot as u8 },
298 ActionKey::EncoreDecline { slot } => ActionDesc::EncoreDecline { slot: slot as u8 },
299 ActionKey::TriggerOrder { index } => ActionDesc::TriggerOrder { index: index as u8 },
300 ActionKey::ChoiceSelect { index } => ActionDesc::ChoiceSelect { index: index as u8 },
301 ActionKey::ChoicePrevPage => ActionDesc::ChoicePrevPage,
302 ActionKey::ChoiceNextPage => ActionDesc::ChoiceNextPage,
303 ActionKey::Concede => ActionDesc::Concede,
304 }
305}
306
307#[inline]
308fn action_id_desc_for_key(action: ActionKey) -> ActionIdDesc {
309 match action {
310 ActionKey::MulliganConfirm => ActionIdDesc {
311 family: "mulligan_confirm",
312 params: vec![],
313 },
314 ActionKey::MulliganSelect { hand_index } => ActionIdDesc {
315 family: "mulligan_select",
316 params: vec![ActionParam {
317 name: "hand_index",
318 value: ActionParamValue::Int(hand_index as i32),
319 }],
320 },
321 ActionKey::Pass => ActionIdDesc {
322 family: "pass",
323 params: vec![],
324 },
325 ActionKey::ClockFromHand { hand_index } => ActionIdDesc {
326 family: "clock_from_hand",
327 params: vec![ActionParam {
328 name: "hand_index",
329 value: ActionParamValue::Int(hand_index as i32),
330 }],
331 },
332 ActionKey::MainPlayCharacter {
333 hand_index,
334 stage_slot,
335 } => ActionIdDesc {
336 family: "main_play_character",
337 params: vec![
338 ActionParam {
339 name: "hand_index",
340 value: ActionParamValue::Int(hand_index as i32),
341 },
342 ActionParam {
343 name: "stage_slot",
344 value: ActionParamValue::Int(stage_slot as i32),
345 },
346 ],
347 },
348 ActionKey::MainPlayEvent { hand_index } => ActionIdDesc {
349 family: "main_play_event",
350 params: vec![ActionParam {
351 name: "hand_index",
352 value: ActionParamValue::Int(hand_index as i32),
353 }],
354 },
355 ActionKey::MainMove { from_slot, to_slot } => ActionIdDesc {
356 family: "main_move",
357 params: vec![
358 ActionParam {
359 name: "from_slot",
360 value: ActionParamValue::Int(from_slot as i32),
361 },
362 ActionParam {
363 name: "to_slot",
364 value: ActionParamValue::Int(to_slot as i32),
365 },
366 ],
367 },
368 ActionKey::ClimaxPlay { hand_index } => ActionIdDesc {
369 family: "climax_play",
370 params: vec![ActionParam {
371 name: "hand_index",
372 value: ActionParamValue::Int(hand_index as i32),
373 }],
374 },
375 ActionKey::Attack {
376 slot,
377 attack_type_code,
378 } => ActionIdDesc {
379 family: "attack",
380 params: vec![
381 ActionParam {
382 name: "slot",
383 value: ActionParamValue::Int(slot as i32),
384 },
385 ActionParam {
386 name: "attack_type",
387 value: ActionParamValue::Str(match attack_type_code {
388 0 => "frontal",
389 1 => "side",
390 _ => "direct",
391 }),
392 },
393 ],
394 },
395 ActionKey::LevelUp { index } => ActionIdDesc {
396 family: "level_up",
397 params: vec![ActionParam {
398 name: "index",
399 value: ActionParamValue::Int(index as i32),
400 }],
401 },
402 ActionKey::EncorePay { slot } => ActionIdDesc {
403 family: "encore_pay",
404 params: vec![ActionParam {
405 name: "slot",
406 value: ActionParamValue::Int(slot as i32),
407 }],
408 },
409 ActionKey::EncoreDecline { slot } => ActionIdDesc {
410 family: "encore_decline",
411 params: vec![ActionParam {
412 name: "slot",
413 value: ActionParamValue::Int(slot as i32),
414 }],
415 },
416 ActionKey::TriggerOrder { index } => ActionIdDesc {
417 family: "trigger_order",
418 params: vec![ActionParam {
419 name: "index",
420 value: ActionParamValue::Int(index as i32),
421 }],
422 },
423 ActionKey::ChoiceSelect { index } => ActionIdDesc {
424 family: "choice_select",
425 params: vec![ActionParam {
426 name: "index",
427 value: ActionParamValue::Int(index as i32),
428 }],
429 },
430 ActionKey::ChoicePrevPage => ActionIdDesc {
431 family: "choice_prev_page",
432 params: vec![],
433 },
434 ActionKey::ChoiceNextPage => ActionIdDesc {
435 family: "choice_next_page",
436 params: vec![],
437 },
438 ActionKey::Concede => ActionIdDesc {
439 family: "concede",
440 params: vec![],
441 },
442 }
443}
444
445fn action_meta_for_key(action: ActionKey) -> [u16; ACTION_META_WIDTH] {
446 let unused = ACTION_META_UNUSED;
447 match action {
448 ActionKey::MulliganConfirm => {
449 [ActionFamily::MulliganConfirm as u16, unused, unused, unused]
450 }
451 ActionKey::MulliganSelect { hand_index } => [
452 ActionFamily::MulliganSelect as u16,
453 hand_index as u16,
454 unused,
455 unused,
456 ],
457 ActionKey::Pass => [ActionFamily::Pass as u16, unused, unused, unused],
458 ActionKey::ClockFromHand { hand_index } => [
459 ActionFamily::ClockFromHand as u16,
460 hand_index as u16,
461 unused,
462 unused,
463 ],
464 ActionKey::MainPlayCharacter {
465 hand_index,
466 stage_slot,
467 } => [
468 ActionFamily::MainPlayCharacter as u16,
469 hand_index as u16,
470 stage_slot as u16,
471 unused,
472 ],
473 ActionKey::MainPlayEvent { hand_index } => [
474 ActionFamily::MainPlayEvent as u16,
475 hand_index as u16,
476 unused,
477 unused,
478 ],
479 ActionKey::MainMove { from_slot, to_slot } => [
480 ActionFamily::MainMove as u16,
481 from_slot as u16,
482 to_slot as u16,
483 unused,
484 ],
485 ActionKey::ClimaxPlay { hand_index } => [
486 ActionFamily::ClimaxPlay as u16,
487 hand_index as u16,
488 unused,
489 unused,
490 ],
491 ActionKey::Attack {
492 slot,
493 attack_type_code,
494 } => [
495 ActionFamily::Attack as u16,
496 slot as u16,
497 attack_type_code as u16,
498 unused,
499 ],
500 ActionKey::LevelUp { index } => {
501 [ActionFamily::LevelUp as u16, index as u16, unused, unused]
502 }
503 ActionKey::EncorePay { slot } => {
504 [ActionFamily::EncorePay as u16, slot as u16, unused, unused]
505 }
506 ActionKey::EncoreDecline { slot } => [
507 ActionFamily::EncoreDecline as u16,
508 slot as u16,
509 unused,
510 unused,
511 ],
512 ActionKey::TriggerOrder { index } => [
513 ActionFamily::TriggerOrder as u16,
514 index as u16,
515 unused,
516 unused,
517 ],
518 ActionKey::ChoiceSelect { index } => [
519 ActionFamily::ChoiceSelect as u16,
520 index as u16,
521 unused,
522 unused,
523 ],
524 ActionKey::ChoicePrevPage => [ActionFamily::ChoicePrevPage as u16, unused, unused, unused],
525 ActionKey::ChoiceNextPage => [ActionFamily::ChoiceNextPage as u16, unused, unused, unused],
526 ActionKey::Concede => [ActionFamily::Concede as u16, unused, unused, unused],
527 }
528}
529
530#[inline]
531fn factorized_action_desc_for_key(action: ActionKey) -> FactorizedActionDesc {
532 match action {
533 ActionKey::MulliganConfirm => FactorizedActionDesc {
534 family: "mulligan_confirm",
535 arg0: None,
536 arg1: None,
537 arg2: None,
538 },
539 ActionKey::MulliganSelect { hand_index } => FactorizedActionDesc {
540 family: "mulligan_select",
541 arg0: Some(hand_index as u16),
542 arg1: None,
543 arg2: None,
544 },
545 ActionKey::Pass => FactorizedActionDesc {
546 family: "pass",
547 arg0: None,
548 arg1: None,
549 arg2: None,
550 },
551 ActionKey::ClockFromHand { hand_index } => FactorizedActionDesc {
552 family: "clock_from_hand",
553 arg0: Some(hand_index as u16),
554 arg1: None,
555 arg2: None,
556 },
557 ActionKey::MainPlayCharacter {
558 hand_index,
559 stage_slot,
560 } => FactorizedActionDesc {
561 family: "main_play_character",
562 arg0: Some(hand_index as u16),
563 arg1: Some(stage_slot as u16),
564 arg2: None,
565 },
566 ActionKey::MainPlayEvent { hand_index } => FactorizedActionDesc {
567 family: "main_play_event",
568 arg0: Some(hand_index as u16),
569 arg1: None,
570 arg2: None,
571 },
572 ActionKey::MainMove { from_slot, to_slot } => FactorizedActionDesc {
573 family: "main_move",
574 arg0: Some(from_slot as u16),
575 arg1: Some(to_slot as u16),
576 arg2: None,
577 },
578 ActionKey::ClimaxPlay { hand_index } => FactorizedActionDesc {
579 family: "climax_play",
580 arg0: Some(hand_index as u16),
581 arg1: None,
582 arg2: None,
583 },
584 ActionKey::Attack {
585 slot,
586 attack_type_code,
587 } => FactorizedActionDesc {
588 family: "attack",
589 arg0: Some(slot as u16),
590 arg1: Some(attack_type_code as u16),
591 arg2: None,
592 },
593 ActionKey::LevelUp { index } => FactorizedActionDesc {
594 family: "level_up",
595 arg0: Some(index as u16),
596 arg1: None,
597 arg2: None,
598 },
599 ActionKey::EncorePay { slot } => FactorizedActionDesc {
600 family: "encore_pay",
601 arg0: Some(slot as u16),
602 arg1: None,
603 arg2: None,
604 },
605 ActionKey::EncoreDecline { slot } => FactorizedActionDesc {
606 family: "encore_decline",
607 arg0: Some(slot as u16),
608 arg1: None,
609 arg2: None,
610 },
611 ActionKey::TriggerOrder { index } => FactorizedActionDesc {
612 family: "trigger_order",
613 arg0: Some(index as u16),
614 arg1: None,
615 arg2: None,
616 },
617 ActionKey::ChoiceSelect { index } => FactorizedActionDesc {
618 family: "choice_select",
619 arg0: Some(index as u16),
620 arg1: None,
621 arg2: None,
622 },
623 ActionKey::ChoicePrevPage => FactorizedActionDesc {
624 family: "choice_prev_page",
625 arg0: None,
626 arg1: None,
627 arg2: None,
628 },
629 ActionKey::ChoiceNextPage => FactorizedActionDesc {
630 family: "choice_next_page",
631 arg0: None,
632 arg1: None,
633 arg2: None,
634 },
635 ActionKey::Concede => FactorizedActionDesc {
636 family: "concede",
637 arg0: None,
638 arg1: None,
639 arg2: None,
640 },
641 }
642}
643
644#[inline]
645fn action_key_for_factorized_desc(desc: &FactorizedActionDesc) -> Option<ActionKey> {
646 match desc.family {
647 "mulligan_confirm" if desc.arg0.is_none() && desc.arg1.is_none() && desc.arg2.is_none() => {
648 Some(ActionKey::MulliganConfirm)
649 }
650 "mulligan_select" if desc.arg1.is_none() && desc.arg2.is_none() => {
651 let hand_index = usize::from(desc.arg0?);
652 (hand_index < MULLIGAN_SELECT_COUNT).then_some(ActionKey::MulliganSelect { hand_index })
653 }
654 "pass" if desc.arg0.is_none() && desc.arg1.is_none() && desc.arg2.is_none() => {
655 Some(ActionKey::Pass)
656 }
657 "clock_from_hand" if desc.arg1.is_none() && desc.arg2.is_none() => {
658 let hand_index = usize::from(desc.arg0?);
659 (hand_index < CLOCK_HAND_COUNT).then_some(ActionKey::ClockFromHand { hand_index })
660 }
661 "main_play_character" if desc.arg2.is_none() => match (desc.arg0, desc.arg1) {
662 (Some(hand_index), Some(stage_slot)) => {
663 let hand_index = usize::from(hand_index);
664 let stage_slot = usize::from(stage_slot);
665 (hand_index < MAX_HAND && stage_slot < MAX_STAGE).then_some(
666 ActionKey::MainPlayCharacter {
667 hand_index,
668 stage_slot,
669 },
670 )
671 }
672 _ => None,
673 },
674 "main_play_event" if desc.arg1.is_none() && desc.arg2.is_none() => {
675 let hand_index = usize::from(desc.arg0?);
676 (hand_index < MAIN_PLAY_EVENT_COUNT).then_some(ActionKey::MainPlayEvent { hand_index })
677 }
678 "main_move" if desc.arg2.is_none() => match (desc.arg0, desc.arg1) {
679 (Some(from_slot), Some(to_slot)) => {
680 let from_slot = usize::from(from_slot);
681 let to_slot = usize::from(to_slot);
682 (from_slot < MAX_STAGE && to_slot < MAX_STAGE && from_slot != to_slot)
683 .then_some(ActionKey::MainMove { from_slot, to_slot })
684 }
685 _ => None,
686 },
687 "climax_play" if desc.arg1.is_none() && desc.arg2.is_none() => {
688 let hand_index = usize::from(desc.arg0?);
689 (hand_index < CLIMAX_PLAY_COUNT).then_some(ActionKey::ClimaxPlay { hand_index })
690 }
691 "attack" if desc.arg2.is_none() => match (desc.arg0, desc.arg1) {
692 (Some(slot), Some(attack_type_code)) => {
693 let slot = usize::from(slot);
694 let attack_type_code = usize::from(attack_type_code);
695 (slot < ATTACK_SLOT_COUNT && attack_type_code < 3).then_some(ActionKey::Attack {
696 slot,
697 attack_type_code,
698 })
699 }
700 _ => None,
701 },
702 "level_up" if desc.arg1.is_none() && desc.arg2.is_none() => {
703 let index = usize::from(desc.arg0?);
704 (index < LEVEL_UP_COUNT).then_some(ActionKey::LevelUp { index })
705 }
706 "encore_pay" if desc.arg1.is_none() && desc.arg2.is_none() => {
707 let slot = usize::from(desc.arg0?);
708 (slot < ENCORE_PAY_COUNT).then_some(ActionKey::EncorePay { slot })
709 }
710 "encore_decline" if desc.arg1.is_none() && desc.arg2.is_none() => {
711 let slot = usize::from(desc.arg0?);
712 (slot < ENCORE_DECLINE_COUNT).then_some(ActionKey::EncoreDecline { slot })
713 }
714 "trigger_order" if desc.arg1.is_none() && desc.arg2.is_none() => {
715 let index = usize::from(desc.arg0?);
716 (index < TRIGGER_ORDER_COUNT).then_some(ActionKey::TriggerOrder { index })
717 }
718 "choice_select" if desc.arg1.is_none() && desc.arg2.is_none() => {
719 let index = usize::from(desc.arg0?);
720 (index < CHOICE_COUNT).then_some(ActionKey::ChoiceSelect { index })
721 }
722 "choice_prev_page" if desc.arg0.is_none() && desc.arg1.is_none() && desc.arg2.is_none() => {
723 Some(ActionKey::ChoicePrevPage)
724 }
725 "choice_next_page" if desc.arg0.is_none() && desc.arg1.is_none() && desc.arg2.is_none() => {
726 Some(ActionKey::ChoiceNextPage)
727 }
728 "concede" if desc.arg0.is_none() && desc.arg1.is_none() && desc.arg2.is_none() => {
729 Some(ActionKey::Concede)
730 }
731 _ => None,
732 }
733}
734
735pub fn decode_action_id(id: usize) -> Option<ActionIdDesc> {
737 let action = action_key_for_id(id)?;
738 Some(action_id_desc_for_key(action))
739}
740
741pub fn decode_factorized_action_id(id: usize) -> Option<FactorizedActionDesc> {
743 let action = action_key_for_id(id)?;
744 Some(factorized_action_desc_for_key(action))
745}
746
747pub fn encode_factorized_action(desc: &FactorizedActionDesc) -> Option<usize> {
749 let action = action_key_for_factorized_desc(desc)?;
750 action_id_for(&action_desc_for_key(action))
751}
752
753pub(crate) fn action_meta_for_id(id: usize) -> Option<[u16; ACTION_META_WIDTH]> {
755 let action = action_key_for_id(id)?;
756 Some(action_meta_for_key(action))
757}
758
759pub fn action_desc_for_id(id: usize) -> Option<ActionDesc> {
761 let action = action_key_for_id(id)?;
762 Some(action_desc_for_key(action))
763}
764
765pub fn action_id_for(action: &ActionDesc) -> Option<usize> {
767 match action {
768 ActionDesc::MulliganConfirm => Some(MULLIGAN_CONFIRM_ID),
769 ActionDesc::MulliganSelect { hand_index } => {
770 let hi = *hand_index as usize;
771 if hi < MULLIGAN_SELECT_COUNT {
772 Some(MULLIGAN_SELECT_BASE + hi)
773 } else {
774 None
775 }
776 }
777 ActionDesc::Pass => Some(PASS_ACTION_ID),
778 ActionDesc::Clock { hand_index } => {
779 let hi = *hand_index as usize;
780 if hi < MAX_HAND {
781 Some(CLOCK_HAND_BASE + hi)
782 } else {
783 None
784 }
785 }
786 ActionDesc::MainPlayCharacter {
787 hand_index,
788 stage_slot,
789 } => {
790 let hi = *hand_index as usize;
791 let ss = *stage_slot as usize;
792 if hi < MAX_HAND && ss < MAX_STAGE {
793 Some(MAIN_PLAY_CHAR_BASE + hi * MAX_STAGE + ss)
794 } else {
795 None
796 }
797 }
798 ActionDesc::MainPlayEvent { hand_index } => {
799 let hi = *hand_index as usize;
800 if hi < MAX_HAND {
801 Some(MAIN_PLAY_EVENT_BASE + hi)
802 } else {
803 None
804 }
805 }
806 ActionDesc::MainMove { from_slot, to_slot } => {
807 let fs = *from_slot as usize;
808 let ts = *to_slot as usize;
809 if fs < MAX_STAGE && ts < MAX_STAGE && fs != ts {
810 let to_index = if ts < fs { ts } else { ts - 1 };
811 Some(MAIN_MOVE_BASE + fs * (MAX_STAGE - 1) + to_index)
812 } else {
813 None
814 }
815 }
816 ActionDesc::MainActivateAbility { .. } => None,
817 ActionDesc::ClimaxPlay { hand_index } => {
818 let hi = *hand_index as usize;
819 if hi < MAX_HAND {
820 Some(CLIMAX_PLAY_BASE + hi)
821 } else {
822 None
823 }
824 }
825 ActionDesc::Attack { slot, attack_type } => {
826 let s = *slot as usize;
827 let t = attack_type_to_i32(*attack_type) as usize;
828 if s < ATTACK_SLOT_COUNT && t < 3 {
829 Some(ATTACK_BASE + s * 3 + t)
830 } else {
831 None
832 }
833 }
834 ActionDesc::CounterPlay { .. } => None,
835 ActionDesc::LevelUp { index } => {
836 let idx = *index as usize;
837 if idx < LEVEL_UP_COUNT {
838 Some(LEVEL_UP_BASE + idx)
839 } else {
840 None
841 }
842 }
843 ActionDesc::EncorePay { slot } => {
844 let s = *slot as usize;
845 if s < ENCORE_PAY_COUNT {
846 Some(ENCORE_PAY_BASE + s)
847 } else {
848 None
849 }
850 }
851 ActionDesc::EncoreDecline { slot } => {
852 let s = *slot as usize;
853 if s < ENCORE_DECLINE_COUNT {
854 Some(ENCORE_DECLINE_BASE + s)
855 } else {
856 None
857 }
858 }
859 ActionDesc::TriggerOrder { index } => {
860 let idx = *index as usize;
861 if idx < TRIGGER_ORDER_COUNT {
862 Some(TRIGGER_ORDER_BASE + idx)
863 } else {
864 None
865 }
866 }
867 ActionDesc::ChoiceSelect { index } => {
868 let idx = *index as usize;
869 if idx < CHOICE_COUNT {
870 Some(CHOICE_BASE + idx)
871 } else {
872 None
873 }
874 }
875 ActionDesc::ChoicePrevPage => Some(CHOICE_PREV_ID),
876 ActionDesc::ChoiceNextPage => Some(CHOICE_NEXT_ID),
877 ActionDesc::Concede => Some(CONCEDE_ID),
878 }
879}
880
881fn attack_type_to_i32(attack_type: AttackType) -> i32 {
882 match attack_type {
883 AttackType::Frontal => 0,
884 AttackType::Side => 1,
885 AttackType::Direct => 2,
886 }
887}
888
889#[inline]
890fn attack_type_from_code(code: usize) -> AttackType {
891 match code {
892 0 => AttackType::Frontal,
893 1 => AttackType::Side,
894 _ => AttackType::Direct,
895 }
896}