patternpythonMinor
Game of Life, separation of logic / GUI
Viewed 0 times
logicgamelifeguiseparation
Problem
This is one of my first steps with GUI. I've tried an implementation of Conway's Game of Life with TkInter.
I would appreciate any opinions about my code, especially about the separation of GUI and logic.
```
import Tkinter as tk
from copy import deepcopy
from random import randrange
def random_fields(width):
"""
Returns a set of tuples of random integers, eg: set((2,4), (6,1)).
These tuples are meant as coordiantes.
The parameter width is the maximum value of x and y.
"""
fields_alive = width * width // 6
fields = set()
for _ in range(fields_alive):
x = randrange(width)
y = randrange(width)
fields.add((x,y))
return fields
class Field(object):
""" Represents a field in Game of Life. """
def __init__(self, x, y):
""" Constructs a "dead" field at coords x, y """
self.x = x
self.y = y
self.is_alive = False
def __str__(self):
return "Field: \n x: {0}; y: {1}; is_alive: {2}\n".format(self.x, self.y, self.is_alive)
def change(self):
""" Changes from dead to alive and vice-versa. """
if self.is_alive:
self.is_alive = False
else:
self.is_alive = True
class GameOfLifeMatrix(object):
"""
Represents a matrix in Game of Life and contains the logic.
The global STARTFIELDS is a dict, with some well known repeating patterns like:
"blinker" and "toad"
"""
STARTFIELDS = { "blinker": ((2,1), (2,2), (2,3)),
"toad": ((2,2), (3,2), (4,2), (1,3), (2,3), (3,3))
}
def __init__(self, width = 25, start_fields = "random"):
"""
Constructs a new square matrix for Game Of Life.
parameters:
width: integer, the width and height of the matrix
start_fields, you can provide:
- "blinker" or "toad" for the well known patterns
- "random" to create a random matrix
- a set of tuples containing spec
I would appreciate any opinions about my code, especially about the separation of GUI and logic.
```
import Tkinter as tk
from copy import deepcopy
from random import randrange
def random_fields(width):
"""
Returns a set of tuples of random integers, eg: set((2,4), (6,1)).
These tuples are meant as coordiantes.
The parameter width is the maximum value of x and y.
"""
fields_alive = width * width // 6
fields = set()
for _ in range(fields_alive):
x = randrange(width)
y = randrange(width)
fields.add((x,y))
return fields
class Field(object):
""" Represents a field in Game of Life. """
def __init__(self, x, y):
""" Constructs a "dead" field at coords x, y """
self.x = x
self.y = y
self.is_alive = False
def __str__(self):
return "Field: \n x: {0}; y: {1}; is_alive: {2}\n".format(self.x, self.y, self.is_alive)
def change(self):
""" Changes from dead to alive and vice-versa. """
if self.is_alive:
self.is_alive = False
else:
self.is_alive = True
class GameOfLifeMatrix(object):
"""
Represents a matrix in Game of Life and contains the logic.
The global STARTFIELDS is a dict, with some well known repeating patterns like:
"blinker" and "toad"
"""
STARTFIELDS = { "blinker": ((2,1), (2,2), (2,3)),
"toad": ((2,2), (3,2), (4,2), (1,3), (2,3), (3,3))
}
def __init__(self, width = 25, start_fields = "random"):
"""
Constructs a new square matrix for Game Of Life.
parameters:
width: integer, the width and height of the matrix
start_fields, you can provide:
- "blinker" or "toad" for the well known patterns
- "random" to create a random matrix
- a set of tuples containing spec
Solution
In terms of your specific question, I think you have done a good job of separating out the simulation and the presentation. However, you have a few slightly awkward bits of code:
could be simplified to
and
to
and
to
Your approach to
def change(self):
""" Changes from dead to alive and vice-versa. """
if self.is_alive:
self.is_alive = False
else:
self.is_alive = Truecould be simplified to
def change(self):
""" Changes from dead to alive and vice-versa. """
self.is_alive = not self.is_aliveand
if not self.STARTFIELDS.get(start_fields) is None:to
if start_fields in self.STARTFIELDS:and
self.matrix = list()
for x in range(width):
row = list()
self.matrix.append(row)
for y in range(width):
field = Field(x, y)
if (x, y) in self.start_fields:
field.change()
row.append(field)to
matrix = [[Field(x, y) for y in range(width)] for x in range(width)]
for x, y in self.start_fields:
matrix[x][y].change()Your approach to
next_generation could be improved; if you made a list of all cells that _should_change, then changed them, you could do it in-place and wouldn't need the deepcopy. Separating out _should_change was a good idea, but you could make more of it.Code Snippets
def change(self):
""" Changes from dead to alive and vice-versa. """
if self.is_alive:
self.is_alive = False
else:
self.is_alive = Truedef change(self):
""" Changes from dead to alive and vice-versa. """
self.is_alive = not self.is_aliveif not self.STARTFIELDS.get(start_fields) is None:if start_fields in self.STARTFIELDS:self.matrix = list()
for x in range(width):
row = list()
self.matrix.append(row)
for y in range(width):
field = Field(x, y)
if (x, y) in self.start_fields:
field.change()
row.append(field)Context
StackExchange Code Review Q#27638, answer score: 2
Revisions (0)
No revisions yet.