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
7 in 1..8 representing how many neighbours a cell must have
8 to stay alive, and dd are digits in 1..8 representing how
9 many neighbours a dead cell must have to become alive.
10
11 For example, the default conway rule (which is used if no
12 string is passed as parameter) is "23/3", which means a cell
13 stay alive if it has 2 or 3 neighbours, and becomes alive if
14 it has 3 neighbours.
15 """
16 if not rule:
17 #if not rule is passed, use the default one
18 self.rule = "23/3"
19 else:
20 self.rule = rule
21
22 def countNeighbours (self, row, col):
23 """
24 countNeighbours (row, col)
25
26 Returns a number between 0 and 8, counting how many
27 'living' neighbours (=1)
28 a cell specified by x,y has.
29 """
30 total = 0
31
32 #count the living neighbours (=1) on the row above
33 if row > 0:
34 total += self.world[row-1, col]
35 if col > 0:
36 total += self.world[row-1, col-1]
37 if col < self.num_cols - 1:
38 total += self.world[row-1, col+1]
39
40 #count the living neighbours on the left and right
41 if col > 0:
42 total += self.world[row, col-1]
43 if col < self.num_cols - 1:
44 total += self.world[row, col+1]
45
46 #count the living neighbours on the row below
47 if row < self.num_rows - 1:
48 total += self.world[row+1, col]
49 if col > 0:
50 total += self.world[row+1, col-1]
51 if col < self.num_cols - 1:
52 total += self.world[row+1, col+1]
53 return total
54
55 def resizeGrid (self, world, rows, cols, centralizeData=True):
56 """
57 resizeGrid (world, rows, cols, centralizeData=True)
58
59 Resize the matrix passed on 'world'.
60 If the new values of 'rows' and 'cols' are smaller than
61 the current matrix, the matrix is trimmed down starting
62 from the last rows and columns.
63
64 If centralizeData=True, any eventual data present in the
65 matrix will be placed centralized in the new matrix, for
66 example
67 [[1,1],
68 [1,1]]
69 when resized to 4 rows and cols would become
70 [[0,0,0,0],
71 [0,1,1,0],
72 [0,1,1,0],
73 [0,0,0,0]]
74
75 this method is used for example to "zoom in" and "zoom
76 out" the matrix on graphical clients.
77 """
78 num_rows = len(world)
79 num_cols = len(world[0])
80 new_world = zeros([rows, cols])
81
82 #if it's centered, calculates the row/col offset to be used
83 row_offset = 0
84 col_offset = 0
85 if centralizeData and rows > num_rows:
86 row_offset = (rows/2) - (num_rows/2)
87 if centralizeData and cols > num_cols:
88 col_offset = (cols/2) - (num_cols/2)
89
90 #copy the data
91 for row in range(num_rows):
92 for col in range(num_cols):
93 if (row+row_offset) < len(new_world) and \
94 (col+col_offset) < len(new_world[0]):
95 new_world[row+row_offset, col+col_offset] = \
96 world[row, col]
97 return new_world
98
99 def playLife (self, world, debug=False):
100 """
101 playLife (world, debug=False)
102
103 This is the main method for the class.
104 It will return a matrix which is the result of the game
105 rules (defined by rule attribute) applied on the 'world'
106 matrix.
107
108 If debug=True, the resulting matrix is also printed on
109 the console.
110 """
111 self.world = world
112 self.num_rows = len(world)
113 self.num_cols = len(world[0])
114
115 if debug:
116 print "Original grid:"
117 print world
118
119 result = zeros([self.num_rows, self.num_cols])
120 for row in range(self.num_rows):
121 for col in range(self.num_cols):
122 result [row, col] = self.applyRules(row, col)
123
124 if debug:
125 print "Result grid:"
126 print result
127 return result
128
129
130 def applyRules (self, row, col):
131 """
132 applyRules (row, col)
133
134 Apply the rule defined on self.rule for the cell(row,col)
135 in the matrix self.world. This method is usually not
136 called directly, but called through playLife.
137 """
138 survival_rule = [int(c) for c in self.rule[:self.rule.find("/")]]
139 birth_rule = [int(c) for c in self.rule[self.rule.find("/")+1:]]
140 cell = 0
141 sum_neighb = self.countNeighbours(row,col)
142 if self.world[row,col] == 1:
143 if sum_neighb in survival_rule:
144 cell = 1
145 else:
146 if sum_neighb in birth_rule:
147 cell = 1
148 return cell
149
150 if __name__ == '__main__':
151 #initialize grids
152 testworld = array([[1, 0, 1, 0, 0, 0, 0, 0],
153 [0, 1, 1, 0, 0, 0, 0, 0],
154 [0, 1, 0, 0, 0, 0, 0, 0],
155 [0, 0, 0, 0, 0, 0, 0, 0],
156 [0, 0, 0, 0, 0, 0, 0, 0]])
157
158 #play Life!
159 lg = LifeGame()
160 for i in range(1,5):
161 print "-" * 20
162 print "Iteration", i
163 testworld = lg.playLife(testworld, True)
164 print "-" * 20
165 print "This test matrix had a glider on the top corner, that moved"
166 print "down and right after 4 iterations, as you'd expect a"
167 print "glider to do :)"
Volta para CookBook.