Last Updated:

Creating a Game of Life in C++

The scene of the game is the "universe" - a limited plane marked into cells. Every cell on this plane can be "alive" or "dead" (empty). The cell has 8 neighbors – the surrounding cells.
 

Rules of the game

 

The first generation is the position of the game cells at the beginning of the game. We'll fill it in randomly. Each new generation is calculated on the basis of the previous one according to the following rules:

  • In an empty cage, next to which there are exactly three living cells, life is born.
  • If a living cell has two or three living neighbors, then that cell continues to live. Otherwise, the cell dies (from "loneliness" or "overpopulation").

The game stops if there is not a single living cell left on the field or no cell changes its state at the next step (a stable configuration is formed).

Playing field

The playing field will be stored in a two-dimensional array of bitwise structures. The number of rows of which is the height of the field, and the number of columns is its width.

// 
struct point {
    unsigned is_live:1;
};

/* Ширина игрового поля */
#define __WORLD_HEIGHT__ 10

/* Высота игрового поля */
#define __WORLD_WIDTH__ 10

// Игровое поле размером 10x10 клеток
point world[__WORLD_WIDTH__][__WORLD_HEIGHT__];

The first generation of the game will be randomly generated. To do this, let's use the library from C++ 11.<random>

/*
 * 
 */
void init_world(point world[][__WORLD_HEIGHT__])
{
    std::random_device rd;
    std::mt19937 gen(rd());
    std::uniform_int_distribution<> dis(1, 10000);

    unsigned int i, j;

    for (i = 0; i < __WORLD_WIDTH__; i++) {
        for (j = 0; j < __WORLD_HEIGHT__; j++) {
            unsigned int num = dis(gen);
            if (num % 2 == 0) {
                world[i][j].is_live = 1;
            } else {
                world[i][j].is_live = 0;
            }
        }
    }
}

After the formation of the first generation, the game cycle will begin, which will end when all the cells die, or the optimal configuration will be found (the same state of the cells in the current and previous step).

Neighbor cells

Counting the number of living cells will be done by the function .get_live_count

/*
 * 
*/
unsigned int get_live_count(point world[][__WORLD_HEIGHT__])
{
    unsigned int count = 0;
    unsigned i, j;
    for (i = 0; i < __WORLD_WIDTH__; i++) {
        for (j = 0; j < __WORLD_HEIGHT__; j++) {
            if (world[i][j].is_live == 1) {
                count++;
            }
        }
    }
    return count;
}

For each step of the cycle, you need to generate a new generation, in accordance with the rules of the game. Let's write a function that will determine the coordinates of the cell's neighbors.

/*
 * 
*/
void read_point_neighbors(signed int nb[][2], unsigned int x, unsigned int y)
{
    unsigned int i, j;
    unsigned int k = 0;

    for (i = x - 1; i <= x + 1; i++) {
        for (j = y - 1; j <= y + 1; j++) {
            if (i == x && j == y) {
                continue;
            }
            nb[k][0] = i;
            nb[k][1] = j;
            k++;
        }
    }
}

The function takes a pointer to a two-dimensional array (to record the result) and the coordinates of the point at which we are looking for neighbors.read_point_neighborsxy

It is necessary to know the number of living neighbors of the cell. To do this, write a function .get_live_neighbors

/*
 * 
 */
unsigned int count_live_neighbors(point world[][__WORLD_HEIGHT__], unsigned int x, unsigned int y)
{
    unsigned int count = 0;
    unsigned int i;
    signed int nb[8][2];
    signed int _x, _y;

    read_point_neighbors(nb, x, y);

    for (i = 0; i < 8; i++) {
        _x = nb[i][0];
        _y = nb[i][1];

        if (_x < 0 || _y < 0) {
            continue;
        }
        if (_x >= __WORLD_WIDTH__ || _y >= __WORLD_HEIGHT__) {
            continue;
        }

        if (world[_x][_y].is_live == 1) {
            count++;
        }
    }

    return count;
}

