1
\$\begingroup\$

I made the game of Life in C++ for a highschool class project. The website we're using doesn't have an autograder for C++, and my programming teacher doesn't really know how to program... so I'd love some external feedback! While I believe this simulation works, I'm not sure about the quality of the code. I tried making it as readable and fluid as possible, but I'm new to C++. Let me know how "C++" this code is, and about any pitfalls I may have dug myself into.

I'm unsure what version of C++ this website uses, but I believe its pre-C++11? (Due to a lack of smart pointers.)

I highly reccomend running it on the website here: https://codehs.com/sandbox/drakepickett/game-of-life

p.s. - I added console clearing and such just as a cheeky experiment to make it look nice. Not sure how safe console clearing with my method is :)

Thank you very much!

Here is the raw code:

#include "util.h"
#include <vector>
#include <chrono>
#include <thread>

using namespace std;

struct Board
{
    int BOARD_SIZE;
    char liveCell;
    char deadCell;
    vector<vector<char> > cells;
    
    void populateBoard()
    {
        int numLive = readInt(1, BOARD_SIZE*BOARD_SIZE, "Please enter number of active cells between 1 and " + to_string(BOARD_SIZE*BOARD_SIZE)+ ": ");
        for (int i = 0; i < numLive; i++)
        {
            int x, y;
            while (true)
            {
                x = randInt(0, this->BOARD_SIZE-1);
                y = randInt(0, this->BOARD_SIZE-1);
                if (this->cells[x][y] == this->deadCell) break;
            }
            
            this->cells[x][y] = this->liveCell;
        }
    }
    
    Board(int size, char liveCell, char deadCell)
    {
        this->BOARD_SIZE = size;
        this->liveCell = liveCell;
        this->deadCell = deadCell;
        
        for (int i = 0; i < this->BOARD_SIZE; i++)
        {
            vector<char> row;
            for (int j = 0; j < this->BOARD_SIZE; j++)
            {
                row.push_back(this->deadCell);
            }
            this->cells.push_back(row);
        }
        
        populateBoard();
    }
    
    int getNumNeighbors(int row, int column)
    {
        int numNeighbors = 0;
        
        vector<int> xRange;
        vector<int> yRange;
        
        if (row > 0) yRange.push_back(-1);
        yRange.push_back(0);
        if (row < this->cells.size()-1) yRange.push_back(1);
        
        if (column > 0) xRange.push_back(-1);
        xRange.push_back(0);
        if (column < this->cells.size()-1) xRange.push_back(1);
        
        for (int y : yRange)
        {
            for (int x : xRange)
            {
                if (x == 0 && y == 0) continue;
                //cout <<"Y: " << row << " | Row: " << row+y << " _ X: " << column << " | Column: " << column + x << endl;
                if (this->cells[row+y][column+x] == this->liveCell) numNeighbors++;
                //cout << "We made it!" << endl;
            }
        }
        
        return numNeighbors;
    }
    
    void update()
    {
        vector<vector<char> > b = this->cells;
        
        for (int i = 0; i < this->BOARD_SIZE; i++)
        {
            for(int j = 0; j < this->BOARD_SIZE; j++)
            {
                int numNeighbors = getNumNeighbors(i, j);
                
                if (this->cells[i][j] == this->liveCell && !(numNeighbors == 2 || numNeighbors == 3)) b[i][j] = this-> deadCell;
                if (this->cells[i][j] == this->deadCell && numNeighbors == 3) b[i][j] = this->liveCell;
                
            }
        }
        
        this->cells = b;
    }
    
    void print()
    {
        for (int i = 0; i < this->BOARD_SIZE; i++)
        {
            for (int j = 0; j < this->BOARD_SIZE; j++)
            {
                cout << this->cells[i][j] << " ";
            }
            cout << endl;
        }
    }
};


void cls()
{
    using namespace std::chrono_literals;
    this_thread::sleep_for(1000ms);
    std::cout << "\x1B[2J\x1B[H";
}

int main()
{
    Board board(15, 'X', '-');
    int numIterations = readInt(0, 1000, "Run how many iterations? (0-1000): ");
    for (int i = 0; i < numIterations; i++)
    {
        cls();
        cout << "Iteration " << i << endl;
        board.print();
        board.update();
    }
    cls();
    cout <<"Final Board" << endl;
    board.print();
    
    return 0;
}
\$\endgroup\$
1
  • \$\begingroup\$ A possible trick is to surround the map by extra cases. That might avoid bound checking. \$\endgroup\$
    – Jarod42
    Jan 4, 2022 at 11:48

1 Answer 1

1
\$\begingroup\$
  1. It is a good idea to list headers in alphabetical order, this enables searching for the reader. Also user-defined headers can be separated from global ones.

2.BOARD_SIZE, dead and alive can be consts. As these are not changed once the object is initialized.

  1. Loops to initialize are not required. You can write like:

    vector<vector> (BOARD_SIZE,vector(BOARD_SIZE,DEAD));

  2. Also,the boolean logic of determining the fate of each cell can be enclosed in a function.

    bool shouldDie(int row,int col) { return (this->cells[row][col] == this->liveCell && !(numNeighbors == 2 || numNeighbors == 3)); }

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.