lysogeny_broth/
lib.rs

1//! `lysogeny-broth` provides data-structures and functions
2//! to implement Cellular Automata.
3//! The grid is of toroidal shape, i.e. the coordinate
4//! values/neighbours wrap around. It also uses a statically
5//! allocated grid to sidestep the need for dynamic memory
6//! management.
7//! This code is dual-licensed under the MIT and Apache 2.0 licenses.
8// SPDX-License-Identifier: MIT AND Apache-2.0
9/*
10Copyright (c) 2021 tpltnt
11
12Permission is hereby granted, free of charge, to any person obtaining a copy
13of this software and associated documentation files (the "Software"), to deal
14in the Software without restriction, including without limitation the rights
15to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
16copies of the Software, and to permit persons to whom the Software is
17furnished to do so, subject to the following conditions:
18
19The above copyright notice and this permission notice shall be included in all
20copies or substantial portions of the Software.
21
22THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
23IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
24FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
25AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
26LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
27OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
28SOFTWARE.
29
30
31Copyright 2021 tpltnt
32
33Licensed under the Apache License, Version 2.0 (the "License");
34you may not use this file except in compliance with the License.
35You may obtain a copy of the License at
36
37http://www.apache.org/licenses/LICENSE-2.0
38
39Unless required by applicable law or agreed to in writing, software
40distributed under the License is distributed on an "AS IS" BASIS,
41WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
42See the License for the specific language governing permissions and
43limitations under the License.
44 */
45#![no_std]
46#![warn(missing_docs)]
47#![warn(clippy::missing_docs_in_private_items)]
48
49/// Tweak here for vertical grid size / memory usage.
50/// `u8` was chosen to accommodate memory constraints.
51const VERTICAL_MAX: usize = u8::MAX as usize;
52/// Tweak here for horizontal grid size / memory usage.
53/// `u8` was chosen to accommodate memory constraints.
54const HORIZONTAL_MAX: usize = u8::MAX as usize;
55
56/// The state of a cell.
57///
58/// # Remarks
59/// A cell has no concept of its neighbours. Everything
60/// in terms of space is handled by the Grid.
61#[derive(Copy, Clone, Debug, PartialEq)]
62#[cfg(not(feature = "dead-alive-only"))]
63pub enum CellState {
64    Dummy,
65}
66
67/// The state of a cell. In this case it is either
68/// dead or alive.
69///
70/// # Remarks
71/// A cell has no concept of its neighbours. Everything
72/// in terms of space is handled by the Grid.
73#[derive(Copy, Clone, Debug, PartialEq)]
74#[cfg(feature = "dead-alive-only")]
75pub enum CellState {
76    /// represents a dead cell
77    Dead,
78    /// represents a living cell
79    Alive,
80}
81
82impl CellState {
83    #[cfg(feature = "dead-alive-into-bool")]
84    /// If cells can be either alive or dead, then
85    /// their state could be converted into a boolean
86    /// value where true means 'alive' (i.e. the cell
87    /// is there) and false means 'dead') (i.e. no cell).
88    pub fn into_bool(self) -> bool {
89        if self == CellState::Dead {
90            return false;
91        }
92        true
93    }
94}
95
96#[cfg(feature = "dead-alive-u8-utils")]
97/// Convert eight binary cell states into a u8 / octet.
98/// A dead cell becomes a 0, an alive one a 1.
99pub fn cs8_into_u8(cs: [&CellState; 8]) -> u8 {
100    let mut rdata: u8 = 0b00000000;
101    for s in cs.iter() {
102        rdata = rdata.rotate_left(1);
103        if s == &&CellState::Alive {
104            // set bit
105            let bit: u8 = 0b00000001;
106            rdata = rdata | bit;
107        }
108    }
109    return rdata;
110}
111
112#[cfg(feature = "dead-alive-u8-utils")]
113/// Convert eight binary cell states into a u8 / octet.
114/// A dead cell becomes a 0, an alive one a 1.
115pub fn u8_into_cs8(bits: u8) -> [&'static CellState; 8] {
116    let mut mask: u8 = 0b00000001;
117    let mut rdata = [
118        &CellState::Dead,
119        &CellState::Dead,
120        &CellState::Dead,
121        &CellState::Dead,
122        &CellState::Dead,
123        &CellState::Dead,
124        &CellState::Dead,
125        &CellState::Dead,
126    ];
127    for i in 0..8 {
128        mask = mask.rotate_right(1);
129        let bit = mask & bits;
130        if bit == 0 {
131            rdata[i] = &CellState::Dead;
132        } else {
133            rdata[i] = &CellState::Alive;
134        }
135    }
136    return rdata;
137}
138
139/// A structure to encode a grid with cells.
140/// Cell positions start at the top left corner.
141/// The grid handles everything in terms of space.
142/// The retrieval methods for different neighbours
143/// allow for the implementation of different
144/// neighbourhoods (e.g. von Neumann, Moore ...) or
145/// even arbitrary functions to determine the new
146/// value of a cell based on its adjacent cells
147/// (or even state of the whole grid).
148#[derive(Copy, Clone, Debug)]
149pub struct Grid {
150    /// Allow size allows for 256 horizontal cells.
151    /// This is good enough for embedded environments.
152    /// If you need for more adjust the data types as needed.
153    horizontal_size: u8,
154    /// Allow size allows for 256 vertical cells.
155    /// This is good enough for embedded environments.
156    /// If you need for more adjust the data types as needed.
157    vertical_size: u8,
158    /// The actual arrays to hold cell states.
159    cells: [[CellState; HORIZONTAL_MAX]; VERTICAL_MAX],
160    /// The vertical position of the cell iterator.
161    horizontal_cell_iterator_index: usize,
162    /// The horizontal position of the cell iterator.
163    vertical_cell_iterator_index: usize,
164    /// The vertical position of the cell iterator for byte export.
165    horizontal_byte_iterator_index: usize,
166    /// The horizontal position of the cell iterator for byte export.
167    vertical_byte_iterator_index: usize,
168}
169
170impl Grid {
171    /// Create a new grid with the given dimensions and
172    /// fill it with default (dead) cells.
173    ///
174    /// # Arguments
175    /// * `h_size`: horizontal dimension/size as number of cells
176    /// * `v_size`: vertical dimension/size as number of cells
177    ///
178    /// # Remarks
179    ///
180    /// `u8` was chosen to stay below `usize::MAX` for a `u8` x `u8`
181    /// grid. 256x256 are currently enough cells for embedded applications.
182    /// Larger grid sizes have to keep the target usize (thus architecture)
183    /// in mind and can be adjusted appropriately.
184    pub fn new(h_size: u8, v_size: u8) -> Grid {
185        if h_size == 0 {
186            panic!("horizontal coordinate too small")
187        }
188        if v_size == 0 {
189            panic!("vertical coordinate too small")
190        }
191        if h_size as usize > HORIZONTAL_MAX {
192            panic!("horizontal coordinate too large")
193        }
194        if v_size as usize > VERTICAL_MAX {
195            panic!("vertical coordinate too large")
196        }
197
198        Grid {
199            horizontal_size: h_size,
200            vertical_size: v_size,
201            horizontal_cell_iterator_index: 0,
202            vertical_cell_iterator_index: 0,
203            horizontal_byte_iterator_index: 0,
204            vertical_byte_iterator_index: 0,
205            #[cfg(not(feature = "dead-alive-only"))]
206            cells: [[CellState::Dummy; HORIZONTAL_MAX]; VERTICAL_MAX],
207            #[cfg(feature = "dead-alive-only")]
208            cells: [[CellState::Dead; HORIZONTAL_MAX]; VERTICAL_MAX],
209        }
210    }
211
212    /// Get the number of columns (i.e. horizontal size)
213    pub fn get_horizontal_size(&self) -> u8 {
214        self.horizontal_size
215    }
216
217    /// Get number of rows (i.e. vertical size)
218    pub fn get_vertical_size(&self) -> u8 {
219        self.vertical_size
220    }
221
222    /// Retrieve a cell state (for modification).
223    ///
224    /// # Arguments
225    /// * `h`: horizontal coordinate
226    /// * `v`: vertical coordinate
227    pub fn get_cellstate(&self, h: u8, v: u8) -> &CellState {
228        if h >= self.horizontal_size {
229            panic!("horizontal coordinate too large")
230        }
231        if v >= self.vertical_size {
232            panic!("vertical coordinate too large")
233        }
234        &self.cells[h as usize][v as usize]
235    }
236
237    /// Retrieve a cell state (for modification) using a coordinate tuple.
238    ///
239    /// # Arguments
240    /// * `hv`: tuple (horizontal coordinate, vertical coordinate)
241    pub fn get_cellstate_hv(&self, hv: (u8, u8)) -> &CellState {
242        self.get_cellstate(hv.0, hv.1)
243    }
244
245    /// Set a (modified) cell state.
246    ///
247    /// # Arguments
248    /// * `h`: horizontal coordinate
249    /// * `v`: vertical coordinate
250    pub fn set_cellstate(&mut self, h: u8, v: u8, state: CellState) {
251        if h >= self.horizontal_size {
252            panic!("horizontal coordinate too large")
253        }
254        if v >= self.vertical_size {
255            panic!("vertical coordinate too large")
256        }
257        self.cells[h as usize][v as usize] = state;
258    }
259
260    /// Set a (modified) cell state using a coordination tuple.
261    ///
262    /// # Arguments
263    /// * `hv`: tuple (horizontal coordinate, vertical coordinate)
264    pub fn set_cellstate_hv(&mut self, hv: (u8, u8), state: CellState) {
265        self.set_cellstate(hv.0, hv.1, state)
266    }
267
268    /// Get coordinates of "northern" cell relative
269    /// to the given grid coordinates.
270    ///
271    /// # Arguments
272    /// * `h`: horizontal coordinate
273    /// * `v`: vertical coordinate
274    pub fn get_north_coordinate(&self, h: u8, v: u8) -> (u8, u8) {
275        if h >= self.horizontal_size {
276            panic!("horizontal coordinate too large")
277        }
278        if v >= self.vertical_size {
279            panic!("vertical coordinate too large")
280        }
281        if v == 0 {
282            return (h, self.vertical_size - 1);
283        }
284        (h, v - 1)
285    }
286
287    /// Get coordinates of "northern" cell relative
288    /// to the given grid coordinates.
289    ///
290    /// # Arguments
291    /// * `hv`: tuple (horizontal coordinate, vertical coordinate)
292    pub fn get_north_coordinate_hv(&self, hv: (u8, u8)) -> (u8, u8) {
293        self.get_north_coordinate(hv.0, hv.1)
294    }
295
296    /// Get coordinates of "eastern" cell relative
297    /// to the given grid coordinates.
298    ///
299    /// # Arguments
300    /// * `h`: horizontal coordinate
301    /// * `v`: vertical coordinate
302    pub fn get_east_coordinate(&self, h: u8, v: u8) -> (u8, u8) {
303        if h >= self.horizontal_size {
304            panic!("horizontal coordinate too large")
305        }
306        if v >= self.vertical_size {
307            panic!("vertical coordinate too large")
308        }
309        if h == self.horizontal_size - 1 {
310            return (0, v);
311        }
312        (h + 1, v)
313    }
314
315    /// Get coordinates of "eastern" cell relative
316    /// to the given grid coordinates.
317    ///
318    /// # Arguments
319    /// * `hv`: tuple (horizontal coordinate, vertical coordinate)
320    pub fn get_east_coordinate_hv(&self, hv: (u8, u8)) -> (u8, u8) {
321        self.get_east_coordinate(hv.0, hv.1)
322    }
323
324    /// Get coordinates of "southern" cell relative
325    /// to the given grid coordinates.
326    ///
327    /// # Arguments
328    /// * `h`: horizontal coordinate
329    /// * `v`: vertical coordinate
330    pub fn get_south_coordinate(&self, h: u8, v: u8) -> (u8, u8) {
331        if h >= self.horizontal_size {
332            panic!("horizontal coordinate too large")
333        }
334        if v >= self.vertical_size {
335            panic!("vertical coordinate too large")
336        }
337        if v == self.vertical_size - 1 {
338            return (h, 0);
339        }
340        (h, v + 1)
341    }
342
343    /// Get coordinates of "eastern" cell relative
344    /// to the given grid coordinates.
345    ///
346    /// # Arguments
347    /// * `hv`: tuple (horizontal coordinate, vertical coordinate)
348    pub fn get_south_coordinate_hv(&self, hv: (u8, u8)) -> (u8, u8) {
349        self.get_south_coordinate(hv.0, hv.1)
350    }
351
352    /// Get coordinates of "western" cell relative
353    /// to the given grid coordinates.
354    ///
355    /// # Arguments
356    /// * `h`: horizontal coordinate
357    /// * `v`: vertical coordinate
358    pub fn get_west_coordinate(&self, h: u8, v: u8) -> (u8, u8) {
359        if h >= self.horizontal_size {
360            panic!("horizontal coordinate too large")
361        }
362        if v >= self.vertical_size {
363            panic!("vertical coordinate too large")
364        }
365        if h == 0 {
366            return (self.horizontal_size - 1, v);
367        }
368        (h - 1, v)
369    }
370
371    /// Get coordinates of "western" cell relative
372    /// to the given grid coordinates.
373    ///
374    /// # Arguments
375    /// * `hv`: tuple (horizontal coordinate, vertical coordinate)
376    pub fn get_west_coordinate_hv(&self, hv: (u8, u8)) -> (u8, u8) {
377        self.get_west_coordinate(hv.0, hv.1)
378    }
379
380    /// Get coordinates of "north eastern" cell relative
381    /// to the given grid coordinates.
382    ///
383    /// # Arguments
384    /// * `h`: horizontal coordinate
385    /// * `v`: vertical coordinate
386    pub fn get_northeast_coordinate(&self, h: u8, v: u8) -> (u8, u8) {
387        self.get_north_coordinate_hv(self.get_east_coordinate(h, v))
388    }
389
390    /// Get coordinates of "north eastern" cell relative
391    /// to the given grid coordinates.
392    ///
393    /// # Arguments
394    /// * `hv`: tuple (horizontal coordinate, vertical coordinate)
395    pub fn get_northeast_coordinate_hv(&self, hv: (u8, u8)) -> (u8, u8) {
396        self.get_north_coordinate_hv(self.get_east_coordinate(hv.0, hv.1))
397    }
398
399    /// Get coordinates of "south eastern" cell relative
400    /// to the given grid coordinates.
401    ///
402    /// # Arguments
403    /// * `h`: horizontal coordinate
404    /// * `v`: vertical coordinate
405    pub fn get_southeast_coordinate(&self, h: u8, v: u8) -> (u8, u8) {
406        self.get_south_coordinate_hv(self.get_east_coordinate(h, v))
407    }
408
409    /// Get coordinates of "south eastern" cell relative
410    /// to the given grid coordinates.
411    ///
412    /// # Arguments
413    /// * `hv`: tuple (horizontal coordinate, vertical coordinate)
414    pub fn get_southeast_coordinate_hv(&self, hv: (u8, u8)) -> (u8, u8) {
415        self.get_south_coordinate_hv(self.get_east_coordinate(hv.0, hv.1))
416    }
417
418    /// Get coordinates of "south western" cell relative
419    /// to the given grid coordinates.
420    ///
421    /// # Arguments
422    /// * `h`: horizontal coordinate
423    /// * `v`: vertical coordinate
424    pub fn get_southwest_coordinate(&self, h: u8, v: u8) -> (u8, u8) {
425        self.get_south_coordinate_hv(self.get_west_coordinate(h, v))
426    }
427
428    /// Get coordinates of "south western" cell relative
429    /// to the given grid coordinates.
430    ///
431    /// # Arguments
432    /// * `hv`: tuple (horizontal coordinate, vertical coordinate)
433    pub fn get_southwest_coordinate_hv(&self, hv: (u8, u8)) -> (u8, u8) {
434        self.get_south_coordinate_hv(self.get_west_coordinate(hv.0, hv.1))
435    }
436
437    /// Get coordinates of "north western" cell relative
438    /// to the given grid coordinates.
439    ///
440    /// # Arguments
441    /// * `h`: horizontal coordinate
442    /// * `v`: vertical coordinate
443    pub fn get_northwest_coordinate(&self, h: u8, v: u8) -> (u8, u8) {
444        self.get_north_coordinate_hv(self.get_west_coordinate(h, v))
445    }
446
447    /// Get coordinates of "north western" cell relative
448    /// to the given grid coordinates.
449    ///
450    /// # Arguments
451    /// * `hv`: tuple (horizontal coordinate, vertical coordinate)
452    pub fn get_northwest_coordinate_hv(&self, hv: (u8, u8)) -> (u8, u8) {
453        self.get_north_coordinate_hv(self.get_west_coordinate(hv.0, hv.1))
454    }
455
456    /// Go over the grid row by row and return eight
457    /// cell states as a byte (dead = 0, alive = 1).
458    /// This is an iterator-like utility function.
459    #[cfg(feature = "dead-alive-only")]
460    pub fn next_byte(&mut self) -> Option<u8> {
461        //TODO: check if cell count is a multiple of 8 -> compilation error otherwise
462        let mut rbyte = 0x00u8; // byte to return
463
464        for offset in 0..8 as u8 {
465            if self.horizontal_byte_iterator_index >= self.horizontal_size as usize {
466                // we stepped over the end of a row so ...
467                // ... we go to the next row ...
468                self.vertical_byte_iterator_index += 1;
469                // ... and reset the index pointer
470                self.horizontal_byte_iterator_index = 0;
471            }
472            if self.vertical_byte_iterator_index >= self.vertical_size as usize {
473                return None;
474            }
475
476            match self.get_cellstate(
477                self.horizontal_byte_iterator_index as u8,
478                self.vertical_byte_iterator_index as u8,
479            ) {
480                CellState::Alive => {
481                    // set bit according to offset
482                    rbyte = rbyte | (0x80u8 >> offset);
483                }
484                CellState::Dead => {}
485            }
486            self.horizontal_byte_iterator_index += 1;
487        }
488        Some(rbyte)
489    }
490}
491
492impl Iterator for Grid {
493    type Item = CellState;
494
495    /// Go over the grid along the rows and return
496    /// the respective state if a cell. This function
497    /// is the basis for the iterator trait.
498    fn next(&mut self) -> Option<CellState> {
499        // check if we are at the end of a row
500        if self.horizontal_cell_iterator_index >= self.horizontal_size as usize {
501            // yes we are, so we go to the next row
502            self.vertical_cell_iterator_index += 1;
503            // and reset the index
504            self.horizontal_cell_iterator_index = 0;
505        }
506        // if we are past the last row
507        if self.vertical_cell_iterator_index >= self.vertical_size as usize {
508            // the iteration stops
509            return None;
510        }
511
512        // get what we want to return
513        let rdata = Some(*self.get_cellstate(
514            self.horizontal_cell_iterator_index as u8,
515            self.vertical_cell_iterator_index as u8,
516        ));
517        // increment tracking indexes
518        self.horizontal_cell_iterator_index += 1;
519
520        rdata
521    }
522}
523
524/// A universe contains everything you need to enable
525/// Cellular Automata to do their thing.
526#[derive(Copy, Clone)]
527pub struct Universe {
528    /// The current state of the grid.
529    pub grid: Grid,
530    /// Temporary internal grid to calculate new state.
531    shadow: Grid,
532    /// The transformation function / cellular automaton.
533    automaton: fn(u8, u8, &Grid) -> CellState,
534}
535
536impl Universe {
537    /// Create a new universe with only dead cells.
538    ///
539    /// # Arguments
540    /// * `h_size`: horizontal dimension/size as number of cells
541    /// * `v_size`: vertical dimension/size as number of cells
542    /// * `rules`: a function mapping a coordinate (and thus the state of a cell) on a grid to a new state
543    pub fn new(h_size: u8, v_size: u8, rules: fn(u8, u8, &Grid) -> CellState) -> Universe {
544        Universe {
545            grid: Grid::new(h_size, v_size),
546            shadow: Grid::new(h_size, v_size),
547            automaton: rules,
548        }
549    }
550
551    /// Update the universe according to the given state and rules
552    pub fn update(&mut self) {
553        // calculate new state from original grid and
554        // (temporarily) save in shadow grid
555        for h in 0..self.grid.horizontal_size {
556            for v in 0..self.grid.vertical_size {
557                let state = (self.automaton)(h, v, &self.grid);
558                self.shadow.set_cellstate(h, v, state);
559            }
560        }
561
562        // copy over new (shadow) state to public grid
563        //self.grid = self.shadow;
564        for h in 0..self.grid.horizontal_size {
565            for v in 0..self.grid.vertical_size {
566                let state = self.shadow.get_cellstate(h, v);
567                self.grid.set_cellstate(h, v, *state); // does not work
568            }
569        }
570    }
571}
572
573#[cfg(test)]
574mod tests {
575    use super::*;
576
577    #[test]
578    // check grid creation values
579    fn grid_new() {
580        let g = Grid::new(5, 23);
581        assert_eq!(g.horizontal_size, 5);
582        assert_eq!(g.vertical_size, 23);
583    }
584
585    #[test]
586    #[should_panic]
587    fn grid_new_too_small() {
588        let _ = Grid::new(0, 1);
589        let _ = Grid::new(1, 0);
590    }
591
592    #[test]
593    // check grid creation values
594    fn grid_get_cellstate() {
595        let g = Grid::new(3, 17);
596        let mut c = g.get_cellstate(1, 8);
597        #[cfg(not(feature = "dead-alive-only"))]
598        assert_eq!(c, &CellState::Dummy);
599        #[cfg(feature = "dead-alive-only")]
600        assert_eq!(c, &CellState::Dead);
601
602        // test using tuple
603        c = g.get_cellstate_hv((1, 2));
604        #[cfg(not(feature = "dead-alive-only"))]
605        assert_eq!(c, &CellState::Dummy);
606        #[cfg(feature = "dead-alive-only")]
607        assert_eq!(c, &CellState::Dead);
608    }
609
610    #[test]
611    #[should_panic]
612    fn grid_get_cell_v_too_large() {
613        let g = Grid::new(3, 17);
614        let _c = g.get_cellstate(1, 17);
615    }
616
617    #[test]
618    #[should_panic]
619    fn grid_get_cell_h_too_large() {
620        let g = Grid::new(3, 1);
621        let _c = g.get_cellstate(3, 0);
622    }
623
624    #[test]
625    // check grid creation values
626    fn grid_set_cellstate() {
627        let mut g = Grid::new(3, 17);
628        #[cfg(feature = "dead-alive-only")]
629        g.set_cellstate(1, 8, CellState::Alive);
630        let mut c = g.get_cellstate(1, 8);
631        #[cfg(feature = "dead-alive-only")]
632        assert_eq!(c, &CellState::Alive);
633
634        // use tuple
635        #[cfg(feature = "dead-alive-only")]
636        g.set_cellstate_hv((2, 5), CellState::Alive);
637        c = g.get_cellstate(2, 5);
638        #[cfg(feature = "dead-alive-only")]
639        assert_eq!(c, &CellState::Alive);
640    }
641
642    #[test]
643    #[should_panic]
644    fn grid_set_cell_v_too_large() {
645        let mut g = Grid::new(3, 17);
646        #[cfg(not(feature = "dead-alive-only"))]
647        g.set_cellstate(1, 17, CellState::Dummy);
648        #[cfg(feature = "dead-alive-only")]
649        g.set_cellstate(1, 17, CellState::Alive);
650    }
651
652    #[test]
653    #[should_panic]
654    fn grid_set_cell_h_too_large() {
655        let mut g = Grid::new(3, 1);
656        #[cfg(not(feature = "dead-alive-only"))]
657        g.set_cellstate(3, 0, CellState::Dummy);
658        #[cfg(feature = "dead-alive-only")]
659        g.set_cellstate(3, 0, CellState::Alive);
660    }
661
662    #[test]
663    fn grid_get_north_coordinate() {
664        let g = Grid::new(3, 4);
665        let mut result = g.get_north_coordinate(1, 2);
666        assert_eq!(result.0, 1);
667        assert_eq!(result.1, 1);
668
669        result = g.get_north_coordinate(2, 0);
670        assert_eq!(result.0, 2);
671        assert_eq!(result.1, 3);
672    }
673
674    #[test]
675    #[should_panic]
676    fn grid_get_north_coordinate_v_too_large() {
677        let g = Grid::new(1, 4);
678        let _ = g.get_north_coordinate(0, 4);
679    }
680
681    #[test]
682    #[should_panic]
683    fn grid_get_north_coordinate_h_too_large() {
684        let g = Grid::new(1, 4);
685        let _ = g.get_north_coordinate(1, 2);
686    }
687
688    #[test]
689    fn grid_get_south_coordinate() {
690        let g = Grid::new(3, 4);
691        let mut result = g.get_south_coordinate(1, 2);
692        assert_eq!(result.0, 1);
693        assert_eq!(result.1, 3);
694
695        result = g.get_south_coordinate(2, 0);
696        assert_eq!(result.0, 2);
697        assert_eq!(result.1, 1);
698    }
699
700    #[test]
701    #[should_panic]
702    fn grid_get_south_coordinate_v_too_large() {
703        let g = Grid::new(1, 4);
704        let _ = g.get_south_coordinate(0, 4);
705    }
706
707    #[test]
708    #[should_panic]
709    fn grid_get_south_coordinate_h_too_large() {
710        let g = Grid::new(1, 4);
711        let _ = g.get_south_coordinate(1, 2);
712    }
713
714    #[test]
715    fn grid_get_west_coordinate() {
716        let g = Grid::new(3, 4);
717        let mut result = g.get_west_coordinate(1, 2);
718        assert_eq!(result.0, 0);
719        assert_eq!(result.1, 2);
720
721        result = g.get_west_coordinate(0, 2);
722        assert_eq!(result.0, 2);
723        assert_eq!(result.1, 2);
724    }
725
726    #[test]
727    #[should_panic]
728    fn grid_get_west_coordinate_v_too_large() {
729        let g = Grid::new(1, 4);
730        let _ = g.get_west_coordinate(0, 4);
731    }
732
733    #[test]
734    #[should_panic]
735    fn grid_get_west_coordinate_h_too_large() {
736        let g = Grid::new(1, 4);
737        let _ = g.get_west_coordinate(1, 2);
738    }
739
740    #[test]
741    fn grid_get_northeast_coordinate() {
742        let g = Grid::new(3, 4);
743        let mut result = g.get_northeast_coordinate(1, 2);
744        assert_eq!(result.0, 2);
745        assert_eq!(result.1, 1);
746
747        result = g.get_northeast_coordinate(2, 0);
748        assert_eq!(result.0, 0);
749        assert_eq!(result.1, 3);
750    }
751
752    #[test]
753    #[should_panic]
754    fn grid_get_northeast_coordinate_v_too_large() {
755        let g = Grid::new(1, 4);
756        let _ = g.get_northeast_coordinate(0, 4);
757    }
758
759    #[test]
760    #[should_panic]
761    fn grid_get_northeast_coordinate_h_too_large() {
762        let g = Grid::new(1, 4);
763        let _ = g.get_northeast_coordinate(1, 2);
764    }
765
766    #[test]
767    fn grid_get_southeast_coordinate() {
768        let g = Grid::new(3, 4);
769        let mut result = g.get_southeast_coordinate(1, 2);
770        assert_eq!(result.0, 2);
771        assert_eq!(result.1, 3);
772
773        result = g.get_southeast_coordinate(2, 0);
774        assert_eq!(result.0, 0);
775        assert_eq!(result.1, 1);
776    }
777
778    #[test]
779    #[should_panic]
780    fn grid_get_southeast_coordinate_v_too_large() {
781        let g = Grid::new(1, 4);
782        let _ = g.get_southeast_coordinate(0, 4);
783    }
784
785    #[test]
786    #[should_panic]
787    fn grid_get_southeast_coordinate_h_too_large() {
788        let g = Grid::new(1, 4);
789        let _ = g.get_southeast_coordinate(1, 2);
790    }
791
792    #[test]
793    fn grid_get_southwest_coordinate() {
794        let g = Grid::new(3, 4);
795        let mut result = g.get_southwest_coordinate(1, 2);
796        assert_eq!(result.0, 0);
797        assert_eq!(result.1, 3);
798
799        result = g.get_southwest_coordinate(0, 0);
800        assert_eq!(result.0, 2);
801        assert_eq!(result.1, 1);
802    }
803
804    #[test]
805    #[should_panic]
806    fn grid_get_southwest_coordinate_v_too_large() {
807        let g = Grid::new(1, 4);
808        let _ = g.get_southwest_coordinate(0, 4);
809    }
810
811    #[test]
812    #[should_panic]
813    fn grid_get_southwest_coordinate_h_too_large() {
814        let g = Grid::new(1, 4);
815        let _ = g.get_southwest_coordinate(1, 2);
816    }
817
818    #[test]
819    fn grid_get_northwest_coordinate() {
820        let g = Grid::new(3, 4);
821        let mut result = g.get_northwest_coordinate(1, 2);
822        assert_eq!(result.0, 0);
823        assert_eq!(result.1, 1);
824
825        result = g.get_northwest_coordinate(0, 0);
826        assert_eq!(result.0, 2);
827        assert_eq!(result.1, 3);
828    }
829
830    #[test]
831    #[should_panic]
832    fn grid_get_northwest_coordinate_v_too_large() {
833        let g = Grid::new(1, 4);
834        let _ = g.get_northwest_coordinate(0, 4);
835    }
836
837    #[test]
838    #[should_panic]
839    fn grid_get_northwest_coordinate_h_too_large() {
840        let g = Grid::new(1, 4);
841        let _ = g.get_northwest_coordinate(1, 2);
842    }
843
844    #[test]
845    fn grid_next_byte() {
846        // D,A,D,D,D,A,A,A -> 01000111
847        // A,A,D,D,A,D,A,D -> 11001010
848        let mut g = Grid::new(8, 2);
849        g.set_cellstate(0, 0, CellState::Dead);
850        g.set_cellstate(1, 0, CellState::Alive);
851        g.set_cellstate(2, 0, CellState::Dead);
852        g.set_cellstate(3, 0, CellState::Dead);
853        g.set_cellstate(4, 0, CellState::Dead);
854        g.set_cellstate(5, 0, CellState::Alive);
855        g.set_cellstate(6, 0, CellState::Alive);
856        g.set_cellstate(7, 0, CellState::Alive);
857        g.set_cellstate(0, 1, CellState::Alive);
858        g.set_cellstate(1, 1, CellState::Alive);
859        g.set_cellstate(2, 1, CellState::Dead);
860        g.set_cellstate(3, 1, CellState::Dead);
861        g.set_cellstate(4, 1, CellState::Alive);
862        g.set_cellstate(5, 1, CellState::Dead);
863        g.set_cellstate(6, 1, CellState::Alive);
864        g.set_cellstate(7, 1, CellState::Dead);
865
866        assert_eq!(Some(0b01000111), g.next_byte());
867        assert_eq!(Some(0b11001010), g.next_byte());
868        assert_eq!(None, g.next_byte());
869        assert_eq!(None, g.next_byte());
870    }
871    #[test]
872    fn grid_next() {
873        // D,D,A
874        // D,A,D
875        // -> D,D,A,D,A,D
876        let mut g = Grid::new(3, 2);
877        g.set_cellstate(0, 0, CellState::Dead);
878        g.set_cellstate(1, 0, CellState::Dead);
879        g.set_cellstate(2, 0, CellState::Alive);
880        g.set_cellstate(0, 1, CellState::Dead);
881        g.set_cellstate(1, 1, CellState::Alive);
882        g.set_cellstate(2, 1, CellState::Dead);
883
884        assert_eq!(Some(CellState::Dead), g.next());
885        assert_eq!(Some(CellState::Dead), g.next());
886        assert_eq!(Some(CellState::Alive), g.next());
887        assert_eq!(Some(CellState::Dead), g.next());
888        assert_eq!(Some(CellState::Alive), g.next());
889        assert_eq!(Some(CellState::Dead), g.next());
890        assert_eq!(None, g.next());
891        assert_eq!(None, g.next());
892    }
893
894    #[test]
895    fn universe_update_on_grid() {
896        fn identity(h: u8, v: u8, g: &Grid) -> CellState {
897            *g.get_cellstate(h, v)
898        }
899        let mut u1 = Universe::new(4, 6, identity);
900        u1.update();
901        for h in 0..4u8 {
902            for v in 0..6u8 {
903                let cs = u1.grid.get_cellstate(h, v);
904                #[cfg(not(feature = "dead-alive-only"))]
905                assert_eq!(cs, &CellState::Dummy);
906                #[cfg(feature = "dead-alive-only")]
907                assert_eq!(cs, &CellState::Dead);
908            }
909        }
910
911        fn inversion(h: u8, v: u8, g: &Grid) -> CellState {
912            match g.get_cellstate(h, v) {
913                &CellState::Alive => CellState::Dead,
914                &CellState::Dead => CellState::Alive,
915            }
916        }
917
918        let mut u2 = Universe::new(4, 6, inversion);
919        u2.update();
920        for h in 0..4u8 {
921            for v in 0..6u8 {
922                let cs = u2.grid.get_cellstate(h, v);
923                assert_eq!(cs, &CellState::Alive);
924            }
925        }
926    }
927
928    #[test]
929    #[cfg(feature = "dead-alive-only")]
930    fn universe_automaton() {
931        fn inversion(h: u8, v: u8, g: &Grid) -> CellState {
932            match g.get_cellstate(h, v) {
933                &CellState::Alive => CellState::Dead,
934                &CellState::Dead => CellState::Alive,
935            }
936        }
937
938        let u = Universe::new(1, 1, inversion);
939        assert_eq!(u.grid.get_cellstate(0, 0), &CellState::Dead);
940
941        let state = (u.automaton)(0, 0, &u.grid);
942        assert_eq!(state, CellState::Alive);
943    }
944
945    #[test]
946    #[cfg(feature = "dead-alive-only")]
947    fn universe_update_one_cell_inversion() {
948        fn inversion(h: u8, v: u8, g: &Grid) -> CellState {
949            match g.get_cellstate(h, v) {
950                &CellState::Alive => CellState::Dead,
951                &CellState::Dead => CellState::Alive,
952            }
953        }
954
955        let mut u = Universe::new(1, 1, inversion);
956        assert_eq!(u.grid.get_cellstate(0, 0), &CellState::Dead);
957
958        // do it manually
959        u.grid.set_cellstate(0, 0, CellState::Alive);
960        assert_eq!(u.grid.get_cellstate(0, 0), &CellState::Alive);
961
962        // reset via inversion rule
963        u.update(); // TODO: -> calling update seems to fail moditying the states
964                    //assert_eq!(u.shadow.get_cellstate(0, 0), &CellState::Alive); // sanity check on shadow state -> fails
965                    //u.grid.set_cellstate(0,0,CellState::Dead);  // this works
966        assert_eq!(u.grid.get_cellstate(0, 0), &CellState::Dead); // this fails
967    }
968
969    // test based on Wolfram rule 30
970    // https://mathworld.wolfram.com/Rule30.html
971    // https://en.wikipedia.org/wiki/Rule_30
972    #[test]
973    #[cfg(feature = "dead-alive-only")]
974    fn universe_update_rule30() {
975        fn rule30(h: u8, v: u8, g: &Grid) -> CellState {
976            let left = g.get_west_coordinate(h, v);
977            let right = g.get_east_coordinate(h, v);
978            let state = (
979                g.get_cellstate_hv(left),
980                g.get_cellstate(h, v),
981                g.get_cellstate_hv(right),
982            );
983            return match state {
984                (CellState::Alive, CellState::Alive, CellState::Alive) => CellState::Dead,
985                (CellState::Alive, CellState::Alive, CellState::Dead) => CellState::Dead,
986                (CellState::Alive, CellState::Dead, CellState::Alive) => CellState::Dead,
987                (CellState::Alive, CellState::Dead, CellState::Dead) => CellState::Alive,
988                (CellState::Dead, CellState::Alive, CellState::Alive) => CellState::Alive,
989                (CellState::Dead, CellState::Alive, CellState::Dead) => CellState::Alive,
990                (CellState::Dead, CellState::Dead, CellState::Alive) => CellState::Alive,
991                (CellState::Dead, CellState::Dead, CellState::Dead) => CellState::Dead,
992            };
993        }
994
995        // test on dead universe -> should stay dead
996        let mut u1 = Universe::new(3, 1, rule30);
997        u1.update();
998        for h in 0..2u8 {
999            let cs = u1.grid.get_cellstate(h, 0);
1000            assert_eq!(cs, &CellState::Dead)
1001        }
1002
1003        // test with center cell alive
1004        let mut u2 = Universe::new(3, 1, rule30);
1005        u2.grid.set_cellstate(1, 0, CellState::Alive);
1006        // check for correct initial state
1007        assert_eq!(u2.grid.get_cellstate(0, 0), &CellState::Dead);
1008        assert_eq!(u2.grid.get_cellstate(1, 0), &CellState::Alive);
1009        assert_eq!(u2.grid.get_cellstate(2, 0), &CellState::Dead);
1010
1011        // more in depth sanity checks
1012        assert_eq!((1, 0), u2.grid.get_east_coordinate(0, 0));
1013        assert_eq!((2, 0), u2.grid.get_east_coordinate(1, 0));
1014        assert_eq!((0, 0), u2.grid.get_east_coordinate(2, 0));
1015        assert_eq!((2, 0), u2.grid.get_west_coordinate(0, 0));
1016        assert_eq!((0, 0), u2.grid.get_west_coordinate(1, 0));
1017        assert_eq!((1, 0), u2.grid.get_west_coordinate(2, 0));
1018
1019        // test the rule itself
1020        assert_eq!(CellState::Alive, rule30(0, 0, &u2.grid));
1021        assert_eq!(CellState::Alive, rule30(1, 0, &u2.grid));
1022        assert_eq!(CellState::Alive, rule30(2, 0, &u2.grid));
1023
1024        // all cells become alive in first iteration (apply the rule)
1025        u2.update();
1026
1027        // test shadow state
1028        assert_eq!(u2.shadow.get_cellstate(0, 0), &CellState::Alive);
1029        assert_eq!(u2.shadow.get_cellstate(1, 0), &CellState::Alive);
1030        assert_eq!(u2.shadow.get_cellstate(2, 0), &CellState::Alive);
1031
1032        // test public state
1033        assert_eq!(u2.grid.get_cellstate(0, 0), &CellState::Alive);
1034        assert_eq!(u2.grid.get_cellstate(1, 0), &CellState::Alive);
1035        assert_eq!(u2.grid.get_cellstate(2, 0), &CellState::Alive);
1036
1037        // this universe should die on second iteration
1038        u2.update();
1039        assert_eq!(u2.grid.get_cellstate(0, 0), &CellState::Dead);
1040        assert_eq!(u2.grid.get_cellstate(1, 0), &CellState::Dead);
1041        assert_eq!(u2.grid.get_cellstate(2, 0), &CellState::Dead);
1042    }
1043
1044    #[test]
1045    #[cfg(feature = "dead-alive-into-bool")]
1046    fn cellstate_into_bool() {
1047        let mut cs = CellState::Dead;
1048        assert_eq!(cs.into_bool(), false);
1049        cs = CellState::Alive;
1050        assert_eq!(cs.into_bool(), true);
1051    }
1052
1053    #[test]
1054    #[cfg(feature = "dead-alive-u8-utils")]
1055    fn util_cs8_into_u8() {
1056        let mut group = [&CellState::Dead; 8];
1057        let mut result = cs8_into_u8(group);
1058        assert_eq!(result, 0b00000000);
1059
1060        group[7] = &CellState::Alive;
1061        result = cs8_into_u8(group);
1062        assert_eq!(result, 0b00000001);
1063
1064        group[0] = &CellState::Alive;
1065        result = cs8_into_u8(group);
1066        assert_eq!(result, 0b10000001);
1067
1068        group[0] = &CellState::Alive;
1069        group[3] = &CellState::Alive;
1070        group[7] = &CellState::Dead;
1071        result = cs8_into_u8(group);
1072        assert_eq!(result, 0b10010000);
1073    }
1074
1075    #[test]
1076    #[cfg(feature = "dead-alive-u8-utils")]
1077    fn util_u8_into_cs8() {
1078        // test defaults
1079        let mut expectation = [&CellState::Dead; 8];
1080        let mut result = u8_into_cs8(0);
1081        assert_eq!(result, expectation);
1082
1083        expectation[7] = &CellState::Alive;
1084        result = u8_into_cs8(1);
1085        assert_eq!(result, expectation);
1086
1087        expectation[0] = &CellState::Alive;
1088        result = u8_into_cs8(129);
1089        assert_eq!(result, expectation);
1090    }
1091}