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, 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
427pub 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
433pub 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
439pub 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}