Skip to main content

morpion_solitaire_records/
lib.rs

1//! A curated corpus of Morpion Solitaire record games, embedded at compile time.
2//!
3//! Each entry is `(display name, id, MSR record)`. The `id` is the corpus file
4//! stem (e.g. `"demaine31"`, matching `records/<variant>/demaine31.json`); the
5//! record is its JSON source — decode it with the
6//! [`morpion-solitaire-record`](https://crates.io/crates/morpion-solitaire-record)
7//! crate (which reads both the JSON and the compact `MS1:` forms):
8//!
9//! ```ignore
10//! for (name, id, record) in morpion_solitaire_records::RECORDS {
11//!     let game = msr::decode(record).unwrap();
12//!     assert!(msr::validate(&game).is_ok());
13//!     println!("{name} [{id}]: {} moves", game.moves.len());
14//! }
15//! ```
16//!
17//! The JSON files are the **source of truth**; the compact `.msr`, the rendered
18//! PNG/SVG (with the record embedded) and the Pentasol form are derived artifacts
19//! generated from them (see the workspace's `gen_record_artifacts` example),
20//! not committed. Being embedded, the corpus works without a filesystem (e.g. in
21//! WebAssembly).
22//!
23//! [MSR]: https://crates.io/crates/morpion-solitaire-record
24
25#![forbid(unsafe_code)]
26#![warn(missing_docs)]
27
28/// The record games, as `(display name, id, MSR JSON)` triples, best first. The
29/// `id` is the corpus file stem. All are legal, terminal games (5T unless noted);
30/// each carries its provenance in the record's `source`/`author`/`transcribed_by`
31/// fields. See the crate README.
32pub const RECORDS: &[(&str, &str, &str)] = &[
33    (
34        "Rosin 178",
35        "rosin178",
36        include_str!("../records/5T/rosin178.json"),
37    ),
38    (
39        "Rosin 177A",
40        "rosin177a",
41        include_str!("../records/5T/rosin177a.json"),
42    ),
43    (
44        "Rosin 177B",
45        "rosin177b",
46        include_str!("../records/5T/rosin177b.json"),
47    ),
48    (
49        "Tishchenko 172",
50        "tishchenko172",
51        include_str!("../records/5T/tishchenko172.json"),
52    ),
53    (
54        "Rosin 172",
55        "rosin172",
56        include_str!("../records/5T/rosin172.json"),
57    ),
58    (
59        "Tishchenko 171",
60        "tishchenko171",
61        include_str!("../records/5T/tishchenko171.json"),
62    ),
63    (
64        "Bruneau 170",
65        "bruneau170",
66        include_str!("../records/5T/bruneau170.json"),
67    ),
68    (
69        "Rosin 170A",
70        "rosin170a",
71        include_str!("../records/5T/rosin170a.json"),
72    ),
73    (
74        "Akiyama 146",
75        "akiyama146",
76        include_str!("../records/5T/akiyama146.json"),
77    ),
78    (
79        "Akiyama 145",
80        "akiyama145",
81        include_str!("../records/5T/akiyama145.json"),
82    ),
83    (
84        "Rosin 82 (5D)",
85        "rosin82",
86        include_str!("../records/5D/rosin82.json"),
87    ),
88    (
89        "Hyyrö–Poranen 62 (4T)",
90        "hyyroporanen62",
91        include_str!("../records/4T/hyyroporanen62.json"),
92    ),
93    (
94        "Demaine 56 (4T)",
95        "demaine56",
96        include_str!("../records/4T/demaine56.json"),
97    ),
98    (
99        "Hyyrö–Poranen 35 (4D)",
100        "hyyroporanen35",
101        include_str!("../records/4D/hyyroporanen35.json"),
102    ),
103    (
104        "Demaine 31 (4D)",
105        "demaine31",
106        include_str!("../records/4D/demaine31.json"),
107    ),
108];
109
110#[cfg(test)]
111mod tests {
112    use super::RECORDS;
113
114    /// Every embedded record decodes and is a legal game.
115    #[test]
116    fn all_records_decode_and_validate() {
117        assert!(!RECORDS.is_empty());
118        for (name, _id, record) in RECORDS {
119            let game = msr::decode(record).unwrap_or_else(|e| panic!("{name}: decode failed: {e}"));
120            msr::validate(&game).unwrap_or_else(|e| panic!("{name}: not a legal game: {e}"));
121        }
122    }
123}