0

Right now I'm doing this tutorial for WebAssembly with Rust. The task is to implement Conways Game of Life in Rust. Writing a low-level language like Rust is new for me, but with my prior experience in coding I understand most of the syntax.

The problem I'm having is about the logic of this function:

    fn live_neighbor_count(&self, row: u32, column: u32) -> u8 {
        let mut count = 0;
        for delta_row in [self.height - 1, 0, 1].iter().cloned() {
            for delta_col in [self.width - 1, 0, 1].iter().cloned() {
                if delta_row == 0 && delta_col == 0 {
                    continue;
                }

                let neighbor_row = (row + delta_row) % self.height;
                let neighbor_col = (column + delta_col) % self.width;
                let idx = self.get_index(neighbor_row, neighbor_col);
                count += self.cells[idx] as u8;
            }
        }
        count
    }

In this implementation of Conways Game of Life the grid of cells is represented as an one-dimensional array. The task of this function is now to take coordinates (row and column) and iterate around the neighbours of this coordinates. Because the array is only one-dimensional, the tasks requires delta_row and delta_col which iterate over a array to calculate the neighbors.

My problem is now to understand why these arrays [self.height - 1, 0, 1] and [self.height - 1, 0, 1] are choosen to be iterated over.

I already tried to print out the iterated values and draw the grid on a pieces of paper to visualize the process. Right now I also try to implement a self-written function to understand the choices of the above function better.

Maybe you have already solved this or a similar problem and can give me a hints what's going on.

Thank you for reading and have a nice weekend!

hmaier
  • 53
  • 1
  • 6
  • It's a modulo hack. The trick is in the line `(row + delta_row) % self.height`, the three values you get after the modulo operand are `row-1`, `row` and `row+1`. – aedm May 13 '22 at 09:31
  • 1
    Maybe a better way to write this would be `for neighbor_row in (row-1..=row+1) {...}`. No need for modulo then. – aedm May 13 '22 at 09:33

2 Answers2

1

It ends up looking a bit odd because of the constraint of unsigned integers (u32). What we want to write is

for delta_row in [-1, 0, 1]

which gives every row within 1 of our original, but we can't use -1 because it has a sign. Notice later that we

let neighbor_row = (row + delta_row) % self.height;

so really we're effectively working in the integers modulo self.height. In this case, -1 = self.height - 1, so we are free to substitute the latter (which is legal for a u32).

An easy way to see this is to substitute it in later. On the iteration when delta_row = self.height - 1, we are doing

let neighbor_row = (row + self.height - 1) % self.height;
                 = (row - 1) % self.height - self.height % self.height;
                 = (row - 1) % self.height;

which is the row before row (with wrapping at the edge).

LeopardShark
  • 3,820
  • 2
  • 19
  • 33
0

Inputs:

self.height self.width row column
64 64 4 6

Initially delta_row in creates an array which will have [64-1=63,0,1] and it will replace delta_row as a value with each iteration, so the iteration will look like this:

delta_row delta_col neighbor_row neigbor_col idx
63 63 4+63 % 64 = 3 6+63 % 64 = 5 get_index(3,5)
63 0 3 6 get_index(3,6)
63 1 3 7 get_index(3,7)
0 63 4 5 get_index(4,5)
0 0 CONTINUED CONTINUED CONTINUED
0 1 4 7 get_index(4,7)
1 63 5 5 get_index(5,5)
1 0 5 6 get_index(5,6)
1 1 5 7 get_index(5,7)

See how it sort of does a hollow square around your row and column. Count is increased by self.cells[idx] which will always be either 0 or 1 as defined by the enum. So the ones that are alive will increase the count.