Ho implementato il gioco della vita di Conway in Python. Avete in mente il simbolo hacker (il glider, quella griglia 3×3 con dei pallini neri)? Beh, sono strettramente legati 
Questa implementazione permette svariate cose, tra cui:
- Una volta impostata una griglia, vedere l’evolversi della situazione.
- Definite le dimensioni della griglia, trovare (con un bruteforce) tutte le figure che si ripetono dopo N stadi.
Ecco il codice (commentato parzialmente in inglese):
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
| #!/usr/bin/env python
# -*- coding: utf-8 -*-
try:
import psyco
from psyco.classes import __metaclass__
psyco.full()
except:
pass
class Life:
""" Implementation of the Conway's game of life """
def __init__(self, rows, columns):
""" Inizializes 2d table """
self.rows, self.columns = rows, columns
self.board = [[0,]*self.columns for row in xrange(self.rows)]
def get(self, row, column):
""" Gets value or return 0 """
check = 0 <= row < self.rows and 0 <= column < self.columns
return (check and self.board[row][column] or 0)
def add(self, row, column):
""" Adds eight cells near current cell """
return self.get(row - 1, column - 1) + \
self.get(row - 1, column) + \
self.get(row - 1, column + 1) + \
self.get(row, column - 1) + \
self.get(row, column + 1) + \
self.get(row + 1, column - 1) + \
self.get(row + 1, column) + \
self.get(row + 1, column + 1)
def next(self):
""" Calculates the next stage """
current = [row[:] for row in self.board] # It clones the table
for r, row in enumerate(current): # For every row...
for c, item in enumerate(row): # ...and for every column...
near = self.add(r, c) # ...it calculates near cells
if near not in (2, 3) and item: # Condition to death
current[r][c] = 0 # Let's kill it :(
elif near == 3 and not item: # Condition to became alive
current[r][c] = 1 # Let's live! :D
self.board = current # It overwrites the old stage
def show(self, target = False):
""" Shows the table """
for row in (target or self.board):
print("".join([column and "@" or "x" for column in row]))
def bruteforce(rows, columns):
life = Life(rows, columns)
possibilities = [list(i) for i in product((0, 1), repeat=columns)]
for select in product(xrange(len(possibilities)), repeat=rows):
life.board = [possibilities[i] for i in select]
history = [life.board]
while 1:
life.next()
if life.board in history:
if life.board == history[0]:
life.show(history[0])
print("")
break
history.append(life.board)
if __name__ == "__main__":
from itertools import product
from sys import argv
try:
rows, columns = int(argv[1]), int(argv[2])
except IndexError:
print("Usage: %s [rows] [columns]" % argv[0])
except ValueError:
print("Usage: %s [rows] [columns]" % argv[0])
else:
bruteforce(rows, columns)
"""
# Example code: glider (spaceship, hacker symbol)
if __name__ == "__main__": # If you're executing this program...
life = Life(6, 6) # It inizializes a 2d 6x6 table
life.board[2][0] = 1 # 2, 0 set alive
life.board[2][1] = 1 # 2, 1 set alive
life.board[2][2] = 1 # 2, 2 set alive
life.board[1][2] = 1 # 1, 2 set alive
life.board[0][1] = 1 # 0, 1 set alive
for time in range(12): # For n times...
life.next() # ...it makes the next stage...
life.show() # ...and it shows it :)
""" |
La prima porzione di codice è la seguente:
4
5
6
7
8
9
| try:
import psyco
from psyco.classes import __metaclass__
psyco.full()
except:
pass |
In questa maniera, importiamo, se disponibile, la libreria psyco, che, su macchine x86, permette di eseguire operazioni molto onerose per la cpu, in maniera molto più veloce 
Successivamente troviamo questa classe:
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
| class Life:
""" Implementation of the Conway's game of life """
def __init__(self, rows, columns):
""" Inizializes 2d table """
self.rows, self.columns = rows, columns
self.board = [[0,]*self.columns for row in xrange(self.rows)]
def get(self, row, column):
""" Gets value or return 0 """
check = 0 <= row < self.rows and 0 <= column < self.columns
return (check and self.board[row][column] or 0)
def add(self, row, column):
""" Adds eight cells near current cell """
return self.get(row - 1, column - 1) + \
self.get(row - 1, column) + \
self.get(row - 1, column + 1) + \
self.get(row, column - 1) + \
self.get(row, column + 1) + \
self.get(row + 1, column - 1) + \
self.get(row + 1, column) + \
self.get(row + 1, column + 1)
def next(self):
""" Calculates the next stage """
current = [row[:] for row in self.board] # It clones the table
for r, row in enumerate(current): # For every row...
for c, item in enumerate(row): # ...and for every column...
near = self.add(r, c) # ...it calculates near cells
if near not in (2, 3) and item: # Condition to death
current[r][c] = 0 # Let's kill it :(
elif near == 3 and not item: # Condition to became alive
current[r][c] = 1 # Let's live! :D
self.board = current # It overwrites the old stage
def show(self, target = False):
""" Shows the table """
for row in (target or self.board):
print("".join([column and "@" or "x" for column in row])) |
Questo codice è stato scritto con una particolare attenzione alle prestazioni. Cerchiamo di comprendere meglio le varie funzioni:
- __init__(self, rows, columns): dati due numeri interi, “disegna” una tabella delle dimensioni specificate
- get(self, row, column): è una funzione che viene usata per vedere se una cella è viva o morta. In caso questa cella non esista, ritorna semplicemente zero, come se fosse morta. Ho trovato questo metodo sensibilmente più veloce rispetto al fare un try/except (senza quindi controllare l’esistenza della cella in base alle coordinate), e molto più chiaro rispetto al creare una tabella con un bordo di una cella.
- add(self, row, column): date le coordinate di una cella, somma le otto celle accanto, utilizzando get().
- next(self): calcola lo stadio successivo.
- show(self, target=False): visualizza in maniera abbastanza “grezza” la nostra griglia sul terminale.
Qui finisce il “grosso” del codice. Successivamente abbiamo la funzione per il bruteforce:
47
48
49
50
51
52
53
54
55
56
57
58
59
60
| def bruteforce(rows, columns):
life = Life(rows, columns)
possibilities = [list(i) for i in product((0, 1), repeat=columns)]
for select in product(xrange(len(possibilities)), repeat=rows):
life.board = [possibilities[i] for i in select]
history = [life.board]
while 1:
life.next()
if life.board in history:
if life.board == history[0]:
life.show(history[0])
print("")
break
history.append(life.board) |
Questa funzione fa uso di itertools per calcolare le combinazioni possibili. Bisogna prestare particolarmente attenzione a piccoli accorgimenti, come il calcolare volta per volta ogni possibile combinazione, semplicemente “scegliendo” tra le “righe” generate. Ho trovato questo metodo particolarmente efficente e veloce.
Per sapere se una figura è già apparsa, crea una lista degli stadi, finché qualcosa non si ripete. Questo deve sempre avvenire, in quanto anche se non capitassimo di fronte a una figura “particolare”, dopo qualche passaggio tutte le celle morirebbero, e le ritroveremmo nuovamente morte nello stadio successivo, provocando una ripetizione (che, in questo caso, non verrà però visualizzata).
Il blocco successivo serve a rendere operativo il bruteforce:
62
63
64
65
66
67
68
69
70
71
72
| if __name__ == "__main__":
from itertools import product
from sys import argv
try:
rows, columns = int(argv[1]), int(argv[2])
except IndexError:
print("Usage: %s [rows] [columns]" % argv[0])
except ValueError:
print("Usage: %s [rows] [columns]" % argv[0])
else:
bruteforce(rows, columns) |
Come già accennato precedentemente, importiamo itertools.product (assieme a sys.argv), e prendiamo gli argomenti passati da linea di comando, che diamo in pasto a bruteforce().
L’ultima parte è commentata, e mostra un utilizzo alternativo della classe Life:
74
75
76
77
78
79
80
81
82
83
84
85
86
| """
# Example code: glider (spaceship, hacker symbol)
if __name__ == "__main__": # If you're executing this program...
life = Life(6, 6) # It inizializes a 2d 6x6 table
life.board[2][0] = 1 # 2, 0 set alive
life.board[2][1] = 1 # 2, 1 set alive
life.board[2][2] = 1 # 2, 2 set alive
life.board[1][2] = 1 # 1, 2 set alive
life.board[0][1] = 1 # 0, 1 set alive
for time in range(12): # For n times...
life.next() # ...it makes the next stage...
life.show() # ...and it shows it :)
""" |
Si inizializza una griglia col famoso simbolo hacker, e si mostrano gli stadi successivi
Simpatico, no?
P.S. Ho omesso la licenza per problemi di spazio… Ovviamente è GPLv3
frafra Python Conway, game of life, Python