The function takes a reference to the array of the playing field and the x, y coordinates of the cell.

Generational change

Now we can quickly get all the data we need to build the next generation of cells. Let's write a function for the formation of a new generation:next_generation

/*
 * 
 */
void next_generation(point world[][__WORLD_HEIGHT__], point prev_world[][__WORLD_HEIGHT__])
{
    unsigned int i, j;
    unsigned int live_nb;
    point p;

    for (i = 0; i < __WORLD_WIDTH__; i++) {
        for (j = 0; j < __WORLD_HEIGHT__; j++) {
            p = prev_world[i][j];
            live_nb = count_live_neighbors(prev_world, i, j);

            if (p.is_live == 0) {
                if (live_nb == 3) {
                    world[i][j].is_live = 1;
                }
            } else {
                if (live_nb < 2 || live_nb > 3) {
                    world[i][j].is_live = 0;
                }
            }
        }
    }
}

Another condition for completing the game is when two generations following each other have an identical state of cells. To compare the two generations, let's create two functions — and . The first will copy the state of the playing field to a temporary array before generating the next generation. The second will compare the location of cells on two playing fields.copy_worldcmp_world

/*
 * 
*/
void copy_world(point src[][__WORLD_HEIGHT__], point dest[][__WORLD_HEIGHT__])
{
    unsigned int i, j;
    for (i = 0; i < __WORLD_WIDTH__; i++) {
        for (j = 0; j < __WORLD_HEIGHT__; j++) {
            dest[i][j] = src[i][j];
        }
    }
}


/*
 * 
 */
int cmp_world(point w1[][__WORLD_HEIGHT__], point w2[][__WORLD_HEIGHT__])
{
    unsigned int i, j;
    for (i = 0; i < __WORLD_WIDTH__; i++) {
        for (j = 0; j < __WORLD_HEIGHT__; j++) {
            if (w1[i][j].is_live != w2[i][j].is_live) {
                return -1;
            }
        }
    }
    return 0;
}

The function will display the state of the playing field for each new generation.print_world

/*
 * Вывести на экран игровое поле
*/
void print_world(point world[][__WORLD_HEIGHT__])
{
    unsigned int i, j;
    for (i = 0; i < __WORLD_WIDTH__; i++) {
        for (j = 0; j < __WORLD_HEIGHT__; j++) {
            if (world[i][j].is_live == 1) {
                cout << '*';
            } else {
                cout << ' ';
            }
            cout << ' ';
        }
        cout << endl;
    }
}

Gameplay cycle

Let's define a function that will start the gameplay.main

int main()
{
    point world[__WORLD_WIDTH__][__WORLD_HEIGHT__];
    point prev_world[__WORLD_WIDTH__][__WORLD_HEIGHT__];

    init_world(world);
    unsigned int live_points = -1;
    bool is_optimal = false;

    do {
        print_world(world);
        copy_world(world, prev_world);
        next_generation(world, prev_world);

        is_optimal = cmp_world(world, prev_world) == 0;
        live_points = get_live_count(world);

        if (is_optimal) {
            cout << "Optimal configuration detected" << endl;
        }

        if (live_points == 0) {
            cout << "All points died" << endl;
        }
        msleep(1000);
    } while (live_points != 0 && !is_optimal);

    return 0;
}

Compilation

To compile the project, you need C++ 11 support. In GCC 4.7, such support is included as follows:

c++ -std=c++11 -o life life.cpp

To enable C++ 11 support in Dev C++, go to the menu "Tools → Compiler Options → (select your compiler) → Settings → Code Generation". Set the language standard option to C++11.

Visual Studio 2010 has partial support for C++ 11. At least it should be.<random>

An example of how the game is 20 × 20 cells:


game of life

An attentive reader might have noticed that there is a "periodic configuration" in the rules of the game – when the current state of the game repeats any previous state in all steps.

In this example, we did not write such functionality, because with a large number of iterations, you may need a lot of space to store the history. But if you wish, you can implement this feature and share it with other readers in the comments.