ogs/
game.rs

1use crate::{BitSet, SolverEvent};
2
3use std::{iter::FusedIterator, str::FromStr};
4
5#[derive(Default, Clone)]
6pub struct Game {
7    pub taking_all: [u64; 4],   // set
8    pub taking: Vec<u8>,
9    pub breaking: Vec<u8>,
10    //breaking_even: Vec<u8>,
11    //breaking_odd: Vec<u8>
12}
13
14impl Game {
15    #[inline] fn parse_octal(c: u8) -> Option<u8> {
16        (b'0' <= c && c <= b'8').then(|| c - b'0')
17    }
18
19    pub fn from_ascii(mut s: &[u8]) -> Option<Game> {
20        let mut result = Self::default();
21        if s.starts_with(b"4.") || s.starts_with(b"4,") || s.starts_with(b"4d") {
22            result.breaking.push(0);
23            s = &s[2..];
24        } else if s.starts_with(b"0.") || s.starts_with(b"0,") || s.starts_with(b"0d") {
25            s = &s[2..];
26        } else if s.starts_with(b".") || s.starts_with(b",") || s.starts_with(b"d") {
27            s = &s[1..];
28        }
29        let mut position = 0u8;
30        for c in s {
31            position = position.checked_add(1)?;
32            let oct = Self::parse_octal(*c)?;
33            if oct & 1 != 0 { result.taking_all.add_nimber(position as u16) }
34            if oct & 2 != 0 { result.taking.push(position) }
35            if oct & 4 != 0 { result.breaking.push(position) }
36        }
37        Some(result)
38    }
39
40    #[inline] pub fn can_take_all(&self, n: usize) -> bool {
41        self.taking_all.try_get_bit(n).unwrap_or(false)
42    }
43
44    pub(crate) fn consider_taking<S: SolverEvent>(&self, nimbers: &[u16], option_nimbers: &mut [u64; 1<<(16-6)], stats: &mut S) {
45        let n = nimbers.len();
46        if self.can_take_all(n) { option_nimbers.add_nimber(0) }
47        for t in &self.taking {
48            let t = *t as usize;
49            if t >= n { break }
50            option_nimbers.add_nimber(nimbers[n-t]);
51            stats.take_option();
52        }
53    }
54
55    #[inline] pub fn breaking_moves(&self, n: usize) -> BreakingMoveIterator<std::iter::Copied<std::slice::Iter<'_, u8>>> {
56        //BreakingMoveIterator::for_iter(n, self.breaking.iter().copied())
57        BreakingMoveIterator::for_slice(n, self.breaking.as_slice())
58    }
59
60    /// Returns rules as a sequence of octal numbers.
61    pub fn rules(&self) -> [u8; 256] {
62        let mut result = [0; 256];
63        for a in 0..256 {
64            if self.taking_all.get_bit(a) {
65                result[a] |= 1;
66            }
67        }
68        for t in &self.taking { result[*t as usize] |= 2; }
69        for b in &self.breaking { result[*b as usize] |= 4; }
70        result
71    }
72
73    /// Returns rules as an ascii string, using given decimal separator (for example `b'.'`).
74    pub fn to_ascii(&self, separator: u8) -> Vec<u8> {
75        let rules = self.rules();
76        let mut number_of_rules = 0;
77        for (i, r) in rules.iter().enumerate() {
78            if *r != 0 { number_of_rules = i; }
79        }
80        number_of_rules += 1;
81        let mut result = Vec::with_capacity(number_of_rules);
82        result.push(rules[0] + b'0');
83        result.push(separator);
84        for r in 1..number_of_rules {
85            result.push(rules[r] + b'0');
86        }
87        result
88    }
89
90    /// Returns the total number of taking iterations needed (by any of the methods: naive, RC or RC2)
91    /// to calculate the nimbers of all positions up to and including the one given.
92    pub fn taking_iters(&self, position: usize) -> usize {
93        self.taking.iter().map(|t| position.saturating_sub(*t as usize)).sum()
94    }
95
96    /// Returns the total number of breaking iterations needed by the naive method
97    /// to calculate the nimbers of all positions up to and including the one specified.
98    pub fn breaking_naive_iters(&self, position: usize) -> usize {
99        self.breaking.iter().map(|b| {
100            let b = *b as usize;
101            if position < b + 2 { 0 } else
102            if position & 1 != b & 1 { let k = (position - b - 1) / 2; k*k+k } // difference is odd
103            else { let hd = (position - b) / 2; let k = hd-1; k*k+k + hd }  // difference is even
104        }).sum()
105    }
106
107    /// Returns the total number of iterations needed by the naive method
108    /// to calculate the nimbers of all positions up to and including the one specified.
109    pub fn naive_iters(&self, position: usize) -> usize {
110        self.taking_iters(position) + self.breaking_naive_iters(position)
111    }
112
113    /// Tries to calculates (pre-period, period) of the game using the nimbers of its few first positions.
114    /// 
115    /// Uses a generalized version of a theorem from the paper:
116    /// Richard K. Guy and Cedric A. B. Smith, The G-values of various games, 1956
117    pub fn period(&self, nimbers: &[u16]) -> Option<(usize, usize)> {
118        let mut max_to_take = 
119            (self.taking_all.msb_index().unwrap_or(0) as u8)
120            .max(*self.taking.last().unwrap_or(&0));
121
122        let log2mult = if let Some(b) = self.breaking.last() {   // has breaking moves?
123            max_to_take = max_to_take.max(*b);
124            1   // we need *2 (or <<1) for octal games
125        } else { 0 };   // and *1 (or <<0) otherwise
126
127        let len = nimbers.len();
128        for period in 1 ..= (len >> log2mult) {
129            let mut preperiod = len - period;
130            while preperiod > 0 && nimbers[preperiod-1] == nimbers[preperiod-1+period] {
131                preperiod -= 1;
132            }
133            //if len >= (2*preperiod + 2*period + max_to_take - 1).max(max_to_take+2) {
134            if len >= ((preperiod + period) << log2mult) + max_to_take as usize {
135                return Some((preperiod, period));
136            }
137        }
138        None
139    }
140}
141
142impl FromStr for Game {
143    type Err = &'static str;
144    
145    fn from_str(s: &str) -> Result<Self, Self::Err> {
146        Self::from_ascii(s.as_bytes()).ok_or("game description must be in format [X.]OOO... where X is 0 or 4 and (up to 255) Os are octal digits")
147    }
148}
149
150impl ToString for Game {
151    fn to_string(&self) -> String {
152        unsafe{ String::from_utf8_unchecked(self.to_ascii(b'.')) }
153    }
154}
155
156/*impl Display for Game {
157    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
158        write!(f, "{}", self.to_string())
159    }
160}*/
161
162pub struct BreakingMoveIterator<I> {
163    current: I,
164    n: usize,
165    after_take: usize,
166    last_i: usize,
167    i: usize
168}
169
170impl<I> BreakingMoveIterator<I> {
171    #[inline] pub fn for_iter(n: usize, to_take_iterator: I) -> Self {
172        Self { current: to_take_iterator, n, after_take: 0, last_i: 0, i: 0 }
173    }
174}
175
176impl<'a, IU: Into<usize> + Copy> BreakingMoveIterator<std::iter::Copied<std::slice::Iter<'a, IU>>> {
177    #[inline] pub fn for_slice(n: usize, slice: &'a [IU]) -> Self {
178        Self::for_iter(n, slice.iter().copied())
179    }
180}
181
182impl<IU: Into<usize>, I: Iterator<Item = IU>> Iterator for BreakingMoveIterator<I> {
183    type Item = (usize, usize);
184
185    fn next(&mut self) -> Option<Self::Item> {
186        if self.i == self.last_i {
187            let b = self.current.next()?.into();
188            if b+1 >= self.n { return None; } // n-b >= 2  <=>  !(2 > n-b)  <=>  !(b+2 > n)  <=>  !(b+1 >= n)
189            self.after_take = self.n - b;   // >= 2
190            self.last_i = self.after_take / 2;  // >= 1
191            self.i = 1;
192        } else {
193            self.i += 1;
194        }
195        Some((self.i, self.after_take - self.i))
196    }
197}
198
199impl<IU: Into<usize>, I: Iterator<Item = IU> + FusedIterator> FusedIterator for BreakingMoveIterator<I>{}
200
201
202#[cfg(test)]
203pub(crate) mod tests {
204    use super::*;
205
206    #[test]
207    fn test_4d() {
208        let g = Game::from_str("4.").unwrap();
209        assert_eq!(g.breaking_moves(0).collect::<Vec<_>>(), vec![]);
210        assert_eq!(g.breaking_moves(1).collect::<Vec<_>>(), vec![]);
211        assert_eq!(g.breaking_moves(2).collect::<Vec<_>>(), vec![(1, 1)]);
212        assert_eq!(g.breaking_moves(3).collect::<Vec<_>>(), vec![(1, 2)]);
213        assert_eq!(g.breaking_moves(4).collect::<Vec<_>>(), vec![(1, 3), (2, 2)]);
214        assert_eq!(g.breaking_moves(5).collect::<Vec<_>>(), vec![(1, 4), (2, 3)]);
215    }
216
217    #[test]
218    fn test_0d44() {
219        let g = Game::from_str("0.44").unwrap();
220        assert_eq!(g.breaking_moves(0).collect::<Vec<_>>(), vec![]);
221        assert_eq!(g.breaking_moves(1).collect::<Vec<_>>(), vec![]);
222        assert_eq!(g.breaking_moves(2).collect::<Vec<_>>(), vec![]);
223        assert_eq!(g.breaking_moves(3).collect::<Vec<_>>(), vec![(1, 1)]);
224        assert_eq!(g.breaking_moves(4).collect::<Vec<_>>(), vec![(1, 2), (1, 1)]);
225        assert_eq!(g.breaking_moves(5).collect::<Vec<_>>(), vec![(1, 3), (2, 2), (1, 2)]);
226    }
227}