#!/usr/bin/env python
"""
 OneDCA.py: One-dimensional elementary cellular automaton 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
from time import *
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 required."

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

	# Convert rule number to map (look-up table)
	#   from neighborhoods to next values
	#
def RuleNumberToLUT(rulenum):
	lut = [(rulenum/pow(2,i)) % 2 for i in range(8)]
	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[4*state[(j-1)%nSites]+2*state[j]+state[(j+1)%nSites]]
		   for j in range(nSites)]
	return new

	# Space-time diagram display, wrap around
	#
def SpTmDisplayState(state,nSites,t,CellSize):
	glColor3f(1.0,1.0,1.0)   # Erase current row
	glRecti(0,(nSteps-t-1)*CellSize,nSites*CellSize,(nSteps-t)*CellSize)
	glColor3f(0.0,0.0,0.0)   # Black
	for i in range(nSites):
		if state[i] > 0:
			glRecti(i*CellSize,(nSteps-t-1)*CellSize,
				(i+1)*CellSize,(nSteps-t)*CellSize)
	if (t % nSteps) == 0: screen.flip()

	# Space-time diagram display, scrolling
	#
def SpTmDisplayStateScroll(state,nSites,nSteps,CellSize):
	# Copy front buffer to back buffer:
		# Set buffer to read from. Destination is GL_BACK; this never changes.
	glReadBuffer(GL_FRONT)
		# Offset copy by CellSize pixels to implement vertical scroll
	glRasterPos2i(0,CellSize)
	glCopyPixels(0,0,nSites*CellSize,(nSteps-1)*CellSize,GL_COLOR)
		# If glCopyPixels and related commands elsewhere, use:
#	glReadBuffer(GL_BACK)
	# Write new state at bottom row
	glColor3f(1.0,1.0,1.0)  # White
	glRecti(0,0,nSites*CellSize,CellSize) # Erase current row
	glColor3f(0.0,0.0,0.0)  # Black
	for i in range(nSites):
		if state[i] > 0:
			glRecti(i*CellSize,0,(i+1)*CellSize,CellSize)
	screen.flip()

# Set defaults
CellSize = 4
nSteps   = 150
nSites   = 200
dorandom = 1
rule     = 18
	# Two display formats: Scroll or Wrap
DisplayFormat = 'Scroll'

# Get command line arguments, if any
opts,args = getopt.getopt(sys.argv[1:],'t:n:rwR:')
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 = 0
	if key == '-w': DisplayFormat = 'Wrap'
	if key == '-R': rule     = int(val)

	# Set initial configuration
state = CAInit(nSites,dorandom)
	# Translate rule number to look-up table
LUT = RuleNumberToLUT(rule)

	# Make spacetime diagram by iterating CA
	#
screen = window.Window(CellSize*nSites,CellSize*nSteps,
	caption = '1D Elementary Cellular Automaton Simulator',resizable = False)
screen.set_location(100,50)
screen.clear()

t = 0
FirstTime = 1
LastTime = 0.0
while not screen.has_exit:
	screen.dispatch_events()
		# Iterate state
	state = CAIterate(state,nSites,LUT)
		# Display state in spacetime diagram
	if DisplayFormat == 'Scroll':	
		if (FirstTime == 1):
			SpTmDisplayState(state,nSites,t,CellSize)
			screen.flip()
			if (t == nSteps - 1):
				FirstTime = 0
		else:
			SpTmDisplayStateScroll(state,nSites,nSteps,CellSize)
	else:
		SpTmDisplayState(state,nSites,t,CellSize)
	t += 1
	t %= nSteps
	if t == 0:
		print "%d steps in %f secs; %f sps" \
			% (nSteps, clock()-LastTime, nSteps/(clock()-LastTime))
		LastTime = clock()
