diff --git a/cellular_automaton/README.md b/cellular_automaton/README.md new file mode 100644 index 0000000..29faec4 --- /dev/null +++ b/cellular_automaton/README.md @@ -0,0 +1,39 @@ +# cellular automaton + +for an ascii output run: + +``` +usage: cellular.py [-h] [--w W] [--code CODE] [--random] [--r R] + +one dimensional cellular automaton for k=2 + +optional arguments: + -h, --help show this help message and exit + --w W field width + --code CODE code for the automaton + --random + --r R radius. can be 1 or 2 +``` + +for opengl output: + +``` +usage: glCellular.py [-h] [--steps STEPS] [--w W] [--h H] [--fullscreen] + [--window_w WIN_W] [--window_h WIN_H] [--code CODE] + [--random] [--r R] + +one dimensional cellular automaton for k=2 with opengl output + +optional arguments: + -h, --help show this help message and exit + --steps STEPS steps per second + --w W field width + --h H field height + --fullscreen + --window_w WIN_W window width + --window_h WIN_H window height + --code CODE code for the automaton + --random + --r R radius. can be 1 or 2 +``` + diff --git a/cellular_automaton/cellular.py b/cellular_automaton/cellular.py new file mode 100755 index 0000000..4cfd084 --- /dev/null +++ b/cellular_automaton/cellular.py @@ -0,0 +1,117 @@ +#!/usr/bin/env python3 +import numpy as np +import argparse + +# cellular automaton functions: +def generatestart(startcondition,border,size): + """ + :param startcondition: r for random numers zero or one + :param border: the size of the border one or two + :param size: size of the whole array + :return: the initialized array to work with + """ + + #all cells + cells = np.zeros((size),int) + #cells without borders + writableCells = cells[border:size-border] + + #startcondition for seed + if(startcondition == "s"): + cells[size // 2] = 1 + #condition for setting random values + else: + for i in range(writableCells.shape[0]): + writableCells[i]=np.round(np.random.rand()) + #print(np.shape(writableCells)) + return cells + +def code2FunctionTable(code, r): + result = {} + + c = code + + if r == 1: + e = 8 + for i in range(2): + for j in range(2): + for k in range(2): + if 2**e > c: + result[(i,j,k)] = 0 + else: + result[(i,j,k)] = 1 + c -= 2 ** e + e -= 1 + elif r == 2: + e = 32 + for i in range(2): + for j in range(2): + for k in range(2): + for l in range(2): + for m in range(2): + if 2 ** e > c: + result[(i, j, k, l, m)] = 0 + else: + result[(i, j, k, l, m)] = 1 + c -= 2 ** e + e -= 1 + return result + +def getNextCellValue(functionTable, row, pos, r): + if r == 1: + i = row[pos - 1] + j = row[pos] + k = row[pos + 1] + return functionTable[(i,j,k)] + elif r == 2: + i = row[pos - 2] + j = row[pos - 1] + k = row[pos] + l = row[pos + 1] + m = row[pos + 2] + return functionTable[(i,j,k,l,m)] + return None + +def calculateNextStep(functionTable, size, row, r): + result = np.zeros((size),int) + for i in range(r, size - r): + result[i] = getNextCellValue(functionTable, row, i, r) + return result + +if __name__ == "__main__": + # parsing args: + parser = argparse.ArgumentParser(description="one dimensional cellular automaton for k=2") + #parser.add_argument('--steps', dest='steps', default=20, help='steps per second') + parser.add_argument('--w', dest='w', default=84, help='field width') + #parser.add_argument('--h', dest='h', default=livingSpaceHeight, help='field height') + #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('--code', dest='code', default='17', help='code for the automaton') + parser.add_argument('--random', dest='random', action='store_true') + parser.add_argument('--r', dest='r', default=1, help='radius. can be 1 or 2') + parser.set_defaults(random=False) + + args = parser.parse_args() + + r = int(args.r) + c = int(args.code) + size = int(args.w) + startcondition = "" + + if args.random: + startcondition = "r" + else: + startcondition = "s" + + functionTable = code2FunctionTable(c, r) + print(functionTable) + cells = generatestart(startcondition, r, size) + print(cells) + + for i in range(20): + cells = calculateNextStep(functionTable,size,cells, r) + print(cells) + + + diff --git a/cellular_automaton/glCellular.py b/cellular_automaton/glCellular.py new file mode 100755 index 0000000..15f5af6 --- /dev/null +++ b/cellular_automaton/glCellular.py @@ -0,0 +1,337 @@ +#!/usr/bin/env python3 +import pygame +from OpenGL.GL import * +from pygame.locals import * +from cellular import * + +import argparse + +#TODO: remove global vars + +# NOTE: modified python script from a multi color langton's ant implementation, so +# maybe some variable names are a little bit confusing... + +pygame.init() + +window_w = 1600 +window_h = 900 + +livingSpaceWidth = 84 +livingSpaceHeight = 47 + +creatureW = window_w/(livingSpaceWidth) +creatureH = window_h/(livingSpaceHeight) + +FPS = 30 + +livingSpace = [] +livingSpaceColor = [] + +update_queue = [] +old_update_queue = [] + +# doubled draw buffer because of display double buffering +draw_buffer = [] +draw_buffer_old = [] + +current_iteration = 0 + +num_colors = 2 +color_list = [] +code = 0 +r = 1 + +# 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(): + """ + generate colors linear over HSV Space (one color for every possible state, not yet supported, but maybe in the future) + :return: + """ + 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): + """ + Init opengl viewport + :param shape: + :return: + """ + 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_buffer_old = draw_buffer + draw_buffer = [] + + +def activate(i,j,key = 1): + """ + activate field (i,j) + :param i: + :param j: + :param key: + :return: + """ + 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): + """ + deactivate field (i,j) + :param i: + :param j: + :return: + """ + 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 updateAutomaton(cells): + global livingSpaceWidth + global current_iteration + + for i in range(livingSpaceWidth): + if cells[i] != 0: + activate(i, current_iteration) + +def main(): + + global livingSpaceWidth + global livingSpaceHeight + global creatureW + global creatureH + global window_w + global window_h + global color_list + global num_colors + global code + global current_iteration + global r + + # parsing args: + parser = argparse.ArgumentParser(description="one dimensional cellular automaton for k=2 with opengl output") + parser.add_argument('--steps', dest='steps', default = 60 , help='steps per second') + 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('--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('--code', dest='code', default='150', help='code for the automaton') + parser.add_argument('--random', dest='random', action='store_true') + parser.add_argument('--r', dest='r', default=r, help='radius. can be 1 or 2') + + parser.set_defaults(fullscreen=False) + parser.set_defaults(random=False) + #parser.set_defaults(configurator=False) + + args = parser.parse_args() + steps_per_sec = int(args.steps) + livingSpaceWidth = int(args.w) + livingSpaceHeight = int(args.h) + + video_flags = OPENGL | HWSURFACE | DOUBLEBUF + + r = int(args.r) + c = int(args.code) + startcondition = "" + + if args.random: + startcondition = "r" + else: + startcondition = "s" + + functionTable = code2FunctionTable(c, r) + cells = generatestart(startcondition, r, livingSpaceWidth) + + # generate colors: + num_colors = 2 # k is fix for now + 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) + + pygame.display.set_mode((window_w,window_h),video_flags) + + initLivingSpace() + + updateAutomaton(cells) + + resize((window_w, window_h)) + init() + + clock = pygame.time.Clock() + + frames = 0 + counter = 0 + logic_frame_pause = FPS / float(steps_per_sec) + + field_draws = 0 + + #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() + + counter += 1 + + if current_iteration < livingSpaceHeight-1: + if counter > logic_frame_pause: + cells = calculateNextStep(functionTable, livingSpaceWidth, cells, r) + current_iteration += 1 + updateAutomaton(cells) + print(current_iteration) + counter = 0 + + + + +if __name__ == '__main__': + main()