【100sites #011】LifeGame,生命遊戲模擬無限的可能性

Posted by Eason Chang on 2016-03-28

LifeGame,生命遊戲模擬無限的可能性

點我玩生命遊戲線上demo

點我查看Github原始碼

  • ENTER:暫停
  • 滑鼠左鍵:放置或刪除細胞

螢幕快照 2016-03-27 上午12.35.39.png

什麼是生命遊戲?

生命遊戲,是由英國數學家John Horton Conway研發的一套細胞自動機模型,地圖中每個細胞會根據周圍8格細胞的狀態來決定自身的存亡,詳細規則如下:(摘錄自維基百科

  1. 當前細胞為存活狀態時,當周圍低於 2 個(不包含 2 個)存活細胞時,該細胞變成死亡狀態。(模擬生命數量稀少)
  2. 當前細胞為存活狀態時,當周圍有 2 個或 3 個存活細胞時,該細胞保持原樣。
  3. 當前細胞為存活狀態時,當周圍有 3 個以上(不包含 3 個)的存活細胞時,該細胞變成死亡狀態。(模擬生命數量過多)
  4. 當前細胞為死亡狀態時,當周圍有 3 個存活細胞時,該細胞變成存活狀態。 (模擬繁殖)

僅僅這4條規則,就可以建構出一個複雜而美妙的生物遊戲世界。我們可以找到一些特殊形狀的生物單位,他們會表現出一些有趣的行為模式。

三種經典的特殊生物單位:
螢幕快照 2016-03-28 上午11.36.59.png

  • 左邊的Blinker(信號燈)會以特定週期變化
  • 中間的Spaceship(太空船)會往右下角移動(你也可以設計出往其他方向移動的船)
  • 右邊的Beehive(蜂窩)則會保持靜止

今天的LifeGame

而在今天的LifeGame裡頭,我已經為你在左上角放置一個Blinker了,剩下的空間,就交給你來創造無限的可能性了!

今天的程式碼:

index.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
<!DOCTYPE html>
<html>
<head>
<meta charset-"UTF-8">
<title>LifeGame</title>
<link rel="stylesheet" type="text/css" href="style.css">
<script src="http://cdnjs.cloudflare.com/ajax/libs/p5.js/0.4.23/p5.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/0.4.23/addons/p5.dom.min.js"></script>
<script src="lifegame.js"></script>
</head>

<body>
</body>
</html>
style.css
1
2
3
4
5
6
7
8
* {
margin: 0;
padding: 0;
}

body {
overflow: hidden;
}
lifegame.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
var SIZE = 20;
var oldMap = [],
newMap = [],
num_x = 0,
num_y = 0,
paused = false,
frameCount = 0;
var pauseButton;



function setup() {
createCanvas(windowWidth, windowHeight);
frameRate(30);

// init map
num_x = windowWidth / SIZE;
num_y = windowHeight / SIZE;
for (var i = 0; i < num_x; ++i) {
oldMap.push([]);
newMap.push([]);
for (var j = 0; j < num_y; ++j) {
oldMap[i].push(false);
newMap[i].push(false);
}
}
loadDefaultMap();

// init pause button
pauseButton = createButton('Pause');
pauseButton.size(80, 30);
pauseButton.position(windowWidth/2-40, windowHeight-40);
pauseButton.mousePressed(togglePauseSimulate);
}


function draw() {
// fresh map every 5 frames
if (!paused) {
if (frameCount%5 == 0) {
freshMap();
}
}
// increase frame count
++frameCount;
if (frameCount >= 30) {
frameCount = 0;
}

drawMap();
}


// default map: a blink unit at top left corner
function loadDefaultMap() {
oldMap[1][1] = true;
oldMap[1][2] = true;
oldMap[1][3] = true;
}


function drawMap() {
background(0);
// draw grid
stroke(30);
for (var i = 0; i < num_x; ++i) {
line(i*SIZE, 0, i*SIZE, windowHeight);
}
for (var i = 0; i < num_y; ++i) {
line(0, i*SIZE, windowWidth, i*SIZE);
}
fill(255);
// draw cells
for (var i = 0; i < num_x; ++i) {
for (var j = 0; j < num_y; ++j) {
if (oldMap[i][j]) {
rect(i*SIZE, j*SIZE, SIZE, SIZE);
}
}
}
// draw mouse cell
fill(color('rgba(100,100,100,0.5)'));
var mouseCellX = int(mouseX / SIZE);
var mouseCellY = int(mouseY / SIZE);
rect(mouseCellX*SIZE, mouseCellY*SIZE, SIZE, SIZE);
}


// press ENTER to pause simulate
function keyPressed() {
if (keyCode == ENTER) {
togglePauseSimulate();
}
}


// press mouse to add or remove cell
function mousePressed() {
var mouseCellX = int(mouseX / SIZE);
var mouseCellY = int(mouseY / SIZE);
oldMap[mouseCellX][mouseCellY] = !oldMap[mouseCellX][mouseCellY]
}


function togglePauseSimulate() {
if (paused) {
paused = false;
} else {
paused = true;
}
}



// count how many neighbors there are of a cell
function neighbors(xpos, ypos) {
var total = 0;
// four corners
if (xpos != 0 && ypos != 0) {
if (oldMap[xpos-1][ypos-1]) {
++total;
}
}
if (xpos != 0 && ypos != num_y-1) {
if (oldMap[xpos-1][ypos+1]) {
++total;
}
}
if (xpos != num_x-1 && ypos != 0) {
if (oldMap[xpos+1][ypos-1]) {
++total;
}
}
if (xpos != num_x-1 && ypos != num_y-1) {
if (oldMap[xpos+1][ypos+1]) {
++total;
}
}
// left and right
if (xpos != 0) {
if (oldMap[xpos-1][ypos]) {
++total;
}
}
if (xpos != num_x-1) {
if (oldMap[xpos+1][ypos]) {
++total;
}
}
if (ypos != 0) {
if (oldMap[xpos][ypos-1]) {
++total;
}
}
if (ypos != num_y-1) {
if (oldMap[xpos][ypos+1]) {
++total;
}
}
return total;
}


// calculate the next map according to the Life Game rule
function freshMap() {
for (var i = 0; i < num_x; ++i) {
for (var j = 0; j < num_y; ++j) {
var neighbor_count = neighbors(i, j);
if (neighbor_count <= 1 || neighbor_count >= 4) {
newMap[i][j] = false;
} else if (neighbor_count === 2) {
newMap[i][j] = oldMap[i][j];
} else { // neighbor_count === 3
newMap[i][j] = true;
}
}
}

for (var i = 0; i < num_x; ++i) {
for (var j = 0; j < num_y; ++j) {
oldMap[i][j] = newMap[i][j];
}
}
}