生命游戏介绍
康威生命游戏(Conway’s Game of Life),又称康威生命棋,是英国数学家约翰· 何顿·康威在 1970 年发明的细胞自动机。生命游戏模拟的是元胞内生命有机体的诞生与消亡的过程,其中元胞(Cell)被组织为格网(Grid)的形式。每个元胞要 么为空,或者包含了一个存活的有机体。包含有机体的元胞被称之为“存活的”,而空元胞则被称之为“死亡的”。
规则
元胞的状态将根据以下规则从上一代进化到下一代:
每个元胞有 8 个邻接元胞,分别与该元胞在水平,垂直和对角线等方向上邻接。如果一个元胞是存活的,但有 4 个以上的邻接元胞中存活的,那么这个元胞 将在进化到下一代时由于饥饿或者拥挤而死亡。如果元胞是存活的并且只有 2 个或者 3 个邻接元胞是存活的,那么它在下一 代中将仍然是存活的。如果一个元胞死亡了,并且有 3 个邻接元胞是存活的,那么在下一代中死亡的元胞将重新变成存活的。其他死亡元胞在下一代中将仍然是处于死亡状态。所有诞生和死亡的过程都是在同时发生的。这意味着,即使一个元胞在下一代中将变为死亡元胞,但在使邻接元胞诞生的过程中,仍然被作为存活元胞。
实现
#include <random>
#include <cstdlib>
#include <algorithm>
#include <functional>
#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;
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++模板元编程了,就是个递归的过程。主要的好处是函数形参可变。处理多个函数形参的过程就是一个“减而治之”的过程。