C++练习项目:康威生命游戏的实现

    科技2022-07-12  153

    生命游戏介绍

    康威生命游戏(Conway’s Game of Life),又称康威生命棋,是英国数学家约翰· 何顿·康威在 1970 年发明的细胞自动机。生命游戏模拟的是元胞内生命有机体的诞生与消亡的过程,其中元胞(Cell)被组织为格网(Grid)的形式。每个元胞要 么为空,或者包含了一个存活的有机体。包含有机体的元胞被称之为“存活的”,而空元胞则被称之为“死亡的”。

    规则

    元胞的状态将根据以下规则从上一代进化到下一代:

    每个元胞有 8 个邻接元胞,分别与该元胞在水平,垂直和对角线等方向上邻接。如果一个元胞是存活的,但有 4 个以上的邻接元胞中存活的,那么这个元胞 将在进化到下一代时由于饥饿或者拥挤而死亡。如果元胞是存活的并且只有 2 个或者 3 个邻接元胞是存活的,那么它在下一 代中将仍然是存活的。如果一个元胞死亡了,并且有 3 个邻接元胞是存活的,那么在下一代中死亡的元胞将重新变成存活的。其他死亡元胞在下一代中将仍然是处于死亡状态。所有诞生和死亡的过程都是在同时发生的。这意味着,即使一个元胞在下一代中将变为死亡元胞,但在使邻接元胞诞生的过程中,仍然被作为存活元胞。

    实现

    #include <random> #include <cstdlib> // system(...) #include <algorithm> #include <functional> // ref #include <chrono> #include <thread> #include <vector> #include <array> #include <iostream> class Universe { private: Universe() = delete; public: enum class seed { random, ten_cell_row }; public: Universe(size_t const width, size_t const height) : rows(height), columns(width), grid(width * height), dist(0, 4) { std::random_device rd; auto seed_data = std::array<int, std::mt19937::state_size>{}; std::generate(std::begin(seed_data), std::end(seed_data), std::ref(rd)); std::seed_seq seq(std::begin(seed_data), std::end(seed_data)); mt.seed(seq); } void run(seed const s, int const generations, std::chrono::microseconds const ms = std::chrono::microseconds(100)) { reset(); initialize(s); display(); int i = 0; do { next_generation(); display(); using namespace std::chrono_literals; std::this_thread::sleep_for(ms); } while (i++ < generations || generations == 0); } private: void next_generation() { std::vector<unsigned char> newgrid(grid.size()); for (size_t r = 0; r < rows; ++r) { for (size_t c = 0; c < columns; ++c) { auto count = count_neighbors(r, c); if (cell(c, r) == alive) { newgrid[r * columns + c] = (count == 2 || count == 3) ? alive : dead; } else { newgrid[r * columns + c] = (count == 3) ? alive : dead; } } } grid.swap(newgrid); } void reset_display() { #ifdef __APPLE__ system("clear"); #elif __linux__ system("clear"); #elif WIN32 system("cls"); #endif } void display() { reset_display(); for (size_t r = 0; r < rows; ++r) { for (size_t c = 0; c < columns; ++c) { std::cout << (cell(c, r) ? '*' : ' '); } std::cout << std::endl; } } void initialize(seed const s) { if (s == seed::ten_cell_row) { for (size_t c = columns / 2 - 5; c < columns / 2 + 5; c++) { cell(c, rows / 2) = alive; } } else { for (size_t r = 0; r < rows; ++r) { for (size_t c = 0; c < columns; ++c) { cell(c, r) = dist(mt) == 0 ? alive : dead; } } } } void reset() { for (size_t r = 0; r < rows; ++r) { for (size_t c = 0; c < columns; ++c) { cell(c, r) = dead; } } } int count_alive() { return 0; } template <typename T1, typename... T> auto count_alive(T1 s, T... ts) { return s + count_alive(ts...); } int count_neighbors(size_t const row, size_t const col) { if (row == 0 && col == 0) { return count_alive(cell(1, 0), cell(1, 1), cell(0, 1)); } if (row == 0 && col == columns - 1) { return count_alive(cell(0, rows - 2), cell(1, rows - 2), cell(1, rows - 1)); } if (row == rows - 1 && col == 0) { return count_alive(cell(0, rows - 2), cell(1, rows - 2), cell(1, rows - 1)); } if (row == rows - 1 && col == columns - 1) { return count_alive(cell(columns - 1, rows - 2), cell(columns - 2, rows - 2), cell(columns - 2, rows - 1)); } if (row == 0 && col > 0 && col < columns - 1) { return count_alive(cell(col - 1, 0), cell(col - 1, 1), cell(col, 1), cell(col + 1, 1), cell(col + 1, 0)); } if (row == rows - 1 && col > 0 && col < columns - 1) { return count_alive(cell(col - 1, row), cell(col - 1, row - 1), cell(col, row - 1), cell(col + 1, row - 1), cell(col + 1, row)); } if (col == 0 && row > 0 && row < rows - 1) { return count_alive(cell(0, row - 1), cell(1, row - 1), cell(1, row), cell(1, row + 1), cell(0, row + 1)); } if (col == columns - 1 && row > 0 && row < rows - 1) { return count_alive(cell(col, row - 1), cell(col - 1, row - 1), cell(col - 1, row), cell(col - 1, row + 1), cell(col, row + 1)); } return count_alive(cell(col - 1, row - 1), cell(col, row - 1), cell(col + 1, row - 1), cell(col + 1, row), cell(col + 1, row + 1), cell(col, row + 1), cell(col - 1, row + 1), cell(col - 1, row)); } unsigned char &cell(size_t const col, size_t const row) { return grid[row * columns + col]; } private: size_t rows; size_t columns; std::vector<unsigned char> grid; const unsigned char alive = 1; const unsigned char dead = 0; std::uniform_int_distribution<> dist; std::mt19937 mt; }; int main() { using namespace std::chrono_literals; Universe u(50, 20); u.run(Universe::seed::random, 100, 100ms); return 0; }

    效果

    要点备忘

    enum class

    在C++98中,枚举被声明后,包含枚举的作用域不得有同名的声明。例如,

    enum Color { red, green, blue }; auto red = false; // error!!已经被声明了

    C++11 引入了 enum class 则提供了一个定义域限制。为了区别这二者,前者被称为不限范围(unscoped)的枚举型别,后者被称为限定作用域(scoped)枚举型别。

    enum class seed { random, ten_cell_row };

    我们都清楚,作用域限制得越小(够用就行)是最稳妥的。嗯,全局变量是魔鬼!切记。

    如果只有这个理由就要把熟悉的 enum 换成 enum class ,似乎有点理由不足。因此,使用 enum class 还有一个理由,就是它是强类型的,不会(悄悄)进行隐式转换。Python 之禅这么说来着?Explicit is better than implicit.(显式胜隐式)

    函数模板

    int count_alive() { return 0; } template <typename T1, typename... T> auto count_alive(T1 s, T... ts) { return s + count_alive(ts...); }

    这个算初级的C++模板元编程了,就是个递归的过程。主要的好处是函数形参可变。处理多个函数形参的过程就是一个“减而治之”的过程。

    Processed: 0.011, SQL: 8