"""Game of Life simulation. mylife3.py is a clone of life.py by me from freegames on 2018-11-10 (It contains interesting code at the very end. See comments.) Conway's game of life is a classic cellular automation created in 1970 by John Conway. https://en.wikipedia.org/wiki/Conway%27s_Game_of_Life Exercises 1. Can you identify any Still Lifes, Oscillators, or Spaceships? 2. How can you make the simulation faster? Or bigger? ->process just living cells and build a list of their dead neighbors accumulating the number of living neighbors for living and dead neighbors - birth any dead neighbors with exactly 3 living neighbors - kill any living cells with < 2 or > 3 live neighbors (this is a dangerous and weird neighborhood!) - update just the newly dead and newly born cells on the screen with their new colors - NOTE: after many loops the program slows down and garbage collection does not help. This is a mystery because the number of live cells doesn't seem to match how slow it runs (say, after 10 or 15 minutes.) 3. How would you modify the initial state? - alter the random number seed using a table of seeds and an index into the table that can be changed for each run. this is a way to collect interesting scenarios without specifying each individual starting cell. 4. Try changing the rules of life :) 9. ETC: - tried to get rid of the 'from turtle import *'; just import the turtle! proved to be extremely difficult. abandoned attempt. - the rules call for an unbounded cell array; make it so by connecting the top edge of the board to the bottom edge, and the left to the right: topologically a "torus". - also, give each generation a new color. loop through the Tk colorstring names in a pleasing order. - define a MyLife class to encapsulate state variables and get rid of global declarations - copied freegames.util.py#square() in to eliminate dependency on freegames - tried to account for every pixel to eliminate possible failures. nice... Free Python Games License ------------------------- Copyright 2017 Grant Jenks Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. """ import collections import random from turtle import * from enum import Enum STANDARD_MODE = 'standard' STANDARD_MODE_EAST = 0 #LOGO_MODE = 'logo' #LOGO_MODE_EAST = 90 XY_MAX = 250 BOX_SIZE = 10 LOOP_TIMER = 1 TURTLE_SPEED = 0 DEAD_COLOR = 'black' XY_INIT_RANDOM = 100 CHOICES = [1, 0] RANDOM_SEEDS_INDEX = 3 RANDOM_SEEDS = [ None, 127, 131, '37', '13', '5', '29', '87', '101', 'remaining seeds yet to be tested:', 151,157,163, 167,173,179,181,191,193,197,199,211,223,227,229,233,239,241,251, 257,263,269,271,277,281,283,293,307,311,313,317,331,337,347,349, 353,359,367,373,379,383,389,397,401,409,419,421,431,433,439,443, 449,457,461,463,467,479,487,491,499,503,509,521,523,541,547,557, 563,569,571,577,587,593,599,601,607,613,617,619,631,641,643,647, 653,659,661,673,677,683,691,701,709,719,727,733,739,743,751,757, 761,769,773,787,797,809,811,821,823,827,829,839,853,857,859,863, 877,881,883,887,907,911,919,929,937,941,947,953,967,971,977,983, 991,997,1009,1013,1019,1021,1031,1033,1039,1049,1051,1061,1063, 1069, 1087,1091,1093,1097,1103,1109,1117,1123,1129,1151,1153,1163,1171, 1181,1187,1193,1201,1213,1217,1223,1229,1231,1237,1249,1259,1277, 1279,1283,1289,1291,1297,1301,1303,1307,1319,1321,1327,1361,1367, 1373,1381,1399,1409,1423,1427,1429,1433,1439,1447,1451,1453,1459, 1471, 1481,1483,1487,1489,1493,1499,1511,1523,1531,1543,1549,1553,1559, 1567,1571,1579,1583,1597,1601,1607,1609,1613,1619,1621,1627,1637, 1657, 1663,1667,1669,1693,1697,1699,1709,1721,1723,1733,1741,1747,1753, 1759,1777,1783,1787,1789,1801,1811,1823,1831,1847,1861,1867,1871, 1873,1877,1879,1889,1901,1907,1913,1931,1933,1949,1951,1973,1979, 1987, 1993,1997,1999,2003,2011,2017,2027,2029,2039,2053,2063,2069,2081, 2083,2087,2089,2099,2111,2113,2129,2131,2137,2141,2143,2153,2161, 2179,2203,2207,2213,2221,2237,2239,2243,2251,2267,2269,2273,2281, 2287, 2293,2297,2309,2311,2333,2339,2341,2347,2351,2357,2371,2377,2381, 2383,2389,2393,2399,2411,2417,2423,2437,2441,2447,2459,2467,2473, 2477,2503,2521,2531,2539,2543,2549,2551,2557,2579,2591,2593,2609, 2617, 2621,2633,2647,2657,2659,2663,2671,2677,2683,2687,2689,2693,2699, 2707,2711,2713,2719,2729,2731,2741,2749,2753,2767,2777,2789,2791, 2797,2801,2803,2819,2833,2837,2843,2851,2857,2861,2879,2887,2897, 2903,2909,2917,2927,2939,2953,2957,2963,2969,2971,2999] NEW_LIFE_COLORS = [ 'snow', 'old lace', 'bisque', 'ivory', 'azure', 'white', 'light slate gray', 'cornflower blue', 'medium blue', 'sky blue', 'powder blue', 'cyan', 'dark green', 'light sea green', 'chartreuse', 'forest green', 'light goldenrod yellow', 'goldenrod', 'sienna', 'sandy brown', 'dark salmon', 'coral', 'hot pink', 'maroon', 'plum', 'blue violet', 'snow3', 'AntiqueWhite1', 'bisque3', 'ghost white', 'linen', 'peach puff', 'lemon chiffon', 'alice blue', 'gray', 'dark slate blue', 'royal blue', 'light sky blue', 'pale turquoise', 'light cyan', 'dark olive green', 'pale green', 'medium spring green', 'olive drab', 'light yellow', 'dark goldenrod', 'peru', 'tan', 'salmon', 'light coral','deep pink', 'medium violet red', 'orchid', 'purple', 'snow4', 'AntiqueWhite2', 'bisque4', 'white smoke', 'antique white', 'navajo white', 'seashell', 'lavender', 'dark slate gray', 'light grey', 'slate blue', 'blue', 'steel blue', 'dark turquoise', 'cadet blue', 'dark sea green', 'spring green', 'green yellow', 'dark khaki', 'yellow', 'rosy brown', 'burlywood', 'chocolate', 'light salmon', 'tomato', 'pink', 'violet red', 'medium orchid', 'medium purple', 'seashell2', 'AntiqueWhite3', 'PeachPuff2', 'gainsboro', 'papaya whip', 'moccasin', 'honeydew', 'lavender blush', 'dim gray', 'midnight blue', 'medium slate blue', 'dodger blue', 'light steel blue', 'medium turquoise', 'medium aquamarine', 'sea green', 'lawn green', 'lime green', 'khaki', 'gold', 'indian red', 'beige', 'firebrick', 'orange', 'orange red', 'light pink', 'magenta', 'dark orchid', 'thistle', 'seashell3', 'AntiqueWhite4', 'PeachPuff3', 'floral white', 'blanched almond', 'cornsilk', 'mint cream', 'misty rose', 'slate gray', 'navy', 'light slate blue', 'deep sky blue', 'light blue', 'turquoise', 'aquamarine', 'medium sea green', 'green', 'yellow green', 'pale goldenrod', 'light goldenrod', 'saddle brown', 'wheat', 'brown', 'dark orange', 'red', 'pale violet red', 'violet', 'dark violet', 'snow2', 'seashell4', 'bisque2', 'PeachPuff4'] Cell = collections.namedtuple('Cell', ['x', 'y']) BoardTopology = Enum('BoardTopology', 'TORUS SQUARE') class MyLife: def __init__(self, xyMax, boxSize, xyInitRandom, choices, deadColor, delay, seed=None, boardTopology=None, quitCheckExit=None): self.xyMax = xyMax self.boxSize = boxSize self.xyInitRandom = xyInitRandom self.width = 2 * self.xyMax self.height = 2 * self.xyMax #these cell coordinates are based on cell x,y == lower-left of box #and standard mode turtle heading initially == East (in standard mode). self.topCellY = self.xyMax - self.boxSize self.bottomCellY = -self.xyMax self.rightCellX = self.xyMax - self.boxSize self.leftCellX = -self.xyMax self.choices = choices self.deadColor = deadColor self.delay = delay self.seed = seed if boardTopology == None: self.boardTopology = BoardTopology.TORUS elif boardTopology not in [BoardTopology.TORUS, BoardTopology.SQUARE]: raise ValueError("Valid board topologies are TORUS and SQUARE.") else: self.boardTopology = boardTopology self.quitCheckExit = quitCheckExit self.livingCells = set() self.justBorn = set() self.justDied = set() self.colorIndex = 0 self.newLifeColor = NEW_LIFE_COLORS[self.colorIndex] mode(STANDARD_MODE) setheading(STANDARD_MODE_EAST) setup(width=self.width, height=self.height, startx=None, starty=None) hideturtle() tracer(False) clear() speed(TURTLE_SPEED) # Randomly initialize the cells random.seed(a=self.seed, version=2) for x in range(-self.xyMax, self.xyMax, self.boxSize): for y in range(-self.xyMax, self.xyMax, self.boxSize): if (-self.xyInitRandom <= x < self.xyInitRandom and -self.xyInitRandom <= y < self.xyInitRandom): if random.choice(CHOICES): self.justBorn.add(Cell(x, y)) self.livingCells.add(Cell(x, y)) else: self.justDied.add(Cell(x, y)) else: self.justDied.add(Cell(x, y)) def quitCheck(self): if self.quitCheckExit != None and self.quitCheckExit(): return True return False def drawCells(self): "Draw all the squares...that just changed" color(self.deadColor) for p in self.justDied: self.square(p.x, p.y, self.boxSize) color(self.newLifeColor) for p in self.justBorn: self.square(p.x, p.y, self.boxSize) def square(self, x, y, size): """Draw square at `(x, y)` with side length `size`. The square is oriented so the bottom left corner is at (x, y). Cloned from free-python-games-master/freegames/utils.py 2018-11-10. """ up() goto(x, y) down() begin_fill() forward(size) left(90) forward(size) left(90) forward(size) left(90) forward(size) left(90) end_fill() def updateCells(self): "Compute one step in the Game of Life." self.justBorn.clear() self.justDied.clear() deadNeighbors = {} for p in self.livingCells: aliveNeighborCount = -1 for h in [-self.boxSize, 0, self.boxSize]: for v in [-self.boxSize, 0, self.boxSize]: qX = p.x + h qY = p.y + v if self.boardTopology == BoardTopology.TORUS: if qX > self.rightCellX: qX = self.leftCellX elif qX < self.leftCellX: qX = self.rightCellX if qY > self.topCellY: qY = self.bottomCellY elif qY < self.bottomCellY: qY = self.topCellY q = Cell(qX, qY) if q in self.livingCells: aliveNeighborCount += 1 elif (self.boardTopology == BoardTopology.TORUS or (self.leftCellX <= q.x <= self.rightCellX and self.bottomCellY <= q.y <= self.topCellY)): deadNeighbors[q] = deadNeighbors.get(q, 0) + 1 if aliveNeighborCount < 2 or aliveNeighborCount > 3: self.justDied.add(p) for p in self.justDied: self.livingCells.remove(p) for p, aliveNeighborCount in deadNeighbors.items(): if aliveNeighborCount == 3: self.justBorn.add(p) self.livingCells.add(p) self.colorIndex = (self.colorIndex + 1) % len(NEW_LIFE_COLORS) self.newLifeColor = NEW_LIFE_COLORS[self.colorIndex] global myLife myLife = MyLife(XY_MAX, BOX_SIZE, XY_INIT_RANDOM, CHOICES, DEAD_COLOR, LOOP_TIMER, RANDOM_SEEDS[RANDOM_SEEDS_INDEX], BoardTopology.TORUS, None) def draw(): if not myLife.quitCheck(): myLife.drawCells() update() myLife.updateCells() ontimer(draw, LOOP_TIMER) draw() # done() <- must be last line, ... and if ontimer() earlier in code # omitted alt-Q or Python/File/Quit do not work, u need to cancel # the shell. done()