I'd found myself getting back into programming, and had recalled a fascinating little 'game' many might know of. I'm a very inexperienced programmer, and I know this is sloppy, but I'd wanted to make something that had a sort of pseudo-cellular life kind of behavior.
There's no real pattern to their movement, but it does create a quite mesmerizing effect. All it does is generate a 'turtle' that wanders around turning tiles to the 'on' state until it dies after about 10 steps. If it encounters an 'on' tile, the turtle creates a new turtle in an adjacent space.
I'm well aware I have this really horribly optimized, but I'm hoping to expand upon it and probably just rewrite it more sanely. I used a little package called 'tdl' for ASCII graphics because I enjoy them and I really don't understand matplotlab animation at all or animation in general.
Controls:
- Press 2 or 3 (not numpad) to add a new turtle value at a random position
- Press Enter to increment one 'step'
- Press c to clear the board
- Press p to print the current population to the console
This is my bag of colors. It'll ask if you want to download upon opening the link.
This is the font used:
from random import randint #Woot! My turtles live life with no aim! Just livin on psudo-random whims XD
import numpy as np #You all know what this is, but I barely do
import tdl #A neat little package for handling ascii graphics. Just using it because it's very simple and fun.... and I don't really understand matplotlib animations....
import colors #Just a big bag of color values
SCREEN_WIDTH = 80
SCREEN_HEIGHT = 80
FONTSET = 'arial10x10.png'
LIMIT_FPS = 20
MAP_WIDTH = 100
MAP_HEIGHT = 80
BOARD = np.zeros( (MAP_WIDTH, MAP_HEIGHT) )
class TurtleBrain():
def __init__(self, pattern):
self.flag = True
self.pattern = pattern
def process(self):
"""Handles the 'steps' for each turtle value
I did it this way so that I could later decide to add new values with different movements and behaviors
or have several different types of life on the board at once, if I so choose to impliment them
"""
while self.pattern == 'original':
if self.flag:
self.move_twos()
self.flag = (not self.flag)
break
else:
self.move_threes()
self.flag = (not self.flag)
break
def move_twos(self):
"""
I'm sure there are so many better ways to do this:
Form lists of coordinates and data
Make a dictionary of coordinate keys with data values?
Sparse arrays?
"""
#Find every 2 turtle
for y in range(MAP_HEIGHT-1):
for x in range(MAP_WIDTH-1):
cell = BOARD[x, y]
if 2.0 <= cell <= 2.2:
failed = True
while True:
dx = randint(-1, 1)
dy = randint(-1, 1)
new_x = x + dx
new_y = y + dy
if dx == 0 and dy == 0:
continue
else:
failed = False
break
new_cell = BOARD[int(new_x), int(new_y)]
if new_cell == 1.0:
BOARD[int(new_x), int(new_y)] = BOARD[int(x), int(y)] + 1
BOARD[int(x), int(y)] = 0.0
self.make_child(new_x, new_y)
elif new_cell == 0.0:
BOARD[int(new_x), int(new_y)] = BOARD[int(x), int(y)] + 1
BOARD[int(x), int(y)] = 1.0
else:
BOARD[int(new_x), int(new_y)] += .01
def move_threes(self):
#find every 3 turtle
for y in range(MAP_HEIGHT-1):
for x in range(MAP_WIDTH-1):
cell = BOARD[x, y]
if 3.0 <= cell <= 3.2:
failed = True
while True:
dx = randint(-1, 1)
dy = randint(-1, 1)
new_x = x + dx
new_y = y + dy
if dx == 0 and dy == 0:
continue
else:
failed = False
break
new_cell = BOARD[int(new_x), int(new_y)]
if new_cell == 1.0:
BOARD[int(new_x), int(new_y)] = BOARD[int(x), int(y)] - 1
BOARD[int(x), int(y)] = 0.0
self.make_child(new_x, new_y)
elif new_cell == 0.0:
BOARD[int(new_x), int(new_y)] = BOARD[int(x), int(y)] - 1
BOARD[int(x), int(y)] = 1.0
else:
BOARD[int(new_x), int(new_y)] += .01
def move_fours(self):
"""Planned as a predator type to cull the 2 and 3 hoards mwuahaha"""
for x in BOARD:
for y in BOARD[x]:
cell = BOARD[x, y]
if 4.0 <= cell <= 4.2:
pass
def make_child(self, px, py):
failed = True
while True:
child_x = px + randint(-1, 1)
child_y = py + randint(-1, 1)
if BOARD[int(child_x), int(child_y)] == 0.0 or BOARD[int(child_x), int(child_y)] == 1.0:
failed = False
break
if not failed:
BOARD[int(child_x), int(child_y)] = 2.0
#print("I made a child at (%d, %d)" % (child_x, child_y))
class TimeStep():
def __init__(self, age_value):
self.age_value = age_value
def process(self):
for y in range(MAP_HEIGHT):
for x in range(MAP_WIDTH):
if 2.0 <= BOARD[x, y] <= 3.1:
#print("Test?")
BOARD[x, y] += self.age_value
#print(str(BOARD[x, y]) + " at (%d, %d)" % (x, y))
if 2.1 <= BOARD[x, y] <= 2.9 or 3.1 <= BOARD[x, y] <= 3.9:
BOARD[x, y] = 0.0
#print("Turtle at (%d, %d) died" % (x, y))
pass
class RenderProcessor():
def __init__(self, _con, _root):
super().__init__()
self._con = _con
self._root = _root
def process(self):
for y in range(MAP_HEIGHT):
for x in range(MAP_WIDTH):
if BOARD[x, y] == 0.0:
self._con.draw_char(x, y, '#', fg=colors.dark_grey, bg=colors.darker_grey)
#elif 2.0 <= BOARD[x, y] <= 2.99 or 3.0 <= BOARD[x, y] <= 3.99:
# self._con.draw_char(x, y, '@', fg=colors.dark_green, bg=colors.black)
elif 2.0 <= BOARD[x, y] <= 2.01 or 3.0 <= BOARD[x, y] <= 3.01:
self._con.draw_char(x, y, '0', fg=colors.dark_green, bg=colors.black)
elif 2.01 <= BOARD[x, y] <= 2.02 or 3.01 <= BOARD[x, y] <= 3.02:
self._con.draw_char(x, y, '1', fg=colors.dark_lime, bg=colors.black)
elif 2.02 <= BOARD[x, y] <= 2.03 or 3.02 <= BOARD[x, y] <= 3.03:
self._con.draw_char(x, y, '2', fg=colors.dark_red, bg=colors.black)
elif 2.03 <= BOARD[x, y] <= 2.04 or 3.03 <= BOARD[x, y] <= 3.04:
self._con.draw_char(x, y, '3', fg=colors.dark_blue, bg=colors.black)
elif 2.04 <= BOARD[x, y] <= 2.05 or 3.04 <= BOARD[x, y] <= 3.05:
self._con.draw_char(x, y, '4', fg=colors.dark_purple, bg=colors.black)
elif 2.05 <= BOARD[x, y] <= 2.06 or 3.05 <= BOARD[x, y] <= 3.06:
self._con.draw_char(x, y, '5', fg=colors.dark_violet, bg=colors.black)
elif 2.06 <= BOARD[x, y] <= 2.07 or 3.06 <= BOARD[x, y] <= 3.07:
self._con.draw_char(x, y, '6', fg=colors.dark_sky, bg=colors.black)
elif 2.07 <= BOARD[x, y] <= 2.08 or 3.07 <= BOARD[x, y] <= 3.08:
self._con.draw_char(x, y, '7', fg=colors.dark_cyan, bg=colors.black)
elif 2.08 <= BOARD[x, y] <= 2.09 or 3.08 <= BOARD[x, y] <= 3.09:
self._con.draw_char(x, y, '8', fg=colors.dark_cyan, bg=colors.black)
elif 2.09 <= BOARD[x, y] < 2.1 or 3.09 <= BOARD[x, y] <= 3.1:
self._con.draw_char(x, y, '9', fg=colors.darkest_pink, bg=colors.black)
else:
self._con.draw_char(x, y, '#', fg=colors.darker_green, bg=colors.dark_green)
self._root.blit(self._con, 0, 0, MAP_WIDTH, MAP_HEIGHT, 0, 0)
tdl.flush()
##################################################################
tdl.set_font(FONTSET, greyscale=True, altLayout=True)
root = tdl.init(SCREEN_WIDTH, SCREEN_HEIGHT, title="TURTLES",
fullscreen=False)
tdl.setFPS(LIMIT_FPS)
con = tdl.Console(MAP_HEIGHT, MAP_WIDTH)
##################################################################
def count_turts():
total = 0
for y in range(MAP_HEIGHT - 1):
for x in range(MAP_WIDTH - 1):
cell = BOARD[x, y]
if 2.0 <= cell <= 2.12 or 3.0 <= cell <= 3.12:
total += 1
print("There are %d total turtles" % (total))
BOARD[50, 50] = 2.0
turtlebrain = TurtleBrain(pattern='original')
timeprocessor = TimeStep(.01)
renderprocessor = RenderProcessor(con, root)
con.clear()
def handle_keys():
user_input = tdl.event.key_wait()
if user_input.key == 'ESCAPE':
return 'exit'
if user_input.key == 'ENTER':
return 'go'
if user_input.char == '2':
return user_input.char
if user_input.char == '3':
return user_input.char
if user_input.char == 'c':
return 'clear'
if user_input.char == 'p':
return user_input.char
if user_input.key == 'F11':
return user_input.key
renderprocessor.process()
def add_turtle(turt):
BOARD[randint(0, MAP_WIDTH - 1), randint(0, MAP_HEIGHT - 1)] = 2.0
while not tdl.event.is_window_closed():
player_input = handle_keys()
if player_input == 'go':
turtlebrain.process()
timeprocessor.process()
renderprocessor.process()
elif player_input == 'exit':
break
elif player_input == 'F11':
tdl.set_fullscreen(not tdl.get_fullscreen())
pass
elif player_input == '2':
add_turtle(2)
elif player_input == '3':
add_turtle(3)
elif player_input == 'p':
count_turts()
elif player_input == 'clear':
BOARD = np.zeros( (MAP_WIDTH, MAP_HEIGHT) )
renderprocessor.process()
1 Answer 1
Here are few recommendations, in no particular order.
PEP8
You should consider formatting your code in accordance with pep8. This is important when sharing code, as the consistent style makes it much easier for other programmers to read your code.
There are various tools available to assist in making the code pep8 compliant. I use the PyCharm IDE which will show pep8 violations right in the editor.
Pylint
pylint can be used to help find code errors and non-best practice constructs. Here again PyCharm (and other IDEs) can run these tools on your code and show the results directly in the editor.
Try to simplify similar paths
In this code:
while self.pattern == 'original':
if self.flag:
self.move_twos()
self.flag = (not self.flag)
break
else:
self.move_threes()
self.flag = (not self.flag)
break
Both if and else have a break, therefore the loop is unneeded. So it can be done more simply as:
if self.flag:
self.move_twos()
else:
self.move_threes()
self.flag = not self.flag
In addition, this loop:
failed = True
while True:
dx = randint(-1, 1)
dy = randint(-1, 1)
new_x = x + dx
new_y = y + dy
if dx == 0 and dy == 0:
continue
else:
failed = False
break
can be simplified as:
while True:
new_x = x + randint(-1, 1)
new_y = y + randint(-1, 1)
if x != new_x or y != newy:
break
Calculate relationships, and use dict lookups:
Just about anytime you see a stacked if, in which the conditions and actions are basically the same, you should look to use a dict lookup instead.
So something this:
....
elif 2.0 <= BOARD[x, y] <= 2.01 or 3.0 <= BOARD[x, y] <= 3.01:
self._con.draw_char(x, y, '0', fg=colors.dark_green, bg=colors.black)
elif 2.01 <= BOARD[x, y] <= 2.02 or 3.01 <= BOARD[x, y] <= 3.02:
self._con.draw_char(x, y, '1', fg=colors.dark_lime, bg=colors.black)
elif 2.02 <= BOARD[x, y] <= 2.03 or 3.02 <= BOARD[x, y] <= 3.03:
self._con.draw_char(x, y, '2', fg=colors.dark_red, bg=colors.black)
elif 2.03 <= BOARD[x, y] <= 2.04 or 3.03 <= BOARD[x, y] <= 3.04:
....
can likely be changed to something like (untested):
actions = {
0: dict(char='0', fg=colors.dark_green, bg=colors.black),
1: dict(char='1', fg=colors.dark_lime, bg=colors.black),
2: dict(char='2', fg=colors.dark_red, bg=colors.black),
....
}
lookup = int((BOARD[x, y] - int(BOARD[x, y]) * 100)
self._con.draw_char(x, y, **actions[lookup])
This calculates a a lookup index into the actions
dict and the gets the values for draw_char
from there. The resulting construct will be much easier to read and modify.
-
\$\begingroup\$ Thank you! I knew there was room to simplify the loops and the if statements but the solutions went right over my head. Much appreciated. I'm pretty sure my nested
for
loops over a big board isn't the best way to do this, perhaps I can find a cleaner algorithm for the entire thing. \$\endgroup\$Xulder– Xulder2018年06月11日 05:54:33 +00:00Commented Jun 11, 2018 at 5:54
Explore related questions
See similar questions with these tags.