commit 225afbfc5e5ba362d5ebc352227903086e488620 Author: Jonas Weinz Date: Tue May 15 22:39:53 2018 +0200 copied langtons ant into own repo diff --git a/Main.py b/Main.py new file mode 100755 index 0000000..1b78f73 --- /dev/null +++ b/Main.py @@ -0,0 +1,557 @@ +#!/usr/bin/env python3 + +import pygame +from OpenGL.GL import * +from pygame.locals import * + +import argparse +import sys +import random +import time + +#TODO: remove global vars + +pygame.init() + +window_w = 1600 +window_h = 900 + +livingSpaceWidth = 64 +livingSpaceHeight = 36 + +creatureW = window_w/(livingSpaceWidth) +creatureH = window_h/(livingSpaceHeight) + +FPS = 30 + +NORTH = 0 +EAST = 1 +SOUTH = 2 +WEST = 3 + +livingSpace = [] +livingSpaceColor = [] + +update_queue = [] +old_update_queue = [] + +# doubled draw buffer because of display double buffering +draw_buffer = [] +draw_buffer_old = [] + +antPosition = (livingSpaceWidth // 2, livingSpaceHeight // 2) +antRotation = NORTH + +num_colors = 2 +color_list = [] +code = [False,True] + +# store number of active cells +num_active_cells = 0 + +# 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)) + + +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([]) + livingSpaceColor.append([]) + for y in range(livingSpaceHeight): + livingSpace[x].append(0) + rgba = [float(x)/float(livingSpaceWidth), float(livingSpaceWidth-x)/float(livingSpaceWidth), float(y)/float(livingSpaceHeight),0.15] + livingSpaceColor[x].append(rgba) + draw_buffer.append((x,y)) + +def isAlive(x,y): + return livingSpace[x][y] != 0 + +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: + r,g,b,a = livingSpaceColor[column][row] + + #glColor4f(255, 0, 0, 1.0) + glColor4f(a * r, a * g, a * b, 1.0) + x = column * creatureW + y = row * creatureH + + 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) + + # 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) + + 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) + + glEnd() + + draw_buffer_old = draw_buffer + draw_buffer = [] + + +def activate(i,j,key = 1): + global num_active_cells + if livingSpace[i][j] == 0: + num_active_cells += 1 + + 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 + update_queue.append((i,j)) + +def deactivate(i,j): + global num_active_cells + if livingSpace[i][j] != 0: + num_active_cells -= 1 + livingSpace[i][j] = 0 + # correct color: + livingSpaceColor[i][j] = [ + float(i) / float(livingSpaceWidth), + float(livingSpaceWidth - i) / float(livingSpaceWidth), + float(j) / float(livingSpaceHeight), + 0.6 + ] + + 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)) + 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 + update_queue.append((i,j)) + +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(): + global antPosition + global antRotation + # 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]) + + # turn + code_index = 1 if isAlive(antPosition[0], antPosition[1]) else 0 + + if code[code_index]: + # turn right + antRotation = (antRotation + 3) % 4 + else: + # turn left + antRotation = (antRotation + 1) % 4 + + else: + # 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) + + + # 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 + + move_ant(dx,dy) + + +def main(): + + global livingSpaceWidth + global livingSpaceHeight + global creatureW + global creatureH + global antPosition + global antRotation + global window_w + global window_h + global color_list + global num_colors + global code + + # parsing args: + parser = argparse.ArgumentParser(description="langton\'s ant simulation tool", formatter_class=argparse.RawTextHelpFormatter) + parser.add_argument('--steps', dest='steps', default = 20 , help='steps per second. Default = 20') + 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') + 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') + parser.add_argument('--configurator', dest='configurator', action='store_true', help='start in field edit mode') + 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\'') + 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') + + parser.set_defaults(fullscreen=False) + parser.set_defaults(configurator=False) + + 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 + settings["calc"] = 0 + 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"]) + + + + + args = parser.parse_args() + steps_per_sec = int(args.steps) + livingSpaceWidth = int(args.w) + livingSpaceHeight = int(args.h) + calc = int(args.calc) + filename = args.file + fileobj = None + if len(filename) > 0: + fileobj = open(filename, mode='w') + + video_flags = OPENGL | HWSURFACE | DOUBLEBUF + + # 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() + + + if args.fullscreen: + video_flags = OPENGL | HWSURFACE | DOUBLEBUF | FULLSCREEN + dinfo = pygame.display.Info() + window_w = dinfo.current_w + window_h = dinfo.current_h + else: + window_w = int(args.win_w) + window_h = int(args.win_h) + + + creatureW = window_w / (livingSpaceWidth) + creatureH = window_h / (livingSpaceHeight) + + antPosition = (livingSpaceWidth // 2, livingSpaceHeight // 2) + + + pygame.display.set_mode((window_w,window_h),video_flags) + + initLivingSpace() + + resize((window_w, window_h)) + init() + + clock = pygame.time.Clock() + + frames = 0 + counter = 0 + logic_frame_pause = FPS / steps_per_sec + configurator_mode = bool(args.configurator) + + field_draws = 0 + + # 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) + + if (calc > 0): + for i in range(calc): + update_ant() + + #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 + + update_field() + draw() + + 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 + + + pygame.display.flip() + + 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: + 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]) + 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) + elif event.key == K_RETURN: + configurator_mode = False + + else: + + if (calc == 0): + counter += 1 + + if logic_frame_pause >= 1: + if counter > logic_frame_pause: + update_ant() + if fileobj != None: + fileobj.write(str(num_active_cells) + '\n') + counter = 0 + else: + # multiple calculations per frame: + for i in range(int(1 / logic_frame_pause)): + update_ant() + if fileobj != None: + fileobj.write(str(num_active_cells) + '\n') + + # switch to configurator mode: + if event.type == KEYDOWN and event.key == K_RETURN: + configurator_mode = True + if fileobj != None: + fileobj.close() + + +if __name__ == '__main__': + main() diff --git a/README.md b/README.md new file mode 100644 index 0000000..9eba3ec --- /dev/null +++ b/README.md @@ -0,0 +1,104 @@ +# Langton's Ant Simulator + +## install dependencies: + +this python program needs pygame and PyOpenGL for the displaying stuff. + +this can be done via pip (on ubuntu-linux pip3 for using python 3): + +``` +pip install PyOpenGL PyOpenGL_accelerate pygame +``` + +## run the program: + +``` +usage: Main.py [-h] [--steps STEPS] [--w W] [--h H] [--calc CALC] + [--fullscreen] [--window_w WIN_W] [--window_h WIN_H] + [--configurator] [--code CODE] [--file FILE] + [--pattern PATTERN] + +langton's ant simulation tool + +optional arguments: + -h, --help show this help message and exit + --steps STEPS steps per second + --w W field width + --h H field height + --calc CALC calculate steps and only display result + --fullscreen + --window_w WIN_W window width + --window_h WIN_H window height + --configurator start in field edit mode + --code CODE binary code for the ant ('10' corresponds to the starndard ant behaviour). 1 means 'turn left', 0 means 'turn right' + --file FILE writing number of living cells per step in this file + --pattern PATTERN initial pattern for the field. Possible values: + * 0: all fields inactive + * 1: all fields active + * check: checkboard pattern + * horizontal: horizontal stripes + * vertical: vertical stripes + * random: random values +``` + +* if no arguments are given, the program will ask the user at the runtime for the requested arguments. + +## keys + +| key | function | +| ---------------------- | ---------------------------------------- | +| return | enter/leave configuration mode | +| arrow keys | move langton's ant (in configuration mode) | +| space | activate field or switch field's color (in configuration mode) | +| backspace | deactivate field (in configuration mode) | +| ctrl left / ctrl right | rotate ant (in configuration mode) | +| escape | exit program | + +## patterns: + +| pattern name | with standard ant | with multicolor ant | +| ------------ | -------------------- | -------------------- | +| 0 | ![](doc/p0a.png) | ![](doc/p0a.png) | +| 1 | ![](doc/p1a.png) | ![](doc/p1b.png) | +| check | ![](doc/pchecka.png) | ![](doc/pcheckb.png) | +| horizontal | ![](doc/pha.png) | ![](doc/phb.png) | +| vertical | ![](doc/pva.png) | ![](doc/pvb.png) | + + + +## examples + +* run standard langton's ant and write number of living cells per timestep to 'out.txt': + + ``` + ./Main.py --file out.txt + ``` + +* run standard langton's and with checkboard pattern: + + ``` + ./Main.py --pattern check + ``` + +* run on a fieldsize of 16x9 and start in configuration mode to adjust the ant's position and rotation: + + ``` + ./Main.py --w 16 --h 9 --configurator + ``` + +* run the multicolored ant 'RRLLLRLLLRRR' on a field with size 320x180 and perform 1000 steps per second: + + ``` + ./Main.py --w 320 --h 180 --code 110001000111 --steps 1000 + ``` + +* run the multicolored ant 'RRLLLRLLLRRR' on a field with size 320x180 and calculate the result after 10.000 steps + + ``` + ./Main.py --w 320 --h 180 --code 110001000111 --calc 100000 + ``` + + * result: + + ![](doc/example.png) + diff --git a/doc/example.png b/doc/example.png new file mode 100644 index 0000000..81cf679 Binary files /dev/null and b/doc/example.png differ diff --git a/doc/p0a.png b/doc/p0a.png new file mode 100644 index 0000000..13528c0 Binary files /dev/null and b/doc/p0a.png differ diff --git a/doc/p1a.png b/doc/p1a.png new file mode 100644 index 0000000..21cb35e Binary files /dev/null and b/doc/p1a.png differ diff --git a/doc/p1b.png b/doc/p1b.png new file mode 100644 index 0000000..6589ef0 Binary files /dev/null and b/doc/p1b.png differ diff --git a/doc/pchecka.png b/doc/pchecka.png new file mode 100644 index 0000000..ec9a679 Binary files /dev/null and b/doc/pchecka.png differ diff --git a/doc/pcheckb.png b/doc/pcheckb.png new file mode 100644 index 0000000..4d94f14 Binary files /dev/null and b/doc/pcheckb.png differ diff --git a/doc/pha.png b/doc/pha.png new file mode 100644 index 0000000..232df61 Binary files /dev/null and b/doc/pha.png differ diff --git a/doc/phb.png b/doc/phb.png new file mode 100644 index 0000000..d462713 Binary files /dev/null and b/doc/phb.png differ diff --git a/doc/pva.png b/doc/pva.png new file mode 100644 index 0000000..c9fe066 Binary files /dev/null and b/doc/pva.png differ diff --git a/doc/pvb.png b/doc/pvb.png new file mode 100644 index 0000000..4df5277 Binary files /dev/null and b/doc/pvb.png differ