#!/usr/bin/env python
"""
 TwoDECA.py: Two-dimensional CA simulator
 	Using PyGlet graphics
	2008 (C) JPC

 Options:
 -t #  Number of time steps
 -n #  Size of lattice
 -c #  Size of cell
 -r #  Random initial configuration, rather than a single ON site
 -R #  Use rule # for the simulation
"""

	# Import modules
	#
import getopt,sys
from random import randint
import time
from numpy import zeros
try:
		# Disable error checking for increased performance
	from pyglet import options
	options['debug_gl'] = False
	from pyglet import window
	from pyglet.gl import *
except ImportError:
	raise ImportError, "PyGlet package required."

	# Celullar automata routines
	# Initialize CA lattice, load in initial configuration
	#
def CAInit(nSites,dorandom):
	state = zeros((nSites,nSites),dtype=int)
	if dorandom:   # Random IC
		for i in range(nSites):
			for j in range(nSites): state[i][j] = randint(0,1)
	else:          # Single ON site
		state[nSites/2][nSites/2] = 1
	return state

	# Convert rule number to map (look-up table)
	#   from neighborhoods to next values
	#	Binary CA, radius 1.
	#   von Neumann neighborhood
	#            a
	#           bcd
	#            e
	#	LUT index = abcde
	#
def RuleNumberToLUT(rulenum):
	nStates = 2
	Radius  = 1
	nEntries = pow(nStates,4*Radius+1)
	lut = [(rulenum/pow(2,i)) % 2 for i in range(nEntries)]
	return lut

	# Sum mod 2 of local states in neighborhood
	#
def SumMod2RuleLUT():
	nStates = 2
	Radius  = 1
	nNeighbors = 4 * Radius + 1
	nEntries = pow(nStates,nNeighbors)
	lut = [0]*nEntries
	for i in range(nEntries):
		sum = 0
		for j in range(nNeighbors):
			if i & (1<<j): sum += 1
		lut[i] = sum % 2
	return lut

	# Random rule
	#
def RandomRuleLUT():
	nStates = 2
	Radius  = 1
	nNeighbors = 4 * Radius + 1
	nEntries = pow(nStates,nNeighbors)
	lut = [0]*nEntries
	for i in range(nEntries): lut[i] = randint(0,1)
	return lut

	# Iterate the CA lattice, producing a spacetime diagram:
	#   New value of site is a function of previous values of
	#      itself and two neighbors.
	#   Form integer of the previous values, use as index into look-up table.
	#   Impose spatially periodic boundary conditions.
	#
def CAIterate(state,nSites,rule):
	new = [[
		rule[ 8*state[(i-1)%nSites][j] + 4*state[i][j]
			+ 2*state[(i+1)%nSites][j]
			+ 16*state[i][(j-1)%nSites] + state[i][(j+1)%nSites] ]
			for i in range(nSites)]
			for j in range(nSites)]
	return new

	# Spatial configuration display
	#
def SpDisplayState(state,nSites,CellSize):
	glColor3f(1.0,0.0,0.0)   # Erase current buffer
	glRecti(0,0,nSites*CellSize,nSites*CellSize)
	glColor3f(0.1,0.4,0.5)   # ON site color
	for i in range(nSites):
		for j in range(nSites):
			if state[i][j] > 0:
				glRecti(i*CellSize,j*CellSize,(i+1)*CellSize,(j+1)*CellSize)
	screen.flip()

# Set defaults
CellSize = 8
nSteps   = 100
nSites   = 107
dorandom = 0
rule     = 18

# Get command line arguments, if any
opts,args = getopt.getopt(sys.argv[1:],'t:n:c:rR:')
for key,val in opts:
	if key == '-t': nSteps   = int(val)
	if key == '-n': nSites   = int(val)
	if key == '-c': CellSize = int(val)
	if key == '-r': dorandom = 1
	if key == '-R': rule     = int(val)

	# Set initial configuration
state = CAInit(nSites,dorandom)
	# Build rule's look-up table
LUT = SumMod2RuleLUT()
# LUT = RandomRuleLUT()

	# Get display surface
screen = window.Window(CellSize*nSites,CellSize*nSteps,
	caption = '2D Cellular Automaton Simulator',resizable = False)
screen.set_location(100,50)
screen.clear()

t = 0
t0 = time.clock()
while not screen.has_exit:
	screen.dispatch_events()
		# Iterate state
	state = CAIterate(state,nSites,LUT)
		# Display state
	SpDisplayState(state,nSites,CellSize)
	t += 1
	if (t % nSteps) == 0:
		t1 = time.clock()
		print "Iterations per second: ", float(nSteps) / (t1 - t0)
		t0 = t1
