'''
MINI|MARCH (wolfram lab)
a line-based automata program
Small's Clone Industries, 2005

---------------------------------------------------

VARIABLES

THE MARCH

SYSTEM and DISPLAY

MAIN CALLS

---------------------------------------------------
'''

import curses, random, sys, copy, traceback, smidi
# http://larndham.net/service/pys60/smidi.py

# VARIABLES ---------------------------------------

# system things
LineSize = 70
WholeScreen = 0 ; ScreenRows = 0 ; ScreenCols = 0
DisplayScreen = 0 ; DisplayRows = 0 ; DisplayCols = 0
ControlScreen = 0 ; ControlRows = 0 ; ControlCols = 0

# initialize the CurrentLine variable
CurrentLine = []

# initialize the OldLines variable (a list of lists)
OldLines = []

# initialize the Ticks variable
Ticks = 0

# kinds of troops
Off = 0
On = 1

# how many kinds of troops we're using
Kinds = 2

# What is the current Rule?
RuleInt = 0
RuleBin = [0,0,0,0,0,0,0,0]

# for the Diverse Rule setting
DiverseP = False
DiverseList = []

# for the music system
ScoreFileName = 'score.midi'
ScoreRecordingP = False
ScoreFile = ""
ScoreTemp = 50
ScoreInst = 0
ScoreRest = 0

# THE MARCH

def SetRule(integer):
''' input: an integer (1-255) | output: none; sets Rule variables '''
global RuleInt, RuleBin, On, Off
i = copy.copy(integer)
tempBin = []
while i > 0:
tempBin.insert(0,i % 2)
i = i >> 1
while len(tempBin) < 8:
tempBin.insert(0,Off)
RuleInt = integer
RuleBin = tempBin

def JudgeValue(l,c,r):
'''
input: 3 On/Off values | output: an On/Off value
'''
global On, Off
resultCell = 0
# generate the result
if r == On and c == On and l == On : resultCell = 0
elif r == On and c == On and l == Off: resultCell = 1
elif r == On and c == Off and l == On : resultCell = 2
elif r == On and c == Off and l == Off: resultCell = 3
elif r == Off and c == On and l == On : resultCell = 4
elif r == Off and c == On and l == Off: resultCell = 5
elif r == Off and c == Off and l == On : resultCell = 6
elif r == Off and c == Off and l == Off: resultCell = 7
return RuleBin[resultCell]

# this is the one that determines the rule
def NewValue(index, prevList):
'''
input: location of cell, previous list | output: the cell's new value
'''
global LineSize, CurrentLine, DiverseP, DiverseList, RuleInt, RuleBin
# set indicies for neighbors
rIndex = index-1
cIndex = index
lIndex = index+1
if rIndex < 0: rIndex = LineSize + rIndex
if lIndex >= LineSize: lIndex = lIndex - LineSize
# handle the arbitrary mode
if DiverseP:
cellRule = DiverseList[cIndex]
SetRule(cellRule)
return JudgeValue(prevList[lIndex],prevList[cIndex],prevList[rIndex])

def Advance():
global OldLines, CurrentLine, Ticks, ScoreRecordingP
RecordToScore()
nextLine = []
for troop in range(len(CurrentLine)):
nextLine.append(NewValue(troop, CurrentLine))
# finally, reassign CurrentLine
OldLines.append(CurrentLine)
CurrentLine = nextLine
Ticks += 1

def RecordToScore():
global CurrentLine
global ScoreRecordingP, ScoreInst, ScoreTemp, ScoreFile, ScoreRest
if ScoreRecordingP:
for i in range(len(CurrentLine)):
if CurrentLine[i] == On:
ToggleNote(True,ScoreInst,i)
ScoreFile.update_time(ScoreTemp)
for i in range(len(CurrentLine)):
ToggleNote(False,ScoreInst,i)
ScoreFile.update_time(ScoreRest)

def ToggleNote(onP, inst, note):
''' turn a MIDI note on or off '''
global ScoreFile
n = 30 + note
if onP:
ScoreFile.note_on(note=n)
else:
ScoreFile.note_off(note=n)

