2017-05-07 18:47:32 +02:00
#!/usr/bin/env python3
2017-05-21 11:11:24 +02:00
2017-05-07 18:47:32 +02:00
import pygame
from OpenGL . GL import *
from pygame . locals import *
import argparse
2017-05-21 11:11:24 +02:00
import sys
import random
import time
2017-05-07 18:47:32 +02:00
2017-05-07 21:23:09 +02:00
#TODO: remove global vars
2017-05-07 18:47:32 +02:00
pygame . init ( )
window_w = 1600
window_h = 900
livingSpaceWidth = 64
livingSpaceHeight = 36
creatureW = window_w / ( livingSpaceWidth )
creatureH = window_h / ( livingSpaceHeight )
2017-05-21 11:48:09 +02:00
FPS = 30
2017-05-07 18:47:32 +02:00
NORTH = 0
EAST = 1
SOUTH = 2
WEST = 3
livingSpace = [ ]
2017-05-07 23:02:51 +02:00
livingSpaceColor = [ ]
2017-05-07 18:47:32 +02:00
update_queue = [ ]
old_update_queue = [ ]
2017-05-07 23:02:51 +02:00
# doubled draw buffer because of display double buffering
2017-05-07 18:47:32 +02:00
draw_buffer = [ ]
draw_buffer_old = [ ]
antPosition = ( livingSpaceWidth / / 2 , livingSpaceHeight / / 2 )
antRotation = NORTH
2017-05-07 23:02:51 +02:00
num_colors = 2
color_list = [ ]
code = [ False , True ]
2017-05-21 11:11:24 +02:00
# store number of active cells
num_active_cells = 0
2017-05-07 23:02:51 +02:00
# helper function for colors:
def HSVtoRGB ( h , s , v ) :
c = v * s
x = c * ( 1 - abs ( ( h / 60 ) % 2 - 1 ) )
m = v - c
rr = 0
gg = 0
bb = 0
if ( h < 60 ) :
rr = c
gg = x
bb = 0
elif ( h < 120 ) :
rr = x
gg = c
bb = 0
elif ( h < 180 ) :
rr = 0
gg = c
bb = x
elif ( h < 240 ) :
rr = 0
gg = x
bb = c
elif ( h < 300 ) :
rr = x
gg = 0
bb = c
elif ( h < 360 ) :
rr = c
gg = 0
bb = x
return ( rr + m , gg + m , bb + m )
def generate_colors ( ) :
global color_list
global num_colors
color_list = [ ]
for i in range ( num_colors ) :
color_list . append ( HSVtoRGB ( i * 360.0 / num_colors , 1 , 1 ) )
2017-05-07 18:47:32 +02:00
def resize ( shape ) :
width , height = shape
if height == 0 :
height = 1
glViewport ( 0 , 0 , width , height )
glMatrixMode ( GL_PROJECTION )
glLoadIdentity ( )
glOrtho ( 0.0 , livingSpaceWidth * creatureW , livingSpaceHeight * creatureH , 0.0 , - 6.0 , 0.0 )
glMatrixMode ( GL_MODELVIEW )
glLoadIdentity ( )
def init ( ) :
glClearColor ( 0.0 , 0.0 , 0.0 , 0.0 )
def initLivingSpace ( ) :
for x in range ( livingSpaceWidth ) :
livingSpace . append ( [ ] )
2017-05-07 23:02:51 +02:00
livingSpaceColor . append ( [ ] )
2017-05-07 18:47:32 +02:00
for y in range ( livingSpaceHeight ) :
2017-05-07 23:02:51 +02:00
livingSpace [ x ] . append ( 0 )
rgba = [ float ( x ) / float ( livingSpaceWidth ) , float ( livingSpaceWidth - x ) / float ( livingSpaceWidth ) , float ( y ) / float ( livingSpaceHeight ) , 0.15 ]
livingSpaceColor [ x ] . append ( rgba )
2017-05-07 18:47:32 +02:00
draw_buffer . append ( ( x , y ) )
def isAlive ( x , y ) :
2017-05-07 23:02:51 +02:00
return livingSpace [ x ] [ y ] != 0
2017-05-07 18:47:32 +02:00
def draw ( ) :
global draw_buffer
global draw_buffer_old
#glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
glLoadIdentity ( )
glTranslatef ( 0.0 , 0.0 , 3.0 )
glBegin ( GL_QUADS )
for column , row in draw_buffer + draw_buffer_old :
2017-05-07 23:02:51 +02:00
r , g , b , a = livingSpaceColor [ column ] [ row ]
2017-05-07 18:47:32 +02:00
2017-05-07 23:02:51 +02:00
#glColor4f(255, 0, 0, 1.0)
glColor4f ( a * r , a * g , a * b , 1.0 )
x = column * creatureW
y = row * creatureH
2017-05-07 18:47:32 +02:00
2017-05-07 23:02:51 +02:00
glVertex3f ( x , y , 0.0 )
glVertex3f ( x + creatureW - 1.0 , y , 0.0 )
glVertex3f ( x + creatureW - 1 , y + creatureH - 1 , 0.0 )
glVertex3f ( x , y + creatureH - 1 , 0.0 )
2017-05-07 18:47:32 +02:00
# draw langton's ant quad:
x = antPosition [ 0 ] * creatureW
y = antPosition [ 1 ] * creatureH
offX = creatureW / 2
offY = creatureH / 2
glColor4f ( 1 , 1 , 1 , 0.5 )
2017-05-21 11:11:24 +02:00
glVertex3f ( x + offX , y , 0.0 ) if antRotation != SOUTH else glVertex3f ( x + offX , y + creatureH / 2 , 0.0 )
glVertex3f ( x + creatureW - 1 , y + offY , 0.0 ) if antRotation != WEST else glVertex3f ( x + creatureW / 2 , y + offY , 0.0 )
glVertex3f ( x + offX , y + creatureH - 1 , 0.0 ) if antRotation != NORTH else glVertex3f ( x + offX , y + creatureH / 2 , 0.0 )
glVertex3f ( x , y + offY , 0.0 ) if antRotation != EAST else glVertex3f ( x + creatureW / 2 , y + offY , 0.0 )
2017-05-07 18:47:32 +02:00
glEnd ( )
draw_buffer_old = draw_buffer
draw_buffer = [ ]
2017-05-07 23:02:51 +02:00
def activate ( i , j , key = 1 ) :
2017-05-21 11:11:24 +02:00
global num_active_cells
if livingSpace [ i ] [ j ] == 0 :
num_active_cells + = 1
2017-05-07 23:02:51 +02:00
livingSpace [ i ] [ j ] = key
if num_colors > 2 :
livingSpaceColor [ i ] [ j ] = [
color_list [ key - 1 ] [ 0 ] ,
color_list [ key - 1 ] [ 1 ] ,
color_list [ key - 1 ] [ 2 ] ,
1.0
]
else :
livingSpaceColor [ i ] [ j ] [ 3 ] = 1.0
2017-05-07 18:47:32 +02:00
update_queue . append ( ( i , j ) )
def deactivate ( i , j ) :
2017-05-21 11:11:24 +02:00
global num_active_cells
if livingSpace [ i ] [ j ] != 0 :
num_active_cells - = 1
2017-05-07 23:02:51 +02:00
livingSpace [ i ] [ j ] = 0
# correct color:
livingSpaceColor [ i ] [ j ] = [
float ( i ) / float ( livingSpaceWidth ) ,
float ( livingSpaceWidth - i ) / float ( livingSpaceWidth ) ,
float ( j ) / float ( livingSpaceHeight ) ,
0.6
]
2017-05-07 18:47:32 +02:00
update_queue . append ( ( i , j ) )
def update_field ( ) :
global old_update_queue
global update_queue
old_update_queue = update_queue
update_queue = [ ]
for i , j in old_update_queue :
draw_buffer . append ( ( i , j ) )
2017-05-07 23:02:51 +02:00
if livingSpace [ i ] [ j ] < = 0 and livingSpaceColor [ i ] [ j ] [ 3 ] > 0.15 :
livingSpaceColor [ i ] [ j ] [ 3 ] * = 0.98
if livingSpaceColor [ i ] [ j ] [ 3 ] < 0.15 :
livingSpaceColor [ i ] [ j ] [ 3 ] = 0.15
update_queue . append ( ( i , j ) )
elif livingSpace [ i ] [ j ] > 0 and livingSpaceColor [ i ] [ j ] [ 3 ] > 0.6 :
livingSpaceColor [ i ] [ j ] [ 3 ] * = 0.98
if livingSpaceColor [ i ] [ j ] [ 3 ] < 0.6 :
livingSpaceColor [ i ] [ j ] [ 3 ] = 0.6
2017-05-07 18:47:32 +02:00
update_queue . append ( ( i , j ) )
2017-05-07 21:23:09 +02:00
def move_ant ( dx , dy ) :
global antPosition
global antRotation
# finally move:
antPosition = ( antPosition [ 0 ] + dx , antPosition [ 1 ] + dy )
# wrap around:
if antPosition [ 0 ] < 0 :
antPosition = ( livingSpaceWidth - 1 , antPosition [ 1 ] )
elif antPosition [ 0 ] > = livingSpaceWidth :
antPosition = ( 0 , antPosition [ 1 ] )
if antPosition [ 1 ] < 0 :
antPosition = ( antPosition [ 0 ] , livingSpaceHeight - 1 )
elif antPosition [ 1 ] > = livingSpaceHeight :
antPosition = ( antPosition [ 0 ] , 0 )
update_queue . append ( antPosition )
def update_ant ( ) :
2017-05-07 18:47:32 +02:00
global antPosition
global antRotation
2017-05-07 23:02:51 +02:00
# if we have only two colors, we will switch between on and off
if num_colors == 2 :
# switch cell
if isAlive ( antPosition [ 0 ] , antPosition [ 1 ] ) :
deactivate ( antPosition [ 0 ] , antPosition [ 1 ] )
else :
activate ( antPosition [ 0 ] , antPosition [ 1 ] )
2017-05-07 18:47:32 +02:00
2017-05-07 23:02:51 +02:00
# turn
2017-05-21 11:48:09 +02:00
code_index = 1 if isAlive ( antPosition [ 0 ] , antPosition [ 1 ] ) else 0
if code [ code_index ] :
2017-05-07 23:02:51 +02:00
# turn right
2017-05-21 11:48:09 +02:00
antRotation = ( antRotation + 3 ) % 4
2017-05-07 23:02:51 +02:00
else :
# turn left
2017-05-21 11:48:09 +02:00
antRotation = ( antRotation + 1 ) % 4
2017-05-07 18:47:32 +02:00
else :
2017-05-07 23:02:51 +02:00
# if we have more than 3 colors, we use the color code
old_key = livingSpace [ antPosition [ 0 ] ] [ antPosition [ 1 ] ] - 1
new_key = ( livingSpace [ antPosition [ 0 ] ] [ antPosition [ 1 ] ] ) % num_colors + 1 # avoiding key zero, it is deactivated
if not isAlive ( antPosition [ 0 ] , antPosition [ 1 ] ) :
# first activation time, increase key:
new_key + = 1
old_key + = 1
#determine direction:
if code [ old_key ] :
# turn left:
antRotation = ( antRotation + 3 ) % 4
else :
# turn right:
antRotation = ( antRotation + 1 ) % 4
# then activate with new key
activate ( antPosition [ 0 ] , antPosition [ 1 ] , new_key )
2017-05-07 18:47:32 +02:00
# move on step:
dx = 0
dy = 0
if antRotation == NORTH :
dy = - 1
elif antRotation == SOUTH :
dy = + 1
elif antRotation == EAST :
dx = + 1
elif antRotation == WEST :
dx = - 1
2017-05-07 21:23:09 +02:00
move_ant ( dx , dy )
2017-05-07 18:47:32 +02:00
def main ( ) :
global livingSpaceWidth
global livingSpaceHeight
global creatureW
global creatureH
global antPosition
2017-05-21 11:11:24 +02:00
global antRotation
2017-05-07 21:23:09 +02:00
global window_w
global window_h
2017-05-07 23:02:51 +02:00
global color_list
global num_colors
global code
2017-05-07 18:47:32 +02:00
# parsing args:
2017-05-21 11:48:09 +02:00
parser = argparse . ArgumentParser ( description = " langton \' s ant simulation tool " , formatter_class = argparse . RawTextHelpFormatter )
2017-05-26 22:44:37 +02:00
parser . add_argument ( ' --steps ' , dest = ' steps ' , default = 20 , help = ' steps per second. Default = 20 ' )
2017-05-07 18:47:32 +02:00
parser . add_argument ( ' --w ' , dest = ' w ' , default = livingSpaceWidth , help = ' field width ' )
parser . add_argument ( ' --h ' , dest = ' h ' , default = livingSpaceHeight , help = ' field height ' )
parser . add_argument ( ' --calc ' , dest = ' calc ' , default = 0 , help = ' calculate steps and only display result ' )
2017-05-07 21:23:09 +02:00
parser . add_argument ( ' --fullscreen ' , dest = ' fullscreen ' , action = ' store_true ' )
parser . add_argument ( ' --window_w ' , dest = ' win_w ' , default = window_w , help = ' window width ' )
parser . add_argument ( ' --window_h ' , dest = ' win_h ' , default = window_h , help = ' window height ' )
2017-05-07 21:32:10 +02:00
parser . add_argument ( ' --configurator ' , dest = ' configurator ' , action = ' store_true ' , help = ' start in field edit mode ' )
2017-05-21 11:48:09 +02:00
parser . add_argument ( ' --code ' , dest = ' code ' , default = ' 10 ' , help = ' binary code for the ant ( \' 10 \' corresponds to the starndard ant behaviour). 1 means \' turn left \' , 0 means \' turn right \' ' )
2017-05-21 11:11:24 +02:00
parser . add_argument ( ' --file ' , dest = ' file ' , default = ' ' , help = ' writing number of living cells per step in this file ' )
parser . add_argument ( ' --pattern ' , dest = ' pattern ' , default = ' 0 ' ,
help = ' initial pattern for the field. Possible values: \n ' +
' \t * 0: all fields inactive \n ' +
' \t * 1: all fields active \n ' +
' \t * check: checkboard pattern \n ' +
' \t * horizontal: horizontal stripes \n ' +
' \t * vertical: vertical stripes \n ' +
' \t * random: random values \n ' )
2017-05-07 21:23:09 +02:00
parser . set_defaults ( fullscreen = False )
parser . set_defaults ( configurator = False )
2017-05-07 18:47:32 +02:00
2017-05-26 22:44:37 +02:00
if ( len ( sys . argv ) == 1 ) :
# no parameters given. Print help and ask user at runtime for options:
settings = { }
settings [ " steps " ] = 20
settings [ " w " ] = livingSpaceWidth
settings [ " h " ] = livingSpaceHeight
2017-05-27 12:06:47 +02:00
settings [ " calc " ] = 0
2017-05-26 22:44:37 +02:00
settings [ " fullscreen " ] = False
settings [ " window_w " ] = window_w
settings [ " window_h " ] = window_h
settings [ " configurator " ] = False
settings [ " code " ] = ' 10 '
settings [ " file " ] = " "
settings [ " pattern " ] = ' 0 '
while True :
parser . print_help ( )
print ( " current settings: " )
for key in settings . keys ( ) :
print ( str ( key ) + " = " + str ( settings [ key ] ) )
a = input ( " enter parameter to change. press enter to continue: " )
if len ( a ) == 0 :
break
val = input ( " enter new value: " )
settings [ a ] = val
# passing settings to parser:
parser . set_defaults ( steps = settings [ " steps " ] )
parser . set_defaults ( w = settings [ " w " ] )
parser . set_defaults ( h = settings [ " h " ] )
parser . set_defaults ( calc = settings [ " calc " ] )
parser . set_defaults ( fullscreen = settings [ " fullscreen " ] )
parser . set_defaults ( window_w = settings [ " window_w " ] )
parser . set_defaults ( window_h = settings [ " window_h " ] )
parser . set_defaults ( configurator = settings [ " configurator " ] )
parser . set_defaults ( code = settings [ " code " ] )
if len ( settings [ " file " ] ) > 0 :
parser . set_defaults ( file = settings [ " file " ] )
parser . set_defaults ( pattern = settings [ " pattern " ] )
2017-05-07 18:47:32 +02:00
args = parser . parse_args ( )
steps_per_sec = int ( args . steps )
livingSpaceWidth = int ( args . w )
livingSpaceHeight = int ( args . h )
calc = int ( args . calc )
2017-05-21 11:11:24 +02:00
filename = args . file
fileobj = None
if len ( filename ) > 0 :
fileobj = open ( filename , mode = ' w ' )
2017-05-07 18:47:32 +02:00
2017-05-07 21:23:09 +02:00
video_flags = OPENGL | HWSURFACE | DOUBLEBUF
2017-05-07 23:02:51 +02:00
# parse code:
code = [ ]
for c in args . code :
if c == ' 0 ' :
code . append ( False )
else :
code . append ( True )
# generate colors:
num_colors = len ( code )
generate_colors ( )
2017-05-07 21:23:09 +02:00
if args . fullscreen :
video_flags = OPENGL | HWSURFACE | DOUBLEBUF | FULLSCREEN
2017-05-07 21:32:10 +02:00
dinfo = pygame . display . Info ( )
2017-05-07 21:23:09 +02:00
window_w = dinfo . current_w
window_h = dinfo . current_h
else :
window_w = int ( args . win_w )
window_h = int ( args . win_h )
2017-05-07 19:18:34 +02:00
2017-05-07 18:47:32 +02:00
creatureW = window_w / ( livingSpaceWidth )
creatureH = window_h / ( livingSpaceHeight )
antPosition = ( livingSpaceWidth / / 2 , livingSpaceHeight / / 2 )
pygame . display . set_mode ( ( window_w , window_h ) , video_flags )
initLivingSpace ( )
2017-05-07 19:18:34 +02:00
2017-05-07 18:47:32 +02:00
resize ( ( window_w , window_h ) )
init ( )
clock = pygame . time . Clock ( )
frames = 0
counter = 0
logic_frame_pause = FPS / steps_per_sec
2017-05-07 21:23:09 +02:00
configurator_mode = bool ( args . configurator )
2017-05-07 18:47:32 +02:00
2017-05-07 23:02:51 +02:00
field_draws = 0
2017-05-21 11:11:24 +02:00
# apply pattern
if args . pattern == ' 0 ' :
# nothing to do
pass
elif args . pattern == ' 1 ' :
for i in range ( livingSpaceWidth ) :
for j in range ( livingSpaceHeight ) :
activate ( i , j )
elif args . pattern == ' check ' :
for i in range ( livingSpaceWidth ) :
for j in range ( livingSpaceHeight ) :
if ( i + j ) % 2 == 0 :
activate ( i , j )
elif args . pattern == ' horizontal ' :
for i in range ( livingSpaceWidth ) :
for j in range ( livingSpaceHeight ) :
m = j % num_colors
if num_colors > 2 :
m + = 1
if m != 0 :
activate ( i , j , m )
elif args . pattern == ' vertical ' :
for i in range ( livingSpaceWidth ) :
for j in range ( livingSpaceHeight ) :
m = i % num_colors
if num_colors > 2 :
m + = 1
if m != 0 :
activate ( i , j , m )
elif args . pattern == ' random ' :
r = random . Random ( time . time ( ) )
for i in range ( livingSpaceWidth ) :
for j in range ( livingSpaceHeight ) :
k = r . randint ( 0 , num_colors - 1 )
if num_colors > 2 :
k + = 1
if k != 0 :
activate ( i , j , k )
else :
print ( " error. unknown pattern " )
parser . print_help ( )
sys . exit ( - 1 )
2017-05-07 18:47:32 +02:00
if ( calc > 0 ) :
for i in range ( calc ) :
2017-05-21 11:11:24 +02:00
update_ant ( )
2017-05-07 18:47:32 +02:00
#main loop:
while True :
ticktime = clock . tick ( FPS )
#print ticktime
event = pygame . event . poll ( )
if event . type == QUIT or ( event . type == KEYDOWN and event . key == K_ESCAPE ) :
break
2017-05-21 11:11:24 +02:00
update_field ( )
2017-05-07 18:47:32 +02:00
draw ( )
2017-05-07 23:02:51 +02:00
field_draws + = len ( draw_buffer ) + len ( draw_buffer_old )
frames + = 1
if frames % FPS == 0 :
print ( " average field draws per frame: " + str ( field_draws / FPS ) )
field_draws = 0
2017-05-07 18:47:32 +02:00
pygame . display . flip ( )
2017-05-07 21:23:09 +02:00
if configurator_mode :
# move with keys:
if event . type == KEYDOWN :
draw_buffer . append ( ( antPosition [ 0 ] , antPosition [ 1 ] ) )
if event . key == K_DOWN :
move_ant ( 0 , + 1 )
elif event . key == K_UP :
move_ant ( 0 , - 1 )
elif event . key == K_LEFT :
move_ant ( - 1 , 0 )
elif event . key == K_RIGHT :
move_ant ( + 1 , 0 )
elif event . key == K_SPACE :
2017-05-07 23:02:51 +02:00
new_key = ( livingSpace [ antPosition [ 0 ] ] [ antPosition [ 1 ] ] ) % num_colors + 1
if not isAlive ( antPosition [ 0 ] , antPosition [ 1 ] ) :
new_key + = 1
activate ( antPosition [ 0 ] , antPosition [ 1 ] , new_key )
elif event . key == K_BACKSPACE :
deactivate ( antPosition [ 0 ] , antPosition [ 1 ] )
2017-05-21 11:11:24 +02:00
elif event . key == K_LCTRL :
antRotation = ( antRotation - 1 ) % 4
move_ant ( 0 , 0 )
elif event . key == K_RCTRL :
antRotation = ( antRotation + 1 ) % 4
move_ant ( 0 , 0 )
2017-05-07 21:23:09 +02:00
elif event . key == K_RETURN :
configurator_mode = False
2017-05-07 18:47:32 +02:00
2017-05-07 19:28:52 +02:00
else :
2017-05-07 18:47:32 +02:00
2017-05-07 21:23:09 +02:00
if ( calc == 0 ) :
counter + = 1
if logic_frame_pause > = 1 :
if counter > logic_frame_pause :
update_ant ( )
2017-05-21 11:11:24 +02:00
if fileobj != None :
fileobj . write ( str ( num_active_cells ) + ' \n ' )
2017-05-07 21:23:09 +02:00
counter = 0
else :
# multiple calculations per frame:
for i in range ( int ( 1 / logic_frame_pause ) ) :
update_ant ( )
2017-05-21 11:11:24 +02:00
if fileobj != None :
fileobj . write ( str ( num_active_cells ) + ' \n ' )
2017-05-07 21:23:09 +02:00
# switch to configurator mode:
if event . type == KEYDOWN and event . key == K_RETURN :
configurator_mode = True
2017-05-21 11:11:24 +02:00
if fileobj != None :
fileobj . close ( )
2017-05-07 18:47:32 +02:00
if __name__ == ' __main__ ' :
main ( )