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