def InitializeSingle():
''' initializes with a single On cell in the middle '''
global LineSize, CurrentLine
tempLine = []
for i in range(LineSize):
tempLine.append(Off)
tempLine[LineSize/2] = On
CurrentLine = tempLine
RecordToScore()

def InitializeRandom():
'''
input: none | output: none; CurrentLine initialized
'''
global CurrentLine, LineSize
tempLine = []
for i in range(LineSize):
k = random.randint(0,Kinds-1)
tempLine.append(k)
CurrentLine = tempLine
RecordToScore()

def ClearSlate():
''' clears things up and sets up the variables '''
global OldLines, DisplayScreen, CurrentLine, Ticks
OldLines = []
CurrentLine = []
Ticks = 0
DisplayScreen.clear() ; DisplayScreen.refresh()

def DiverseRule():
''' sets an arbitrary rule state '''
global DiverseP, DiverseList, LineSize
random.seed()
DiverseP = True
DiverseList = []
for i in range(LineSize):
k = random.randint(0,255)
DiverseList.append(k)

def SetCell(troopIndex):
'''
input: an index value | output: none; reverses a cell on CurrentLine
'''
global CurrentLine
CurrentLine[troopIndex] = (CurrentLine[troopIndex] + 1) % 2

def SeedDialog():
''' handles human interaction '''
global ControlScreen
ControlScreen.addstr(3,0,'SEED (R)ANDOM OR (S)INGLE? ')
ControlScreen.refresh()
key = ControlScreen.getch()
if key == ord('s'): InitializeSingle()
if key == ord('r'): InitializeRandom()
ControlScreen.addstr(3,0,'') ; ControlScreen.clrtoeol()
ControlScreen.refresh()

def RuleDialog():
''' handles human interaction '''
global RuleInt, RuleBin, DiverseP
ControlScreen.addstr(3,0,'Set Rule (0...255) or (d)iverse: ')
ControlScreen.refresh()
curses.echo()
input = ControlScreen.getstr()
if input.isdigit():
r = int(input)
if 0 <= r <= 255:
SetRule(r)
DiverseP = False
elif input == 'd':
DiverseRule()
curses.noecho()
ControlScreen.addstr(3,0,'') ; ControlScreen.clrtoeol()
ControlScreen.refresh()

def SetCellDialog():
''' handles human interaction '''
global ControlScreen, LineSize
if CurrentLine != []:
ls = LineSize
ControlScreen.addstr(3,0,'Change Cell (0...' + str(ls-1) + '): ')
ControlScreen.refresh()
curses.echo()
input = ControlScreen.getstr()
if input.isdigit():
index = int(input)
if 0 <= index < ls:
SetCell(index)
curses.noecho()
ControlScreen.addstr(3,0,'') ; ControlScreen.clrtoeol()
ControlScreen.refresh()

def RecordToggle():
''' toggles the record function '''
global ScoreRecordingP, ScoreTemp, ScoreFileName, ScoreFile, ScoreRest
global ControlScreen
if ScoreRecordingP:
ScoreFile.update_time(0)
ScoreFile.end_of_track()
ScoreFile.eof()
ScoreRecordingP = False
else:
ControlScreen.addstr(3,0,'Tempo (1...): ')
ControlScreen.refresh()
curses.echo()
input = ControlScreen.getstr()
if input.isdigit():
t = int(input)
if t > 0:
ScoreTemp = t
ControlScreen.addstr(3,0,'') ; ControlScreen.clrtoeol()
ControlScreen.addstr(3,0,'Rest (0...): ')
ControlScreen.refresh()
input = ControlScreen.getstr()
if input.isdigit():
r = int(input)
if r > -1:
ScoreRest = r
curses.noecho()
ControlScreen.addstr(3,0,'') ; ControlScreen.clrtoeol()
ControlScreen.refresh()
ScoreFile = smidi.MidiOutFile('score.mid')
ScoreFile.header()
ScoreFile.start_of_track()
ScoreFile.update_time(0)
ScoreRecordingP = True

# SYSTEM and DISPLAY

