sqrid/sqrid/
gridbool.rs

1// Copyright (C) 2021 Leandro Lisboa Penz <lpenz@lpenz.org>
2// This file is subject to the terms and conditions defined in
3// file 'LICENSE', which is part of this source code package.
4
5#![warn(missing_debug_implementations)]
6#![warn(missing_docs)]
7
8//! Space-optimized grid of booleans using bitmaps
9//!
10//! This submodule has the [`Gridbool`] type and the associated
11//! functionality.
12
13use std::fmt;
14use std::iter;
15use std::ops;
16
17use super::error::Error;
18use super::grid;
19use super::pos::Pos;
20use super::postrait::PosT;
21
22/// Assert const generic expressions inside `impl` blocks
23macro_rules! impl_assert {
24    ($label:ident; $x:expr $(,)?) => {
25        const $label: usize = 0 - !$x as usize;
26    };
27}
28
29/// Helper macro for Gridbool type creation.
30///
31/// More often than not we want to create a grid form an associated
32/// [`super::base::Sqrid`] type. This macros makes the process easier.
33///
34/// Example usage:
35/// ```
36/// type Sqrid = sqrid::sqrid_create!(3, 3, false);
37/// type Gridbool = sqrid::gridbool_create!(Sqrid);
38/// ```
39#[macro_export]
40macro_rules! gridbool_create {
41    ($sqrid: ty) => {
42        $crate::Gridbool<$crate::pos_create!($sqrid),
43        { ((<$sqrid>::XMAX as usize + 1) * (<$sqrid>::YMAX as usize + 1)).div_ceil(32) }>
44    };
45}
46
47/// Space-optimized grid of booleans using bitmaps
48///
49/// `Gridbool` uses an array of u32 to implement a [`Pos`]-indexable
50/// grid of booleans, balancing space and performance.
51///
52/// At the moment we need to provide the number of u32 WORDS to
53/// use due to rust generic const limitations. We are able to check
54/// that the value is appropriate, though.
55///
56/// We can use the [`gridbool_create`] macro to use a [`Pos`] as a
57/// source of these values.
58#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
59pub struct Gridbool<P: PosT, const WORDS: usize>([u32; WORDS], std::marker::PhantomData<P>);
60
61impl<P: PosT, const WORDS: usize> Gridbool<P, WORDS> {
62    // Create the _ASSERTS constant to check W * H == SIZE
63    // We have to instantiate it in all low-level constructors to
64    // actually perform the check.
65    impl_assert!(_ASSERTS;
66                 // WORDS is big enough to hold all bits:
67                 P::WIDTH * P::HEIGHT <= WORDS * 32 &&
68                 // WORDS is not bigger than necessary:
69                 P::WIDTH * P::HEIGHT >= WORDS * 32 - 32);
70    // Used in creation:
71    const WORD_FALSE: u32 = 0;
72    const WORD_TRUE: u32 = 0xFFFFFFFF;
73    // These are used to iterate over references:
74    const TRUE: bool = true;
75    const FALSE: bool = false;
76
77    /// Const Gridbool filled with `true` values.
78    pub const ALL_TRUE: Self = Self::repeat(true);
79    /// Const Gridbool filled with `false` values.
80    pub const ALL_FALSE: Self = Self::repeat(false);
81
82    /// Create a Gridbool filled with the provided `value`.
83    #[inline]
84    pub const fn repeat(value: bool) -> Self {
85        let _ = Self::_ASSERTS;
86        let v = if value {
87            Self::WORD_TRUE
88        } else {
89            Self::WORD_FALSE
90        };
91        Gridbool([v; WORDS], std::marker::PhantomData)
92    }
93
94    #[inline]
95    fn byte_bit(i: usize) -> (usize, u32) {
96        let byte = i / 32;
97        let bit = 0x80000000 >> (i % 32);
98        (byte, bit)
99    }
100
101    /// Set the provided [`Pos`] position to `true`.
102    #[inline]
103    pub fn set_t(&mut self, posref: &P) {
104        let (byte, bit) = Self::byte_bit(posref.to_usize());
105        self.0[byte] |= bit;
106    }
107
108    /// Set the provided [`Pos`] position to `false`.
109    #[inline]
110    pub fn set_f(&mut self, posref: &P) {
111        let (byte, bit) = Self::byte_bit(posref.to_usize());
112        self.0[byte] &= !bit;
113    }
114
115    /// Set the provided [`Pos`] position to `value`.
116    #[inline]
117    pub fn set(&mut self, posref: &P, value: bool) {
118        if value {
119            self.set_t(posref)
120        } else {
121            self.set_f(posref)
122        }
123    }
124
125    /// Return the value at position [`Pos`].
126    #[inline]
127    pub fn get(&self, posref: &P) -> bool {
128        let (byte, bit) = Self::byte_bit(posref.to_usize());
129        self.0[byte] & bit != 0
130    }
131
132    /// Consume self and returns the inner bitmap.
133    #[inline]
134    pub fn into_inner(self) -> [u32; WORDS] {
135        self.0
136    }
137
138    /// Return a reference to the inner bitmap; useful for testing.
139    #[inline]
140    pub fn as_inner(&self) -> &[u32; WORDS] {
141        &self.0
142    }
143
144    /// Return a mut reference to the inner bitmap; useful for testing.
145    #[inline]
146    pub fn as_inner_mut(&mut self) -> &mut [u32; WORDS] {
147        &mut self.0
148    }
149
150    /// Iterate over all `true`/`false` values in the `Gridbool`.
151    #[inline]
152    pub fn iter(&self) -> impl iter::Iterator<Item = bool> + '_ {
153        P::iter().map(|pos| self.get(&pos))
154    }
155
156    /// Iterate over all coordinates and corresponding `true`/`false` values.
157    #[inline]
158    pub fn iter_pos(&self) -> impl iter::Iterator<Item = (P, bool)> + '_
159    where
160        P: Copy,
161    {
162        P::iter().map(move |pos| (pos, { self[pos] }))
163    }
164
165    /// Iterate over all `true` coordinates the `Gridbool`.
166    #[inline]
167    pub fn iter_t(&self) -> impl Iterator<Item = P> + '_ {
168        P::iter().filter(move |pos| self[pos])
169    }
170
171    /// Iterate over all `false` coordinates the `Gridbool`.
172    #[inline]
173    pub fn iter_f(&self) -> impl Iterator<Item = P> + '_ {
174        P::iter().filter(move |pos| !self[pos])
175    }
176
177    /// Take a [`Pos`] iterator and set all corresponding values to `true`.
178    #[inline]
179    pub fn set_iter_t(&mut self, positer: impl Iterator<Item = P>) {
180        for pos in positer {
181            self.set_t(&pos);
182        }
183    }
184
185    /// Take a [`Pos`] iterator and set all corresponding values to `false`.
186    #[inline]
187    pub fn set_iter_f(&mut self, positer: impl Iterator<Item = P>) {
188        for pos in positer {
189            self.set_f(&pos);
190        }
191    }
192
193    /// Flip all elements horizontally.
194    pub fn flip_h(&mut self) {
195        for y in P::iter_y() {
196            for x in 0..P::width() / 2 {
197                let pos1 = P::new(x, y).unwrap();
198                let pos2 = pos1.flip_h();
199                let tmp = self.get(&pos1);
200                self.set(&pos1, self.get(&pos2));
201                self.set(&pos2, tmp);
202            }
203        }
204    }
205
206    /// Flip all elements vertically.
207    pub fn flip_v(&mut self) {
208        for y in 0..P::height() / 2 {
209            for x in P::iter_x() {
210                let pos1 = P::new(x, y).unwrap();
211                let pos2 = pos1.flip_v();
212                let tmp = self.get(&pos1);
213                self.set(&pos1, self.get(&pos2));
214                self.set(&pos2, tmp);
215            }
216        }
217    }
218
219    /// Create a Gridbool from an iterator that yields the right amount of booleans.
220    pub fn from_iter_bool<It>(iter: It) -> Result<Self, Error>
221    where
222        It: iter::IntoIterator<Item = bool>,
223    {
224        let mut gb = Self::ALL_FALSE;
225        let mut it = iter.into_iter();
226        for pos in P::iter() {
227            if let Some(value) = it.next() {
228                gb.set(&pos, value);
229            } else {
230                return Err(Error::IteratorTooShort(pos.to_usize(), P::dimensions()));
231            }
232        }
233        if it.next().is_some() {
234            return Err(Error::IteratorTooLong(P::dimensions()));
235        }
236        Ok(gb)
237    }
238}
239
240// Rotations are only available for "square" gridbools
241impl<const XYMAX: u16, const WORDS: usize> Gridbool<Pos<XYMAX, XYMAX>, WORDS> {
242    /// Rotate all elements 90 degrees clockwise
243    pub fn rotate_cw(&mut self) {
244        for y in 0..XYMAX / 2 {
245            for x in y..XYMAX - y {
246                let pos0 = Pos::<XYMAX, XYMAX>::try_from((x, y)).unwrap();
247                let pos1 = pos0.rotate_cw();
248                let pos2 = pos1.rotate_cw();
249                let pos3 = pos2.rotate_cw();
250                let values = [
251                    self.get(&pos0),
252                    self.get(&pos1),
253                    self.get(&pos2),
254                    self.get(&pos3),
255                ];
256                self.set(&pos0, values[3]);
257                self.set(&pos1, values[0]);
258                self.set(&pos2, values[1]);
259                self.set(&pos3, values[2]);
260            }
261        }
262    }
263    /// Rotate all elements 90 degrees counterclockwise
264    pub fn rotate_cc(&mut self) {
265        for y in 0..XYMAX / 2 {
266            for x in y..XYMAX - y {
267                let pos0 = Pos::<XYMAX, XYMAX>::try_from((x, y)).unwrap();
268                let pos1 = pos0.rotate_cw();
269                let pos2 = pos1.rotate_cw();
270                let pos3 = pos2.rotate_cw();
271                let values = [
272                    self.get(&pos0),
273                    self.get(&pos1),
274                    self.get(&pos2),
275                    self.get(&pos3),
276                ];
277                self.set(&pos0, values[1]);
278                self.set(&pos1, values[2]);
279                self.set(&pos2, values[3]);
280                self.set(&pos3, values[0]);
281            }
282        }
283    }
284}
285
286impl<P: PosT, const WORDS: usize> Default for Gridbool<P, WORDS> {
287    fn default() -> Self {
288        Self::ALL_FALSE
289    }
290}
291
292// Indexing
293
294impl<P: PosT, const WORDS: usize> ops::Index<&P> for Gridbool<P, WORDS> {
295    type Output = bool;
296    #[inline]
297    fn index(&self, pos: &P) -> &Self::Output {
298        // Trick to be able to return reference to boolean as required
299        // by trait:
300        if self.get(pos) {
301            &Self::TRUE
302        } else {
303            &Self::FALSE
304        }
305    }
306}
307
308impl<P: PosT, const WORDS: usize> ops::Index<P> for Gridbool<P, WORDS> {
309    type Output = bool;
310    #[inline]
311    fn index(&self, pos: P) -> &Self::Output {
312        // Trick to be able to return reference to boolean as required
313        // by trait:
314        if self.get(&pos) {
315            &Self::TRUE
316        } else {
317            &Self::FALSE
318        }
319    }
320}
321
322// from_iter
323
324impl<P: PosT, const WORDS: usize> iter::FromIterator<P> for Gridbool<P, WORDS> {
325    #[inline]
326    fn from_iter<I>(iter: I) -> Self
327    where
328        I: iter::IntoIterator<Item = P>,
329    {
330        let mut gb = Gridbool::<P, WORDS>::ALL_FALSE;
331        gb.set_iter_t(iter.into_iter());
332        gb
333    }
334}
335
336impl<P: PosT, const WORDS: usize> iter::FromIterator<bool> for Gridbool<P, WORDS> {
337    #[inline]
338    fn from_iter<I>(iter: I) -> Self
339    where
340        I: iter::IntoIterator<Item = bool>,
341    {
342        match Self::from_iter_bool(iter) {
343            Ok(gb) => gb,
344            Err(e) => {
345                panic!("{}", e);
346            }
347        }
348    }
349}
350
351// display
352
353impl<P: PosT, const WORDS: usize> fmt::Display for Gridbool<P, WORDS> {
354    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
355        grid::display_fmt_helper(
356            f,
357            P::width(),
358            P::height(),
359            self.iter().map(|b| (if b { "#" } else { "." }).to_string()),
360        )
361    }
362}