conway/
grids.rs

1/*  Copyright 2017-2019 the Conwayste Developers.
2 *
3 *  This file is part of libconway.
4 *
5 *  libconway is free software: you can redistribute it and/or modify
6 *  it under the terms of the GNU General Public License as published by
7 *  the Free Software Foundation, either version 3 of the License, or
8 *  (at your option) any later version.
9 *
10 *  libconway is distributed in the hope that it will be useful,
11 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
12 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 *  GNU General Public License for more details.
14 *
15 *  You should have received a copy of the GNU General Public License
16 *  along with libconway.  If not, see <http://www.gnu.org/licenses/>. */
17
18use std::ops::{Index, IndexMut};
19use std::cmp;
20use crate::universe::Region;
21use crate::rle::Pattern;
22
23
24#[derive(Eq, PartialEq, Debug, Clone, Copy)]
25pub enum BitOperation {
26    Clear,
27    Set,
28    Toggle
29}
30
31
32#[derive(Debug, PartialEq, Clone)]
33pub struct BitGrid(pub Vec<Vec<u64>>);
34
35impl BitGrid {
36    /// Creates a new zero-initialized BitGrid of given dimensions.
37    ///
38    /// # Panics
39    ///
40    /// This function will panic if `with_in_words` or `height` are zero.
41    pub fn new(width_in_words: usize, height: usize) -> Self {
42        assert!(width_in_words != 0);
43        assert!(height != 0);
44
45        let mut rows: Vec<Vec<u64>> = Vec::with_capacity(height);
46        for _ in 0 .. height {
47            let row: Vec<u64> = vec![0; width_in_words];
48            rows.push(row);
49        }
50        BitGrid(rows)
51    }
52
53    pub fn width_in_words(&self) -> usize {
54        if self.height() > 0 {
55            self.0[0].len()
56        } else {
57            0
58        }
59    }
60
61    #[inline]
62    pub fn modify_bits_in_word(&mut self, row: usize, word_col: usize, mask: u64, op: BitOperation) {
63        match op {
64            BitOperation::Set    => self[row][word_col] |=  mask,
65            BitOperation::Clear  => self[row][word_col] &= !mask,
66            BitOperation::Toggle => self[row][word_col] ^=  mask,
67        }
68    }
69
70    /// Sets, clears, or toggles a rectangle of bits.
71    ///
72    /// # Panics
73    ///
74    /// This function will panic if `region` is out of range.
75    pub fn modify_region(&mut self, region: Region, op: BitOperation) {
76        for y in region.top() .. region.bottom() + 1 {
77            assert!(y >= 0);
78            for word_col in 0 .. self[y as usize].len() {
79                let x_left  = word_col * 64;
80                let x_right = x_left + 63;
81
82                if region.right() >= x_left as isize && region.left() <= x_right as isize {
83                    let mut mask = u64::max_value();
84
85                    for shift in (0..64).rev() {
86                        let x = x_right - shift;
87                        if (x as isize) < region.left() || (x as isize) > region.right() {
88                            mask &= !(1 << shift);
89                        }
90                    }
91
92                    // apply change to bitgrid based on mask and bit
93                    self.modify_bits_in_word(y as usize, word_col, mask, op);
94                }
95            }
96        }
97    }
98
99    /// Returns `Some(`smallest region containing every 1 bit`)`, or `None` if there are no 1 bits.
100    pub fn bounding_box(&self) -> Option<Region> {
101        let (width, height) = (self.width(), self.height());
102        let mut first_row = None;
103        let mut last_row = None;
104        let mut first_col = None;
105        let mut last_col = None;
106        for row in 0..height {
107            let mut col = 0;
108            while col < width {
109                let (rle_len, ch) = self.get_run(col, row, None);
110                if ch == 'o' {
111                    if let Some(_first_col) = first_col {
112                        if _first_col > col {
113                            first_col = Some(col);
114                        }
115                    } else {
116                        first_col = Some(col);
117                    }
118                    if first_row.is_none() {
119                        first_row = Some(row);
120                    }
121                    let run_last_col = col + rle_len - 1;
122                    if let Some(_last_col) = last_col {
123                        if _last_col < run_last_col {
124                            last_col = Some(run_last_col);
125                        }
126                    } else {
127                        last_col = Some(run_last_col);
128                    }
129                    last_row = Some(row);
130                }
131                col += rle_len;
132            }
133        }
134        if first_row.is_some() {
135            let width = last_col.unwrap() - first_col.unwrap() + 1;
136            let height = last_row.unwrap() - first_row.unwrap() + 1;
137            Some(Region::new(first_col.unwrap() as isize, first_row.unwrap() as isize, width, height))
138        } else {
139            None
140        }
141    }
142
143    /// Copies `src` BitGrid into `dst` BitGrid, but fit into `dst_region` with clipping. If there
144    /// is a 10x10 pattern in source starting at 0,0 and the dst_region is only 8x8 with top left
145    /// corner at 3,3 then the resulting pattern will be clipped in a 2 pixel region on the bottom
146    /// and right and extend from 3,3 to 10,10. If `dst_region` extends beyond `dst`, the pattern
147    /// will be further clipped by the dimensions of `dst`.
148    ///
149    /// The bits are copied using an `|=` operation, so 1 bits in the destination will not ever be
150    /// cleared.
151    pub fn copy(src: &BitGrid, dst: &mut BitGrid, dst_region: Region) {
152        let dst_left   = cmp::max(0, dst_region.left()) as usize;
153        let dst_right  = cmp::min(dst.width() as isize - 1, dst_region.right()) as usize;
154        let dst_top    = cmp::max(0, dst_region.top()) as usize;
155        let dst_bottom = cmp::min(dst.height() as isize - 1, dst_region.bottom()) as usize;
156        if dst_left > dst_right || dst_top > dst_bottom {
157            // nothing to do because both dimensions aren't positive
158            return;
159        }
160
161        for src_row in 0..src.height() {
162            let dst_row = src_row + dst_top;
163            if dst_row > dst_bottom {
164                break;
165            }
166            let mut src_col = 0;   // word-aligned
167            while src_col < src.width() {
168                let dst_col = src_col + dst_left;    // not word-aligned
169                if dst_col > dst_right {
170                    break;
171                }
172                let dst_word_idx = dst_col / 64;
173                let shift = dst_col - dst_word_idx*64;  // right shift amount
174                let mut word = src[src_row][src_col/64];
175                // clear bits that would be beyond dst_right
176                if dst_right - dst_col + 1 < 64 {
177                    let mask = !((1u64 << (64 - (dst_right - dst_col + 1))) - 1);
178                    word &= mask;
179                }
180                dst[dst_row][dst_word_idx] |= word >> shift;
181                if shift > 0 && dst_word_idx+1 < dst.width_in_words() {
182                    dst[dst_row][dst_word_idx+1] |= word << (64 - shift);
183                }
184                src_col += 64;
185            }
186        }
187    }
188
189    /// Get a Region of the same size as the BitGrid.
190    pub fn region(&self) -> Region {
191        Region::new(0, 0, self.width(), self.height())
192    }
193
194    /// Clear this BitGrid.
195    pub fn clear(&mut self) {
196        for row in &mut self.0 {
197            for col_idx in 0..row.len() {
198                row[col_idx] = 0;
199            }
200        }
201    }
202}
203
204
205impl Index<usize> for BitGrid {
206    type Output = Vec<u64>;
207
208    fn index(&self, i: usize) -> &Vec<u64> {
209        &self.0[i]
210    }
211}
212
213impl IndexMut<usize> for BitGrid {
214    fn index_mut(&mut self, i: usize) -> &mut Vec<u64> {
215        &mut self.0[i]
216    }
217}
218
219
220pub trait CharGrid {
221    /// Write a char `ch` to (`col`, `row`).
222    /// 
223    /// # Panics
224    /// 
225    /// Panics if:
226    /// * `col` or `row` are out of range
227    /// * `char` is invalid for this type. Use `is_valid` to check first.
228    /// * `visibility` is invalid. That is, it equals `Some(player_id)`, but there is no such `player_id`.
229    fn write_at_position(&mut self, col: usize, row: usize, ch: char, visibility: Option<usize>);
230
231    /// Is `ch` a valid character?
232    fn is_valid(ch: char) -> bool;
233
234    /// Width in cells
235    fn width(&self) -> usize;
236
237    /// Height in cells
238    fn height(&self) -> usize;
239
240    /// Returns a Pattern that describes this `CharGrid` as viewed by specified player if
241    /// `visibility.is_some()`, or a fog-less view if `visibility.is_none()`.
242    fn to_pattern(&self, visibility: Option<usize>) -> Pattern {
243
244        fn push(result: &mut String, output_col: &mut usize, rle_len: usize, ch: char) {
245            let what_to_add = if rle_len == 1 {
246                let mut s = String::with_capacity(1);
247                s.push(ch);
248                s
249            } else { format!("{}{}", rle_len, ch) };
250            if *output_col + what_to_add.len() > 70 {
251                result.push_str("\r\n");
252                *output_col = 0;
253            }
254            result.push_str(what_to_add.as_str());
255            *output_col += what_to_add.len();
256        }
257
258        let mut result = "".to_owned();
259        let (mut col, mut row) = (0, 0);
260        let mut line_ends_buffered = 0;
261        let mut output_col = 0;
262        while row < self.height() {
263            while col < self.width() {
264                let (rle_len, ch) = self.get_run(col, row, visibility);
265
266                match ch {
267                    'b' => {
268                        // Blank
269                        // TODO: if supporting diffs with this same algorithm, then need to allow
270                        // other characters to serve this purpose.
271                        if col + rle_len < self.width() {
272                            if line_ends_buffered > 0 {
273                                push(&mut result, &mut output_col, line_ends_buffered, '$');
274                                line_ends_buffered = 0;
275                            }
276                            push(&mut result, &mut output_col, rle_len, ch);
277                        }
278                    }
279                    _ => {
280                        // Non-blank
281                        if line_ends_buffered > 0 {
282                            push(&mut result, &mut output_col, line_ends_buffered, '$');
283                            line_ends_buffered = 0;
284                        }
285                        push(&mut result, &mut output_col, rle_len, ch);
286                    }
287                }
288
289                col += rle_len;
290            }
291
292            row += 1;
293            col = 0;
294            line_ends_buffered += 1;
295        }
296        push(&mut result, &mut output_col, 1, '!');
297        Pattern(result)
298    }
299
300    /// Given a starting cell at `(col, row)`, get the character at that cell, and the number of
301    /// contiguous identical cells considering only this cell and the cells to the right of it.
302    /// This is intended for exporting to RLE.
303    ///
304    /// The `visibility` parameter, if not `None`, is used to generate a run as observed by a
305    /// particular player.
306    ///
307    /// # Returns
308    ///
309    /// `(run_length, ch)`
310    ///
311    /// # Panics
312    ///
313    /// This function will panic if `col`, `row`, or `visibility` (`Some(player_id)`) are out of bounds.
314    fn get_run(&self, col: usize, row: usize, visibility: Option<usize>) -> (usize, char);
315}
316
317
318const VALID_BIT_GRID_CHARS: [char; 2] = ['b', 'o'];
319
320impl CharGrid for BitGrid {
321    /// Width in cells
322    fn width(&self) -> usize {
323        self.width_in_words() * 64
324    }
325
326    /// Height in cells
327    fn height(&self) -> usize {
328        self.0.len()
329    }
330
331    /// _visibility is ignored, since BitGrids have no concept of a player.
332    fn write_at_position(&mut self, col: usize, row: usize, ch: char, _visibility: Option<usize>) {
333        let word_col = col/64;
334        let shift = 63 - (col & (64 - 1));
335        match ch {
336            'b' => {
337                self.modify_bits_in_word(row, word_col, 1 << shift, BitOperation::Clear)
338            }
339            'o' => {
340                self.modify_bits_in_word(row, word_col, 1 << shift, BitOperation::Set)
341            }
342            _ => panic!("invalid character: {:?}", ch)
343        }
344    }
345
346    fn is_valid(ch: char) -> bool {
347        VALID_BIT_GRID_CHARS.contains(&ch)
348    }
349
350    /// Given a starting cell at `(col, row)`, get the character at that cell, and the number of
351    /// contiguous identical cells considering only this cell and the cells to the right of it.
352    /// This is intended for exporting to RLE.
353    ///
354    /// The `_visibility` argument is unused and should be `None`.
355    ///
356    /// # Returns
357    ///
358    /// `(run_length, ch)`
359    ///
360    /// # Panics
361    ///
362    /// This function will panic if `col` or `row` are out of bounds.
363    fn get_run(&self, col: usize, row: usize, _visibility: Option<usize>) -> (usize, char) {
364        let word_col = col/64;
365        let shift = 63 - (col & (64 - 1));
366        let mut word = self.0[row][word_col];
367        let mut mask = 1 << shift;
368        let is_set = (word & (1 << shift)) > 0;
369        let mut end_col = col + 1;
370        let ch = if is_set { 'o' } else { 'b' };
371
372        // go to end of current word
373        mask >>= 1;
374        while mask > 0 {
375            if (word & mask > 0) != is_set {
376                return (end_col - col, ch);
377            }
378            end_col += 1;
379            mask >>= 1;
380        }
381        assert_eq!(end_col % 64, 0);
382
383        // skip words
384        let mut end_word_col = word_col + 1;
385        while end_word_col < self.0[row].len() {
386            word = self.0[row][end_word_col];
387            if is_set && word < u64::max_value() {
388                break;
389            }
390            if !is_set && word > 0 {
391                break;
392            }
393            end_col += 64;
394            end_word_col += 1;
395        }
396        // start from beginning of last word
397        if end_word_col == self.0[row].len() {
398            return (end_col - col, ch);
399        }
400        mask = 1 << 63;
401        while mask > 0 {
402            if (word & mask > 0) != is_set {
403                break;
404            }
405            end_col += 1;
406            mask >>= 1;
407        }
408        return (end_col - col, ch);
409    }
410}