def Start():
'''
input: none | output: none; system variables initialized
'''
global LineSize
global WholeScreen, ScreenRows, ScreenCols
global DisplayScreen, DisplayRows, DisplayCols
global ControlScreen, ControlRows, ControlCols
WholeScreen = curses.initscr()
# curses.curs_set(0) # invisible cursor
curses.noecho() # turn off key echo
curses.cbreak() # enter cbreak mode
ScreenRows, ScreenCols = WholeScreen.getmaxyx()
DisplayRows, DisplayCols = (ScreenRows-5, ScreenCols-1)
DisplayScreen = curses.newwin(DisplayRows,DisplayCols,1,1)
DisplayScreen.border()
ControlScreen = curses.newwin(4,ScreenCols-1,DisplayRows,1)
LineSize = DisplayCols
WholeScreen.addstr(0,1,'MiniMarch | the army automata (Wolfram lab)')
# initialize CurrentLine
global CurrentLine
tempLine = []
for i in range(LineSize):
tempLine.append(Off)
CurrentLine = tempLine
Display()

def MouseHandler(mouseEvent):
'''
input: mouse event | output: none; the mouse click is handled.
this particular one changes colors on the CurrentLine.
'''
global DisplayRows, CurrentLine, ControlScreen
id, x, y, z, bstate = mouseEvent
ControlScreen.addstr(3,0,'y: ' + str(y))
if y == DisplayRows: # if it clicks on CurrentRow
SetCell(x-1)

def MainLoop():
''' The main loop of the program '''
global WholeScreen, CurrentLine
while True:
Display()
key = WholeScreen.getch()
if key == curses.KEY_MOUSE: MouseHandler(WholeScreen.getmouse())
if key == ord('q'): Close() ; break
if key == ord(' ') and CurrentLine != []: Advance()
if key == ord('s'): SeedDialog()
if key == ord('r'): RuleDialog()
if key == ord('e'): ClearSlate()
if key == ord('c'): SetCellDialog()
if key == ord('m'): RecordToggle()

def PrintLine(list, row):
'''
input: a list and a row | output: none; prints list to screen at row
'''
global DisplayScreen, DisplayCols
for i in range(DisplayCols):
value = ''
if list[i] == 0:
DisplayScreen.addstr(row,i,' ',curses.A_NORMAL)
else:
DisplayScreen.addstr(row,i,' ',curses.A_REVERSE)

def Display():
'''
input: none | output: none; prints data to DisplayScreen
'''
global WholeScreen, ControlScreen, DisplayRows, OldLines, Ticks, LineSize
oldTemp = copy.copy(OldLines)
currentRowIndex = DisplayRows - 2
# first, print CurrentLine
if len(CurrentLine) > 0:
PrintLine(CurrentLine, currentRowIndex)
currentRowIndex -= 2 # puts a space between Current and rest
# then, print old lines
for oldy in OldLines:
oldline = oldTemp.pop()
if currentRowIndex > -1 and len(oldline) > 0:
PrintLine(oldline,currentRowIndex)
currentRowIndex -= 1
# build control screen
index = ''
for i in range(LineSize):
if i % 10 == 0:
index += '|'
elif i % 5 == 0:
index += ':'
else:
index += '-'
global AribitraryP, DiverseBase, ScoreRecordingP
if DiverseP: r = 'Div.'
else: r = str(RuleInt)
status = 'Rule: ' + r.ljust(6)
status += 'Ticks: ' + str(Ticks).ljust(6)
if ScoreRecordingP:
status += '*Recording* (toggle again to save)'
options = '(S)EED SET(R)ULE CHANGE(C)ELL '
options += 'R(E)FRESH REC(M)USIC (Q)UIT'
ControlScreen.addstr(0,0,index)
ControlScreen.addstr(1,0,options)
ControlScreen.addstr(2,0,status) ; ControlScreen.clrtoeol()
# finished; refresh screen
DisplayScreen.refresh()
ControlScreen.refresh()
WholeScreen.refresh()

def Close():
''' closes the program down '''
curses.nocbreak()
curses.echo()
curses.endwin()
# curses.curs_set(2)

# MAIN CALLS ------------------------------------------------

try:
Start()
MainLoop()
except:
Close()
print 'ERROR!', sys.exc_info()[0], sys.exc_info()[1]
traceback.print_exc()