"""This module is part of Swampy, a suite of programs available from allendowney.com/swampy. Copyright 2011 Allen B. Downey Distributed under the GNU General Public License at gnu.org/licenses/gpl.html. """ import math from World import World class CellWorld(World): """Contains cells and animals that move between cells.""" def __init__(self, canvas_size=500, cell_size=5, interactive=False): World.__init__(self) self.title('CellWorld') self.canvas_size = canvas_size self.cell_size = cell_size # cells is a map from index tuples to Cell objects self.cells = {} if interactive: self.make_canvas() self.make_control() def make_canvas(self): """Creates the GUI.""" self.canvas = self.ca(width=self.canvas_size, height=self.canvas_size, bg='white', scale = [self.cell_size, self.cell_size]) def make_control(self): """Adds GUI elements that allow the user to change the scale.""" self.la(text='Click or drag on the canvas to create cells.') self.row([0,1,0]) self.la(text='Cell size: ') self.cell_size_en = self.en(width=10, text=str(self.cell_size)) self.bu(text='resize', command=self.rescale) self.endrow() def bind(self): """Creates bindings for the canvas.""" self.canvas.bind('', self.click) self.canvas.bind('', self.click) def click(self, event): """Event handler for clicks and drags. It creates a new cell or toggles an existing cell. """ # convert the button click coordinates to an index tuple x, y = self.canvas.invert([event.x, event.y]) i, j = int(math.floor(x)), int(math.floor(y)) # toggle the cell if it exists; create it otherwise cell = self.get_cell(i,j) if cell: cell.toggle() else: self.make_cell(x, y) def make_cell(self, i, j): """Creates and returns a new cell at i,j.""" cell = Cell(self, i, j) self.cells[i,j] = cell return cell def cell_bounds(self, i, j): """Return the bounds of the cell with indices i, j.""" p1 = [i, j] p2 = [i+1, j] p3 = [i+1, j+1] p4 = [i, j+1] bounds = [p1, p2, p3, p4] return bounds def get_cell(self, i, j, default=None): """Gets the cell at i, j or returns the default value.""" cell = self.cells.get((i,j), default) return cell four_neighbors = [(1,0), (-1,0), (0,1), (0,-1)] eight_neighbors = four_neighbors + [(1,1), (1,-1), (-1,1), (-1,-1)] def get_four_neighbors(self, cell, default=None): """Return the four Von Neumann neighbors of a cell.""" return self.get_neighbors(cell, default, CellWorld.four_neighbors) def get_eight_neighbors(self, cell, default=None): """Returns the eight Moore neighbors of a cell.""" return self.get_neighbors(cell, default, CellWorld.eight_neighbors) def get_neighbors(self, cell, default=None, deltas=[(0,0)]): """Return the neighbors of a cell. Args: cell: Cell deltas: a list of tuple offsets. """ i, j = cell.indices cells = [self.get_cell(i+di, j+dj, default) for di, dj in deltas] return cells def rescale(self): """Event handler that rescales the world. Reads the new scale from the GUI, changes the canvas transform, and redraws the world. """ cell_size = self.cell_size_en.get() cell_size = int(cell_size) self.canvas.transforms[0].scale = [cell_size, cell_size] self.redraw() def redraw(self): """Clears the canvas and redraws all cells and animals.""" self.canvas.clear() for cell in self.cells.itervalues(): cell.draw() for animal in self.animals: animal.draw() class Cell(object): """A rectangular region in CellWorld""" def __init__(self, world, i, j): self.world = world self.indices = i, j self.bounds = self.world.cell_bounds(i, j) # options used for a marked cell self.marked_options = dict(fill='black', outline='gray80') # options used for an unmarked cell self.unmarked_options = dict(fill='yellow', outline='gray80') self.marked = False self.draw() def draw(self): """Draw the cell.""" if self.marked: options = self.marked_options else: options = self.unmarked_options # bounds returns all four corners, so slicing every other # element yields two opposing corners, which is what we # pass to Canvas.rectangle coords = self.bounds[::2] self.item = self.world.canvas.rectangle(coords, **options) def undraw(self): """Delete any items with this cell's tag.""" self.item.delete() self.item = None def get_config(self, option): """Gets the configuration of this cell.""" return self.item.cget(option) def config(self, **options): """Configure this cell with the given options.""" self.item.config(**options) def mark(self): """Marks this cell.""" self.marked = True self.config(**self.marked_options) def unmark(self): """Unmarks this cell.""" self.marked = False self.config(**self.unmarked_options) def is_marked(self): """Checks whether this cell is marked.""" return self.marked def toggle(self): """Toggles the state of this cell.""" if self.is_marked(): self.unmark() else: self.mark() if __name__ == '__main__': world = CellWorld(interactive=True) world.bind() world.mainloop()

AltStyle によって変換されたページ (->オリジナル) /