Game of Life, de Conway
Essa classe foi criada para executar o [http://www.math.com/students/wonders/life/life.html Game of Life] em uma matriz de 0s e 1s, onde 1 representa uma célula "viva". Já existe código para o Game of Life em Python (bem, em qualquer linguagem que você pensar, pois é um problema matemático clássico) mas eu estou querendo desenvolver por conta própria e ver quão longe eu chego, principalmente em termos de desempenho e funcionalidade.
Estou usando essa classe para implementar uma [http://www20.brinkster.com/rodviking/game.gif versão gráfica] do Life usando PyQt e Numarray. Caso tenha interesse nesse código também, contate-me através do link no fim da página
Por enquanto o código não está otimizado pois meu enfoque é primeiro ter meu programa funcionando pra depois melhorar o desempenho (que está razoável), mas estou aberto a sugestões, em especial nos métodos "applyRules" e "countNeighbours" que é onde estão os "gargalos".
Em breve essa classe também irá suportar o carregamento de arquivos de "pattern" (extensão .lif), que inclui regras dinâmicas.
Código
1 from numarray import *
2
3 class LifeGame:
4 def __init__(self, rule=None):
5 """
6 Rule is a string with the format ss/dd where ss are digits in 1..8
7 representing how many neighbours a cell must have to stay alive, and
8 dd are digits in 1..8 representing how many neighbours a dead cell must
9 have to become alive.
10
11 For example, the default conway rule (which is used if no string is passed
12 as parameter) is "23/3", which means a cell stay alive if it has 2 or 3 neighbours,
13 and becomes alive if it has 3 neighbours.
14 """
15 if not rule:
16 #if not rule is passed, use the default one
17 self.rule = "23/3"
18 else:
19 self.rule = rule
20
21 def countNeighbours (self, row, col):
22 """
23 countNeighbours (row, col)
24
25 Returns a number between 0 and 8, counting how many 'living' neighbours (=1)
26 a cell specified by x,y has.
27 """
28 total = 0
29
30 #count the living neighbours (=1) on the row above
31 if row > 0:
32 total += self.world[row-1, col]
33 if col > 0:
34 total += self.world[row-1, col-1]
35 if col < self.num_cols - 1:
36 total += self.world[row-1, col+1]
37
38 #count the living neighbours on the left and right
39 if col > 0:
40 total += self.world[row, col-1]
41 if col < self.num_cols - 1:
42 total += self.world[row, col+1]
43
44 #count the living neighbours on the row below
45 if row < self.num_rows - 1:
46 total += self.world[row+1, col]
47 if col > 0:
48 total += self.world[row+1, col-1]
49 if col < self.num_cols - 1:
50 total += self.world[row+1, col+1]
51 return total
52
53 def resizeGrid (self, world, rows, cols, centralizeData=True):
54 """
55 resizeGrid (world, rows, cols, centralizeData=True)
56
57 Resize the matrix passed on 'world'.
58 If the new values of 'rows' and 'cols' are smaller than the current
59 matrix, the matrix is trimmed down starting from the last rows and
60 columns.
61
62 If centralizeData=True, any eventual data present in the matrix will
63 be placed centralized in the new matrix, for example
64 [[1,1],
65 [1,1]]
66 when resized to 4 rows and cols would become
67 [[0,0,0,0],
68 [0,1,1,0],
69 [0,1,1,0],
70 [0,0,0,0]]
71
72 this method is used for example to "zoom in" and "zoom out" the matrix
73 on graphical clients.
74 """
75 num_rows = len(world)
76 num_cols = len(world[0])
77 new_world = zeros([rows, cols])
78
79 #if it's centered, calculates the row/col offset to be used
80 row_offset = 0
81 col_offset = 0
82 if centralizeData and rows > num_rows:
83 row_offset = (rows/2) - (num_rows/2)
84 if centralizeData and cols > num_cols:
85 col_offset = (cols/2) - (num_cols/2)
86
87 #copy the data
88 for row in range(num_rows):
89 for col in range(num_cols):
90 if (row+row_offset) < len(new_world) and (col+col_offset) < len(new_world[0]):
91 new_world[row+row_offset, col+col_offset] = world[row, col]
92 return new_world
93
94 def playLife (self, world, debug=False):
95 """
96 playLife (world, debug=False)
97
98 This is the main method for the class.
99 It will return a matrix which is the result of the game rules
100 (defined by rule attribute) applied on the 'world' matrix.
101
102 If debug=True, the resulting matrix is also printed on the console.
103 """
104 self.world = world
105 self.num_rows = len(world)
106 self.num_cols = len(world[0])
107
108 if debug:
109 print "Original grid:"
110 print world
111
112 result = zeros([self.num_rows, self.num_cols])
113 for row in range(self.num_rows):
114 for col in range(self.num_cols):
115 result [row, col] = self.applyRules(row, col)
116
117 if debug:
118 print "Result grid:"
119 print result
120 return result
121
122
123 def applyRules (self, row, col):
124 """
125 applyRules (row, col)
126
127 Apply the rule defined on self.rule for the cell(row,col) in the matrix
128 self.world. This method is usually not called directly, but called
129 through playLife.
130 """
131 survival_rule = [int(c) for c in self.rule[:self.rule.find("/")]]
132 birth_rule = [int(c) for c in self.rule[self.rule.find("/")+1:]]
133 cell = 0
134 sum_neighb = self.countNeighbours(row,col)
135 if self.world[row,col] == 1:
136 if sum_neighb in survival_rule:
137 cell = 1
138 else:
139 if sum_neighb in birth_rule:
140 cell = 1
141 return cell
142
143 if __name__ == '__main__':
144 #initialize grids
145 testworld = array([[1, 0, 1, 0, 0, 0, 0, 0],
146 [0, 1, 1, 0, 0, 0, 0, 0],
147 [0, 1, 0, 0, 0, 0, 0, 0],
148 [0, 0, 0, 0, 0, 0, 0, 0],
149 [0, 0, 0, 0, 0, 0, 0, 0]])
150
151 #play Life!
152 lg = LifeGame()
153 for i in range(1,5):
154 print "-" * 20
155 print "Iteration", i
156 testworld = lg.playLife(testworld, True)
157 print "-" * 20
158 print "This test matrix had a glider on the top corner, that moved"
159 print "down and right after 4 iterations, as you'd expect a"
160 print "glider to do :)"
Volta para CookBook.