1mod action_ids;
9mod constants;
10mod mask;
11mod observation;
12mod spec;
13
14pub use action_ids::{
15 action_desc_for_id, action_id_for, decode_action_id, ActionIdDesc, ActionParam,
16 ActionParamValue,
17};
18pub use constants::*;
19pub use mask::{build_action_mask, fill_action_mask, fill_action_mask_sparse};
20pub use observation::encode_observation;
21pub use spec::{
22 action_spec, action_spec_json, observation_spec, observation_spec_json, ActionFamilySpec,
23 ActionSpec, ObsFieldSpec, ObsSliceSpec, ObservationSpec, PlayerBlockSpec,
24};
25
26pub(crate) use observation::{
27 encode_obs_context, encode_obs_header, encode_obs_player_block_into, encode_obs_reason,
28 encode_obs_reveal, encode_observation_with_slot_power,
29};
30
31#[cfg(test)]
32mod tests {
33 use super::*;
34 use crate::ActionDesc;
35
36 const OBS_SPEC_HASH: u64 = 3922564485128559020;
37 const ACTION_SPEC_HASH: u64 = 2958398628847112153;
38
39 #[test]
40 fn observation_spec_json_snapshot_hash() {
41 let json = observation_spec_json();
42 let hash = crate::fingerprint::hash_bytes(json.as_bytes());
43 assert_eq!(hash, OBS_SPEC_HASH, "obs spec JSON hash changed");
44 }
45
46 #[test]
47 fn action_spec_json_snapshot_hash() {
48 let json = action_spec_json();
49 let hash = crate::fingerprint::hash_bytes(json.as_bytes());
50 assert_eq!(hash, ACTION_SPEC_HASH, "action spec JSON hash changed");
51 }
52
53 fn param(name: &'static str, value: ActionParamValue) -> ActionParam {
54 ActionParam { name, value }
55 }
56
57 #[test]
58 fn action_id_decode_roundtrip_samples() {
59 let samples = vec![
60 (
61 ActionDesc::MulliganConfirm,
62 ActionIdDesc {
63 family: "mulligan_confirm",
64 params: vec![],
65 },
66 ),
67 (
68 ActionDesc::MulliganSelect { hand_index: 2 },
69 ActionIdDesc {
70 family: "mulligan_select",
71 params: vec![param("hand_index", ActionParamValue::Int(2))],
72 },
73 ),
74 (
75 ActionDesc::Pass,
76 ActionIdDesc {
77 family: "pass",
78 params: vec![],
79 },
80 ),
81 (
82 ActionDesc::Clock { hand_index: 3 },
83 ActionIdDesc {
84 family: "clock_from_hand",
85 params: vec![param("hand_index", ActionParamValue::Int(3))],
86 },
87 ),
88 (
89 ActionDesc::MainPlayCharacter {
90 hand_index: 1,
91 stage_slot: 2,
92 },
93 ActionIdDesc {
94 family: "main_play_character",
95 params: vec![
96 param("hand_index", ActionParamValue::Int(1)),
97 param("stage_slot", ActionParamValue::Int(2)),
98 ],
99 },
100 ),
101 (
102 ActionDesc::MainPlayEvent { hand_index: 4 },
103 ActionIdDesc {
104 family: "main_play_event",
105 params: vec![param("hand_index", ActionParamValue::Int(4))],
106 },
107 ),
108 (
109 ActionDesc::MainMove {
110 from_slot: 0,
111 to_slot: 1,
112 },
113 ActionIdDesc {
114 family: "main_move",
115 params: vec![
116 param("from_slot", ActionParamValue::Int(0)),
117 param("to_slot", ActionParamValue::Int(1)),
118 ],
119 },
120 ),
121 (
122 ActionDesc::ClimaxPlay { hand_index: 2 },
123 ActionIdDesc {
124 family: "climax_play",
125 params: vec![param("hand_index", ActionParamValue::Int(2))],
126 },
127 ),
128 (
129 ActionDesc::Attack {
130 slot: 1,
131 attack_type: crate::state::AttackType::Side,
132 },
133 ActionIdDesc {
134 family: "attack",
135 params: vec![
136 param("slot", ActionParamValue::Int(1)),
137 param("attack_type", ActionParamValue::Str("side")),
138 ],
139 },
140 ),
141 (
142 ActionDesc::LevelUp { index: 3 },
143 ActionIdDesc {
144 family: "level_up",
145 params: vec![param("index", ActionParamValue::Int(3))],
146 },
147 ),
148 (
149 ActionDesc::EncorePay { slot: 2 },
150 ActionIdDesc {
151 family: "encore_pay",
152 params: vec![param("slot", ActionParamValue::Int(2))],
153 },
154 ),
155 (
156 ActionDesc::EncoreDecline { slot: 2 },
157 ActionIdDesc {
158 family: "encore_decline",
159 params: vec![param("slot", ActionParamValue::Int(2))],
160 },
161 ),
162 (
163 ActionDesc::TriggerOrder { index: 5 },
164 ActionIdDesc {
165 family: "trigger_order",
166 params: vec![param("index", ActionParamValue::Int(5))],
167 },
168 ),
169 (
170 ActionDesc::ChoiceSelect { index: 3 },
171 ActionIdDesc {
172 family: "choice_select",
173 params: vec![param("index", ActionParamValue::Int(3))],
174 },
175 ),
176 (
177 ActionDesc::ChoicePrevPage,
178 ActionIdDesc {
179 family: "choice_prev_page",
180 params: vec![],
181 },
182 ),
183 (
184 ActionDesc::ChoiceNextPage,
185 ActionIdDesc {
186 family: "choice_next_page",
187 params: vec![],
188 },
189 ),
190 (
191 ActionDesc::Concede,
192 ActionIdDesc {
193 family: "concede",
194 params: vec![],
195 },
196 ),
197 ];
198
199 for (action, expected) in samples {
200 let id = action_id_for(&action).expect("id");
201 let decoded = decode_action_id(id).expect("decode");
202 assert_eq!(decoded, expected);
203 let back = action_desc_for_id(id).expect("back");
204 assert_eq!(back, action);
205 }
206 }
207}