# coding=UTF-8 import sys, os import math import time import random import tkFileDialog import Tkinter import pyglet from pyglet.window import key, mouse from pyglet import clock import fileutils from lifepattern import LifePattern # CONSTANTS OFF_COLOR = (200, 200, 200) # VARIABLES root = Tkinter.Tk() window = pyglet.window.Window(width=800, height=600) window.set_caption('Conway\'s Game Of Life') # grid curX = 0 curY = 0 gridDims = {} # cells cellBatch = pyglet.graphics.Batch() cellVLists = [] curOnCells = set() otherOnCells = set() cellDims = {} curCellDim = 15.0 # input mVars = {} keys = key.KeyStateHandler() # text infoDoc = pyglet.text.document.UnformattedDocument('') infoDoc.set_style(None, None, {'color': (0, 0, 0, 255), 'background_color': (255, 255, 255, 255), 'font_name': 'Arial', 'font_size': 12}) infoLayout = pyglet.text.layout.TextLayout(infoDoc) infoLayout.anchor_y = 'bottom' # misc isRunning = False curPattern = None # 0 = 0° # 1 = 90° clockwise # 2 = 180° # 3 = 270° rotation = 0 # FUNCTIONS def configGrid(desiredCellDim): ''' resets the graphical aspect of the grid, the number and size of cells. does not affect actual cell on/off data. desiredCellDim is the desired width/height of the cell in pixels. actual size will vary slightly to fit a whole number of rows and columns on screen. this should be called when zooming in/out and when window changes size. ''' cellDims['w'] = window.width / (window.width // desiredCellDim) cellDims['h'] = window.height / (window.height // desiredCellDim) cellDims['borderW'] = 1.0 if cellDims > 1 else 0.0 gridDims['wInCells'] = int(round(window.width / cellDims['w'])) gridDims['hInCells'] = int(round(window.height / cellDims['h'])) # clear vertex list del cellVLists[:] # no way to clear a Batch :( global cellBatch cellBatch = pyglet.graphics.Batch() for x in xrange(gridDims['wInCells']): cellVLists.append([]) for y in xrange(gridDims['hInCells']): blitX, blitY = x * cellDims['w'], y * cellDims['h'] cellVLists[-1].append(cellBatch.add(4, pyglet.gl.GL_QUADS, None, ('v2f', (blitX + cellDims['borderW'] / 2, blitY + cellDims['borderW'] / 2, blitX + cellDims['w'] - cellDims['borderW'] / 2, blitY + cellDims['borderW'] / 2, blitX + cellDims['w'] - cellDims['borderW'] / 2, blitY + cellDims['h'] - cellDims['borderW'] / 2, blitX + cellDims['borderW'] / 2, blitY + cellDims['h'] - cellDims['borderW'] / 2)), ('c3B', OFF_COLOR * 4) )) updateGrid() def resetMVars(): mVars['curChange'] = None mVars['cellsPassed'] = set() def changeCell(x, y, which, updateOnCells = True): ''' updates the display of cell. if updateOnCells is True, also changes curOnCells ''' if which == 'on': desired = True elif which == 'off': desired = False else: raise ArgumentError, '3rd argument must be "on" or "off"' if updateOnCells: if desired: curOnCells.add((x, y)) else: curOnCells.discard((x, y)) x, y = x - curX, y - curY if (x < gridDims['wInCells'] and x >= 0 and y < gridDims['hInCells'] and y >= 0): if desired: cellVLists[x][y].colors = (0, 0, 0) * 4 else: cellVLists[x][y].colors = OFF_COLOR * 4 def updateGrid(): for x in xrange(curX, curX + gridDims['wInCells']): for y in xrange(curY, curY + gridDims['hInCells']): if (x, y) in curOnCells: cellVLists[x - curX][y - curY].colors = (0, 0, 0) * 4 else: cellVLists[x - curX][y - curY].colors = OFF_COLOR * 4 _counts = {} def runOne(): global curOnCells, otherOnCells _counts.clear() otherOnCells.clear() for cell in curOnCells: for x in xrange(cell[0] - 1, cell[0] + 2): for y in xrange(cell[1] - 1, cell[1] + 2): if (x, y) != cell: try: _counts[(x, y)] += 1 except KeyError: _counts[(x, y)] = 1 for cell in _counts: if _counts[cell] == 3 or (_counts[cell] == 2 and cell in curOnCells): otherOnCells.add(cell) for cell in curOnCells - otherOnCells: changeCell(cell[0], cell[1], 'off', updateOnCells = False) for cell in otherOnCells - curOnCells: changeCell(cell[0], cell[1], 'on', updateOnCells = False) curOnCells, otherOnCells = otherOnCells, curOnCells def insertPattern(x, y, pattern): unencoded = pattern.getUnencoded() for dy, line in enumerate(unencoded): for dx, cell in enumerate(line): if rotation == 0: changeCell(x + dx, y - dy, 'on' if cell else 'off') elif rotation == 1: changeCell(x + pattern.height - dy, y - dx, 'on' if cell else 'off') elif rotation == 2: changeCell(x + pattern.width - dx, y - pattern.height + dy, 'on' if cell else 'off') elif rotation == 3: changeCell(x + dy, y - pattern.width + dx, 'on' if cell else 'off') def setInfoText(): infoDoc.text = (' Current pattern: %s' % str(curPattern) + (' ' if curPattern else ' - press enter to open a pattern') + u' || Rotation: %d° CW' % (rotation * 90)) # EVENTS @window.event def on_draw(): window.clear() if keys[key.UP] or keys[key.DOWN] or keys[key.RIGHT] or keys[key.LEFT]: global curX, curY curY += (keys[key.UP] - keys[key.DOWN]) * int(20 / cellDims['w']) curX += (keys[key.RIGHT] - keys[key.LEFT]) * int(20 / cellDims['w']) updateGrid() cellBatch.draw() infoLayout.draw() @window.event def on_mouse_press(x, y, button, modifiers): if button == mouse.LEFT: cellX, cellY = int(x // cellDims['w']) + curX, int(y // cellDims['h']) + curY if (cellX, cellY) in curOnCells: mVars['curChange'] = 'off' else: mVars['curChange'] = 'on' changeCell(cellX, cellY, mVars['curChange']) mVars['cellsPassed'].add((cellX, cellY)) elif button == mouse.RIGHT: cellX, cellY = int(x // cellDims['w']) + curX, int(y // cellDims['h']) + curY if curPattern: insertPattern(cellX, cellY, curPattern) @window.event def on_mouse_drag(x, y, dx, dy, buttons, modifiers): if buttons & mouse.LEFT: # draw through all cells in line dragged through factor = math.sqrt(x * x + y * y) dx, dy = dx / factor, dy / factor for i in xrange(int(factor)): cellX, cellY = int(x // cellDims['w']) + curX, int(y // cellDims['h']) + curY if ((cellX, cellY) not in mVars['cellsPassed'] and cellX >= curX and cellX < curX + gridDims['wInCells'] and cellY >= curY and cellY < curY + gridDims['wInCells']): if (cellX, cellY) in curOnCells and mVars['curChange'] == 'off': changeCell(cellX, cellY, mVars['curChange']) elif (cellX, cellY) not in curOnCells and mVars['curChange'] == 'on': changeCell(cellX, cellY, mVars['curChange']) mVars['cellsPassed'].add((cellX, cellY)) x -= dx y -= dy @window.event def on_mouse_release(x, y, button, modifiers): resetMVars() @window.event def on_key_press(symbol, modifiers): if symbol == key.SPACE: global isRunning isRunning = not isRunning elif symbol == key.C: curOnCells.clear() updateGrid() elif symbol == key.PLUS: global curCellDim curCellDim += 1 configGrid(curCellDim) elif symbol == key.MINUS: global curCellDim curCellDim -= 1 configGrid(curCellDim) elif symbol == key.ENTER: global curPattern filename = tkFileDialog.askopenfilename() ## root.destroy() if filename: curPattern = LifePattern(filename) setInfoText() elif symbol == key.R: global rotation rotation = (rotation + 1) % 4 setInfoText() # CLOCK def runPerFrame(dt): if isRunning: runOne() # INIT root.withdraw() resetMVars() window.push_handlers(keys) clock.schedule(runPerFrame) configGrid(curCellDim) setInfoText() pyglet.app.run()