MiddleCodeRareNot answered yet
Implement Conway's Game of Life in OOP style
Implement Conway's Game of Life in an OOP style, splitting state from display.
Requirements:
- a
Gridclass owns the cells and advances one generation instep(); a separatePrinter/Rendererdisplays the board (do not merge rendering intoGrid) step()must use a double buffer (write to a second grid, then swap) — in-place updates corrupt neighbour counts within the same generation- take grid dimensions as constructor parameters, not globals
- handle boundary cells explicitly (edge cells have fewer than 8 neighbours)
class Grid {
public:
Grid(int rows, int cols, bool wrap = false);
void set(int r, int c, bool alive);
bool get(int r, int c) const;
int rows() const;
int cols() const;
void step(); // advance one generation (double-buffered)
// your code here
};
Write the implementation.
Split into Grid (owns cells, computes next generation) and a Printer/Renderer (displays). Grid::step() counts live neighbours and applies the four rules using a double buffer — writing to a new grid and swapping — to avoid in-place mutation bugs.
- ✗Updating cells in-place — a cell updated in the current step affects neighbour counts later in the same step
- ✗Hardcoding grid dimensions as globals instead of constructor parameters
- ✗Not handling boundary conditions (edge cells have fewer than 8 neighbours)
- →How would you support an infinite (wrap-around toroidal) grid?
- →How do you make
step()run in parallel with multiple threads?
Contents
Task
Implement Conway's Game of Life:
- A
Gridclass owns the board and implementsstep() - Use a double buffer so updates are atomic
- Write a
Printerthat displays the board
Solution
#include <vector>
#include <iostream>
#include <cassert>
class Grid {
public:
Grid(int rows, int cols, bool wrap = false)
: rows_(rows), cols_(cols), wrap_(wrap),
cells_(rows * cols, false),
next_ (rows * cols, false) {}
void set(int r, int c, bool alive) { cells_[index(r, c)] = alive; }
[[nodiscard]] bool get(int r, int c) const { return cells_[index(r, c)]; }
[[nodiscard]] int rows() const { return rows_; }
[[nodiscard]] int cols() const { return cols_; }
void step() {
for (int r = 0; r < rows_; ++r)
for (int c = 0; c < cols_; ++c) {
int n = countNeighbours(r, c);
bool alive = cells_[index(r, c)];
next_[index(r, c)] = alive ? (n == 2 || n == 3) : (n == 3);
}
std::swap(cells_, next_);
}
private:
int rows_, cols_;
bool wrap_;
std::vector<bool> cells_, next_;
[[nodiscard]] int index(int r, int c) const { return r * cols_ + c; }
[[nodiscard]] int countNeighbours(int r, int c) const {
static constexpr int dr[] = {-1,-1,-1, 0, 0, 1, 1, 1};
static constexpr int dc[] = {-1, 0, 1,-1, 1,-1, 0, 1};
int count = 0;
for (int i = 0; i < 8; ++i) {
int nr = r + dr[i], nc = c + dc[i];
if (wrap_) { nr = (nr + rows_) % rows_; nc = (nc + cols_) % cols_; }
else if (nr < 0 || nr >= rows_ || nc < 0 || nc >= cols_) continue;
if (cells_[index(nr, nc)]) ++count;
}
return count;
}
};
class Printer {
public:
static void print(const Grid& g, char live = '#', char dead = '.') {
for (int r = 0; r < g.rows(); ++r) {
for (int c = 0; c < g.cols(); ++c)
std::cout << (g.get(r, c) ? live : dead);
std::cout << '\n';
}
std::cout << '\n';
}
};
int main() {
Grid g(5, 5);
g.set(2, 1, true); g.set(2, 2, true); g.set(2, 3, true); // blinker
Printer::print(g);
g.step();
assert(!g.get(2,1) && !g.get(2,3));
assert( g.get(1,2) && g.get(2,2) && g.get(3,2));
Printer::print(g);
g.step();
assert(g.get(2,1) && g.get(2,2) && g.get(2,3));
std::cout << "Blinker test passed\n";
return 0;
}
Key points
| Aspect | Decision |
|---|---|
| Double buffer | cells_ + next_ → std::swap at the end of step() |
| Boundary conditions | Range check or toroidal wrap via wrap_ flag |
| Neighbours | Constant offset arrays dr[]/dc[] |
| Test | Blinker oscillator (period 2) — classic verifiable pattern |
Contents