weiss_core/encode/
mod.rs

1//! Observation/action encoding and spec helpers.
2//!
3//! Related docs:
4//! - <https://github.com/victorwp288/weiss-schwarz-simulator/blob/main/docs/README.md>
5//! - <https://github.com/victorwp288/weiss-schwarz-simulator/blob/main/docs/encodings.md>
6//! - <https://github.com/victorwp288/weiss-schwarz-simulator/blob/main/docs/encodings_changelog.md>
7
8mod 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}