diff --git a/readme.md b/readme.md index c3c0d77..7a2aa01 100644 --- a/readme.md +++ b/readme.md @@ -16,6 +16,14 @@ Requires: - https://www.libsdl.org/ +### Running the tests + +`make test` + +Requires: + +- https://github.com/catchorg/Catch2 + ### Running a benchmark `make benchmark` diff --git a/results/benchmark.txt b/results/benchmark.txt index a9860eb..0f54c37 100644 --- a/results/benchmark.txt +++ b/results/benchmark.txt @@ -1,5 +1,5 @@ ---------------------------------------------------------------------------- Benchmark Time CPU Iterations ---------------------------------------------------------------------------- -BM_NextBoard/min_time:5.000 38.6 ms 38.5 ms 181 -BM_RenderNextBoard/min_time:5.000 40.7 ms 40.7 ms 171 +BM_NextBoard/min_time:5.000 23.1 ms 23.1 ms 301 +BM_RenderNextBoard/min_time:5.000 25.3 ms 25.2 ms 275 diff --git a/src/board/generate.cpp b/src/board/generate.cpp index 5327c41..be685f3 100644 --- a/src/board/generate.cpp +++ b/src/board/generate.cpp @@ -1,14 +1,14 @@ #include "generate.h" #include "breeder.h" #include +#include #include #include -#include #include Board randomBoard(int width, int height) { - auto board = std::shared_ptr(new bool[width*height]); - for (int i = 0; i < height*width; ++i) + auto board = std::shared_ptr(new bool[width * height]); + for (int i = 0; i < height * width; ++i) board[i] = rand() % 2; return {board, width, height}; } @@ -25,7 +25,7 @@ Board benchmarkBoard(int width, int height) { "Did not meet minimum height required for the benchmark board"); srand(0); - auto board = std::shared_ptr(new bool[width*height]); + auto board = std::shared_ptr(new bool[width * height]); for (int y = 0; y < height; ++y) { for (int x = 0; x < width; ++x) { const int i = y * width + x; diff --git a/src/board/generate.h b/src/board/generate.h index eb50eeb..8a49f01 100644 --- a/src/board/generate.h +++ b/src/board/generate.h @@ -1,7 +1,10 @@ #pragma once #include "board.h" +#include Board randomBoard(int width, int height); Board benchmarkBoard(int width, int height); + +Board fromVector(std::vector> board); diff --git a/src/board/next.cpp b/src/board/next.cpp index b816eab..93610df 100644 --- a/src/board/next.cpp +++ b/src/board/next.cpp @@ -6,28 +6,6 @@ #include #include -const NeighbourPositions getNeighbourPositions(const int px, const int py, - const int width, - const int height) { - NeighbourPositions positions = {}; - int i = 0; - for (int y = -1; y <= 1; y++) - for (int x = -1; x <= 1; x++) { - if (y == 0 && x == 0) - continue; - - auto xx = (px + x) % width; - if (xx < 0) - xx = xx + width; - auto yy = (py + y) % height; - if (yy < 0) - yy = yy + height; - - positions[i++] = yy * width + xx; - } - return positions; -} - Board nextBoard(Board board) { const auto &[input, width, height] = board; if (height == 0) @@ -43,17 +21,27 @@ Board nextBoard(Board board) { auto alive = input[i]; - auto neighboursPositions = getNeighbourPositions(x, y, width, height); int neighboursCount = 0; - for (int n = 0; n < 8; n++) { - if (input[neighboursPositions[n]]) - neighboursCount++; - - // Optimisation (exit early if we know it's a dead cell) - if (neighboursCount > 3) { - break; + for (int y2 = -1; y2 <= 1; y2++) + for (int x2 = -1; x2 <= 1; x2++) { + if (y2 == 0 && x2 == 0) + continue; + + auto xx = (x2 + x) % width; + if (xx < 0) + xx = xx + width; + auto yy = (y2 + y) % height; + if (yy < 0) + yy = yy + height; + + if (input[yy * width + xx]) + neighboursCount++; + + // Optimisation (exit early if we know it's a dead cell) + if (neighboursCount > 3) { + break; + } } - } if (alive && (neighboursCount < 2 || neighboursCount > 3)) output[i] = false; diff --git a/src/entrypoints/test.cpp b/src/entrypoints/test.cpp index 3e14d10..e792001 100644 --- a/src/entrypoints/test.cpp +++ b/src/entrypoints/test.cpp @@ -3,224 +3,291 @@ #include "../board/next.h" #include +Board generate(std::vector> input) { + const auto height = (int)input.size(); + const auto width = (int)input[0].size(); + + auto board = std::shared_ptr(new bool[width * height]); + for (int y = 0; y < height; ++y) + for (int x = 0; x < width; ++x) + board[y * width + x] = input[y][x]; + + return {board, width, height}; +} + +std::vector> ungenerate(Board board) { + const auto &[input, width, height] = board; + + std::vector> output(height, std::vector(width)); + for (int y = 0; y < height; ++y) + for (int x = 0; x < width; ++x) + output[y][x] = input[y * width + x]; + + return output; +} + +void compare(Board a, Board b) { REQUIRE(ungenerate(a) == ungenerate(b)); } + TEST_CASE("nothing", "[nextBoard]") { - Board input = {{false}}; - Board expected = {{false}}; + auto input = generate({{false}}); + auto expected = generate({{false}}); - REQUIRE(nextBoard(input) == expected); + compare(nextBoard(input), expected); } TEST_CASE("death", "[nextBoard]") { - Board input = {{true}}; - Board expected = {{false}}; + auto input = generate({{true}}); + auto expected = generate({{false}}); - REQUIRE(nextBoard(input) == expected); + compare(nextBoard(input), expected); } TEST_CASE("block", "[nextBoard]") { - Board input = {{0, 0, 0, 0}, {0, 1, 1, 0}, {0, 1, 1, 0}, {0, 0, 0, 0}}; - Board expected = {{0, 0, 0, 0}, {0, 1, 1, 0}, {0, 1, 1, 0}, {0, 0, 0, 0}}; + auto input = + generate({{0, 0, 0, 0}, {0, 1, 1, 0}, {0, 1, 1, 0}, {0, 0, 0, 0}}); + auto expected = + generate({{0, 0, 0, 0}, {0, 1, 1, 0}, {0, 1, 1, 0}, {0, 0, 0, 0}}); - REQUIRE(nextBoard(input) == expected); + compare(nextBoard(input), expected); } TEST_CASE("block (vertical wrap)", "[nextBoard]") { - Board input = {{0, 1, 1, 0}, {0, 0, 0, 0}, {0, 1, 1, 0}}; - Board expected = {{0, 1, 1, 0}, {0, 0, 0, 0}, {0, 1, 1, 0}}; + auto input = generate({{0, 1, 1, 0}, {0, 0, 0, 0}, {0, 1, 1, 0}}); + auto expected = generate({{0, 1, 1, 0}, {0, 0, 0, 0}, {0, 1, 1, 0}}); - REQUIRE(nextBoard(input) == expected); + compare(nextBoard(input), expected); } TEST_CASE("block (horizontal wrap)", "[nextBoard]") { - Board input = {{0, 0, 0}, {1, 0, 1}, {1, 0, 1}, {0, 0, 0}}; - Board expected = {{0, 0, 0}, {1, 0, 1}, {1, 0, 1}, {0, 0, 0}}; + auto input = generate({{0, 0, 0}, {1, 0, 1}, {1, 0, 1}, {0, 0, 0}}); + auto expected = generate({{0, 0, 0}, {1, 0, 1}, {1, 0, 1}, {0, 0, 0}}); - REQUIRE(nextBoard(input) == expected); + compare(nextBoard(input), expected); } TEST_CASE("block (corner wrap)", "[nextBoard]") { - Board input = {{1, 0, 1}, {0, 0, 0}, {1, 0, 1}}; - Board expected = {{1, 0, 1}, {0, 0, 0}, {1, 0, 1}}; + auto input = generate({{1, 0, 1}, {0, 0, 0}, {1, 0, 1}}); + auto expected = generate({{1, 0, 1}, {0, 0, 0}, {1, 0, 1}}); - REQUIRE(nextBoard(input) == expected); + compare(nextBoard(input), expected); } TEST_CASE("bee-hive", "[nextBoard]") { - Board input = {{0, 0, 0, 0, 0, 0}, - {0, 0, 1, 1, 0, 0}, - {0, 1, 0, 0, 1, 0}, - {0, 0, 1, 1, 0, 0}, - {0, 0, 0, 0, 0, 0}}; - Board expected = {{0, 0, 0, 0, 0, 0}, - {0, 0, 1, 1, 0, 0}, - {0, 1, 0, 0, 1, 0}, - {0, 0, 1, 1, 0, 0}, - {0, 0, 0, 0, 0, 0}}; - - REQUIRE(nextBoard(input) == expected); + auto input = generate({{0, 0, 0, 0, 0, 0}, + {0, 0, 1, 1, 0, 0}, + {0, 1, 0, 0, 1, 0}, + {0, 0, 1, 1, 0, 0}, + {0, 0, 0, 0, 0, 0}}); + auto expected = generate({{0, 0, 0, 0, 0, 0}, + {0, 0, 1, 1, 0, 0}, + {0, 1, 0, 0, 1, 0}, + {0, 0, 1, 1, 0, 0}, + {0, 0, 0, 0, 0, 0}}); + + compare(nextBoard(input), expected); } TEST_CASE("loaf", "[nextBoard]") { - Board input = {{0, 0, 0, 0, 0, 0}, {0, 0, 1, 1, 0, 0}, {0, 1, 0, 0, 1, 0}, - {0, 0, 1, 0, 1, 0}, {0, 0, 0, 1, 0, 0}, {0, 0, 0, 0, 0, 0}}; - Board expected = {{0, 0, 0, 0, 0, 0}, {0, 0, 1, 1, 0, 0}, {0, 1, 0, 0, 1, 0}, - {0, 0, 1, 0, 1, 0}, {0, 0, 0, 1, 0, 0}, {0, 0, 0, 0, 0, 0}}; - - REQUIRE(nextBoard(input) == expected); + auto input = generate({{0, 0, 0, 0, 0, 0}, + {0, 0, 1, 1, 0, 0}, + {0, 1, 0, 0, 1, 0}, + {0, 0, 1, 0, 1, 0}, + {0, 0, 0, 1, 0, 0}, + {0, 0, 0, 0, 0, 0}}); + auto expected = generate({{0, 0, 0, 0, 0, 0}, + {0, 0, 1, 1, 0, 0}, + {0, 1, 0, 0, 1, 0}, + {0, 0, 1, 0, 1, 0}, + {0, 0, 0, 1, 0, 0}, + {0, 0, 0, 0, 0, 0}}); + + compare(nextBoard(input), expected); } TEST_CASE("boat", "[nextBoard]") { - Board input = {{0, 0, 0, 0, 0}, - {0, 1, 1, 0, 0}, - {0, 1, 0, 1, 0}, - {0, 0, 1, 0, 0}, - {0, 0, 0, 0, 0}}; - Board expected = {{0, 0, 0, 0, 0}, - {0, 1, 1, 0, 0}, - {0, 1, 0, 1, 0}, - {0, 0, 1, 0, 0}, - {0, 0, 0, 0, 0}}; - - REQUIRE(nextBoard(input) == expected); + auto input = generate({{0, 0, 0, 0, 0}, + {0, 1, 1, 0, 0}, + {0, 1, 0, 1, 0}, + {0, 0, 1, 0, 0}, + {0, 0, 0, 0, 0}}); + auto expected = generate({{0, 0, 0, 0, 0}, + {0, 1, 1, 0, 0}, + {0, 1, 0, 1, 0}, + {0, 0, 1, 0, 0}, + {0, 0, 0, 0, 0}}); + + compare(nextBoard(input), expected); } TEST_CASE("tub", "[nextBoard]") { - Board input = {{0, 0, 0, 0, 0}, - {0, 0, 1, 0, 0}, - {0, 1, 0, 1, 0}, - {0, 0, 1, 0, 0}, - {0, 0, 0, 0, 0}}; - Board expected = {{0, 0, 0, 0, 0}, - {0, 0, 1, 0, 0}, - {0, 1, 0, 1, 0}, - {0, 0, 1, 0, 0}, - {0, 0, 0, 0, 0}}; - - REQUIRE(nextBoard(input) == expected); + auto input = generate({{0, 0, 0, 0, 0}, + {0, 0, 1, 0, 0}, + {0, 1, 0, 1, 0}, + {0, 0, 1, 0, 0}, + {0, 0, 0, 0, 0}}); + auto expected = generate({{0, 0, 0, 0, 0}, + {0, 0, 1, 0, 0}, + {0, 1, 0, 1, 0}, + {0, 0, 1, 0, 0}, + {0, 0, 0, 0, 0}}); + + compare(nextBoard(input), expected); } TEST_CASE("blinker 1", "[nextBoard]") { - Board input = {{0, 0, 0, 0, 0}, - {0, 0, 1, 0, 0}, - {0, 0, 1, 0, 0}, - {0, 0, 1, 0, 0}, - {0, 0, 0, 0, 0}}; - Board expected = {{0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0}, - {0, 1, 1, 1, 0}, - {0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0}}; - - REQUIRE(nextBoard(input) == expected); + auto input = generate({{0, 0, 0, 0, 0}, + {0, 0, 1, 0, 0}, + {0, 0, 1, 0, 0}, + {0, 0, 1, 0, 0}, + {0, 0, 0, 0, 0}}); + auto expected = generate({{0, 0, 0, 0, 0}, + {0, 0, 0, 0, 0}, + {0, 1, 1, 1, 0}, + {0, 0, 0, 0, 0}, + {0, 0, 0, 0, 0}}); + + compare(nextBoard(input), expected); } TEST_CASE("blinker 2", "[nextBoard]") { - Board input = {{0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0}, - {0, 1, 1, 1, 0}, - {0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0}}; - Board expected = {{0, 0, 0, 0, 0}, - {0, 0, 1, 0, 0}, - {0, 0, 1, 0, 0}, - {0, 0, 1, 0, 0}, - {0, 0, 0, 0, 0}}; - - REQUIRE(nextBoard(input) == expected); + auto input = generate({{0, 0, 0, 0, 0}, + {0, 0, 0, 0, 0}, + {0, 1, 1, 1, 0}, + {0, 0, 0, 0, 0}, + {0, 0, 0, 0, 0}}); + auto expected = generate({{0, 0, 0, 0, 0}, + {0, 0, 1, 0, 0}, + {0, 0, 1, 0, 0}, + {0, 0, 1, 0, 0}, + {0, 0, 0, 0, 0}}); + + compare(nextBoard(input), expected); } TEST_CASE("toad 1", "[nextBoard]") { - Board input = {{0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0}, {0, 0, 1, 1, 1, 0}, - {0, 1, 1, 1, 0, 0}, {0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0}}; - Board expected = {{0, 0, 0, 0, 0, 0}, {0, 0, 0, 1, 0, 0}, {0, 1, 0, 0, 1, 0}, - {0, 1, 0, 0, 1, 0}, {0, 0, 1, 0, 0, 0}, {0, 0, 0, 0, 0, 0}}; - - REQUIRE(nextBoard(input) == expected); + auto input = generate({{0, 0, 0, 0, 0, 0}, + {0, 0, 0, 0, 0, 0}, + {0, 0, 1, 1, 1, 0}, + {0, 1, 1, 1, 0, 0}, + {0, 0, 0, 0, 0, 0}, + {0, 0, 0, 0, 0, 0}}); + auto expected = generate({{0, 0, 0, 0, 0, 0}, + {0, 0, 0, 1, 0, 0}, + {0, 1, 0, 0, 1, 0}, + {0, 1, 0, 0, 1, 0}, + {0, 0, 1, 0, 0, 0}, + {0, 0, 0, 0, 0, 0}}); + + compare(nextBoard(input), expected); } TEST_CASE("toad 2", "[nextBoard]") { - Board input = {{0, 0, 0, 0, 0, 0}, {0, 0, 0, 1, 0, 0}, {0, 1, 0, 0, 1, 0}, - {0, 1, 0, 0, 1, 0}, {0, 0, 1, 0, 0, 0}, {0, 0, 0, 0, 0, 0}}; - Board expected = {{0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0}, {0, 0, 1, 1, 1, 0}, - {0, 1, 1, 1, 0, 0}, {0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0}}; - - REQUIRE(nextBoard(input) == expected); + auto input = generate({{0, 0, 0, 0, 0, 0}, + {0, 0, 0, 1, 0, 0}, + {0, 1, 0, 0, 1, 0}, + {0, 1, 0, 0, 1, 0}, + {0, 0, 1, 0, 0, 0}, + {0, 0, 0, 0, 0, 0}}); + auto expected = generate({{0, 0, 0, 0, 0, 0}, + {0, 0, 0, 0, 0, 0}, + {0, 0, 1, 1, 1, 0}, + {0, 1, 1, 1, 0, 0}, + {0, 0, 0, 0, 0, 0}, + {0, 0, 0, 0, 0, 0}}); + + compare(nextBoard(input), expected); } TEST_CASE("beacon 1", "[nextBoard]") { - Board input = {{0, 0, 0, 0, 0, 0}, {0, 1, 1, 0, 0, 0}, {0, 1, 0, 0, 0, 0}, - {0, 0, 0, 0, 1, 0}, {0, 0, 0, 1, 1, 0}, {0, 0, 0, 0, 0, 0}}; - Board expected = {{0, 0, 0, 0, 0, 0}, {0, 1, 1, 0, 0, 0}, {0, 1, 1, 0, 0, 0}, - {0, 0, 0, 1, 1, 0}, {0, 0, 0, 1, 1, 0}, {0, 0, 0, 0, 0, 0}}; - - REQUIRE(nextBoard(input) == expected); + auto input = generate({{0, 0, 0, 0, 0, 0}, + {0, 1, 1, 0, 0, 0}, + {0, 1, 0, 0, 0, 0}, + {0, 0, 0, 0, 1, 0}, + {0, 0, 0, 1, 1, 0}, + {0, 0, 0, 0, 0, 0}}); + auto expected = generate({{0, 0, 0, 0, 0, 0}, + {0, 1, 1, 0, 0, 0}, + {0, 1, 1, 0, 0, 0}, + {0, 0, 0, 1, 1, 0}, + {0, 0, 0, 1, 1, 0}, + {0, 0, 0, 0, 0, 0}}); + + compare(nextBoard(input), expected); } TEST_CASE("beacon 2", "[nextBoard]") { - Board input = {{0, 0, 0, 0, 0, 0}, {0, 1, 1, 0, 0, 0}, {0, 1, 1, 0, 0, 0}, - {0, 0, 0, 1, 1, 0}, {0, 0, 0, 1, 1, 0}, {0, 0, 0, 0, 0, 0}}; - Board expected = {{0, 0, 0, 0, 0, 0}, {0, 1, 1, 0, 0, 0}, {0, 1, 0, 0, 0, 0}, - {0, 0, 0, 0, 1, 0}, {0, 0, 0, 1, 1, 0}, {0, 0, 0, 0, 0, 0}}; - - REQUIRE(nextBoard(input) == expected); + auto input = generate({{0, 0, 0, 0, 0, 0}, + {0, 1, 1, 0, 0, 0}, + {0, 1, 1, 0, 0, 0}, + {0, 0, 0, 1, 1, 0}, + {0, 0, 0, 1, 1, 0}, + {0, 0, 0, 0, 0, 0}}); + auto expected = generate({{0, 0, 0, 0, 0, 0}, + {0, 1, 1, 0, 0, 0}, + {0, 1, 0, 0, 0, 0}, + {0, 0, 0, 0, 1, 0}, + {0, 0, 0, 1, 1, 0}, + {0, 0, 0, 0, 0, 0}}); + + compare(nextBoard(input), expected); } TEST_CASE("glider 1", "[nextBoard]") { - Board input = {{1, 0, 0, 0, 0}, - {0, 1, 1, 0, 0}, - {1, 1, 0, 0, 0}, - {0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0}}; - Board expected = {{0, 1, 0, 0, 0}, - {0, 0, 1, 0, 0}, - {1, 1, 1, 0, 0}, - {0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0}}; - - REQUIRE(nextBoard(input) == expected); + auto input = generate({{1, 0, 0, 0, 0}, + {0, 1, 1, 0, 0}, + {1, 1, 0, 0, 0}, + {0, 0, 0, 0, 0}, + {0, 0, 0, 0, 0}}); + auto expected = generate({{0, 1, 0, 0, 0}, + {0, 0, 1, 0, 0}, + {1, 1, 1, 0, 0}, + {0, 0, 0, 0, 0}, + {0, 0, 0, 0, 0}}); + + compare(nextBoard(input), expected); } TEST_CASE("glider 2", "[nextBoard]") { - Board input = {{0, 1, 0, 0, 0}, - {0, 0, 1, 0, 0}, - {1, 1, 1, 0, 0}, - {0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0}}; - Board expected = {{0, 0, 0, 0, 0}, - {1, 0, 1, 0, 0}, - {0, 1, 1, 0, 0}, - {0, 1, 0, 0, 0}, - {0, 0, 0, 0, 0}}; - - REQUIRE(nextBoard(input) == expected); + auto input = generate({{0, 1, 0, 0, 0}, + {0, 0, 1, 0, 0}, + {1, 1, 1, 0, 0}, + {0, 0, 0, 0, 0}, + {0, 0, 0, 0, 0}}); + auto expected = generate({{0, 0, 0, 0, 0}, + {1, 0, 1, 0, 0}, + {0, 1, 1, 0, 0}, + {0, 1, 0, 0, 0}, + {0, 0, 0, 0, 0}}); + + compare(nextBoard(input), expected); } TEST_CASE("glider 3", "[nextBoard]") { - Board input = {{0, 0, 0, 0, 0}, - {0, 1, 0, 1, 0}, - {0, 0, 1, 1, 0}, - {0, 0, 1, 0, 0}, - {0, 0, 0, 0, 0}}; - Board expected = {{0, 0, 0, 0, 0}, - {0, 0, 0, 1, 0}, - {0, 1, 0, 1, 0}, - {0, 0, 1, 1, 0}, - {0, 0, 0, 0, 0}}; - - REQUIRE(nextBoard(input) == expected); + auto input = generate({{0, 0, 0, 0, 0}, + {0, 1, 0, 1, 0}, + {0, 0, 1, 1, 0}, + {0, 0, 1, 0, 0}, + {0, 0, 0, 0, 0}}); + auto expected = generate({{0, 0, 0, 0, 0}, + {0, 0, 0, 1, 0}, + {0, 1, 0, 1, 0}, + {0, 0, 1, 1, 0}, + {0, 0, 0, 0, 0}}); + + compare(nextBoard(input), expected); } TEST_CASE("glider 4", "[nextBoard]") { - Board input = {{0, 0, 0, 0, 0}, - {0, 0, 0, 1, 0}, - {0, 1, 0, 1, 0}, - {0, 0, 1, 1, 0}, - {0, 0, 0, 0, 0}}; - Board expected = {{0, 0, 0, 0, 0}, - {0, 0, 1, 0, 0}, - {0, 0, 0, 1, 1}, - {0, 0, 1, 1, 0}, - {0, 0, 0, 0, 0}}; - - REQUIRE(nextBoard(input) == expected); + auto input = generate({{0, 0, 0, 0, 0}, + {0, 0, 0, 1, 0}, + {0, 1, 0, 1, 0}, + {0, 0, 1, 1, 0}, + {0, 0, 0, 0, 0}}); + auto expected = generate({{0, 0, 0, 0, 0}, + {0, 0, 1, 0, 0}, + {0, 0, 0, 1, 1}, + {0, 0, 1, 1, 0}, + {0, 0, 0, 0, 0}}); + + compare(nextBoard(input), expected); }