Repository: MichaelJWelsh/bot-evolution
Branch: master
Commit: 6d8e3449fc53
Files: 7
Total size: 24.2 KB
Directory structure:
gitextract_rb342yw4/
├── LICENSE
├── README.md
└── src/
├── main.py
├── neural_network.py
├── population.py
├── settings.py
└── utility.py
================================================
FILE CONTENTS
================================================
================================================
FILE: LICENSE
================================================
MIT License
Copyright (c) 2017 Michael J Welsh
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
================================================
FILE: README.md
================================================
# Bot Evolution
Bot evolution is an interesting display of evolution through neural networks and a genetic algorithm. Bot's have a field of vision represented by their antenna's. They are told if they "see" food in their field of vision, and are then asked to either move forward, turn counterclockwise, turn clockwise, or do nothing. If a bot does not recieve food after a certain period of time, it will die off. When a bot gets food, it reproduces asexually with a chance of mutation. If a bot goes too far out of the map, it dies and a completely random bot is spawned in the middle of the map. Each bot has its own neural network. You can see species emerge based on their colors. A random group of bots is spawned in the middle of the map upon startup.

## Usage
Simply go into the source folder and type this in terminal:
```python
python3.5 main.py
```
## Dependencies
- numpy
- pygame
================================================
FILE: src/main.py
================================================
"""
Bot Evolution v1.0.0
"""
import os
import sys
import pickle
import pygame as pg
from pygame.locals import *
import numpy as np
import datetime
import settings
import population
def main():
np.random.seed()
pg.init()
# Initialize runtime variables.
periodically_save = False
pop = None
if os.path.isfile("save.txt") and input("Save file detected! Use it? (y/n): ").lower() == 'y':
settings.FPS, settings.WINDOW_WIDTH, settings.WINDOW_HEIGHT, settings.TIME_MULTIPLIER, pop = pickle.load(open("save.txt", "rb"))
else:
pop_size = 0
mutation_rate = 0
while True:
pop_size = int(input("Population size: "))
if pop_size < 5:
print("Population size must be at least 5!")
else:
break
while True:
mutation_rate = float(input("Mutation rate: "))
if mutation_rate <= 0 or mutation_rate >= 1:
print("Mutation rate must be in the range (0, 1)!")
else:
break
while True:
settings.TIME_MULTIPLIER = float(input("Time multiplier: "))
if settings.TIME_MULTIPLIER < 1:
print("Time multiplier must be at least 1!")
else:
break
if input("Advance options? (y/n): ").lower() == 'y':
while True:
settings.FPS = int(input("Frames per second: "))
if settings.FPS < 1:
print("FPS must be at least 1!")
else:
break
while True:
settings.WINDOW_WIDTH = int(input("Window width: "))
if settings.WINDOW_WIDTH < 50:
print("Window width must be at least 50!")
else:
break
while True:
settings.WINDOW_HEIGHT = int(input("Window height: "))
if settings.WINDOW_HEIGHT < 50:
print("Window height must be at least 50!")
else:
break
pop = population.Population(pop_size, mutation_rate)
if input("Periodically save every half hour? (y/n): ").lower() == 'y':
periodically_save = True
print("\nNote: ")
print("\tPress 'r' to reset the population.")
print("\tPress 'p' to pause / unpause.")
print("\tPress 's' to save population's data (for use next time).")
print("\tPress 'up' / 'down' to change the populations mutation rate.")
print("\tPress 'left' / 'right' to change the time multiplier.")
print("\tClick on the screen to lay down food.")
# Core variables.
FONT_SIZE = 30
FONT = pg.font.SysFont("Arial", FONT_SIZE)
fps_clock = pg.time.Clock()
window = pg.display.set_mode((settings.WINDOW_WIDTH, settings.WINDOW_HEIGHT))
pg.display.set_caption("Bot Evolution")
# Main loop.
dt = 0.0
fps_clock.tick(int(settings.FPS / (settings.TIME_MULTIPLIER / 5.0 + 1)))
paused = False
while True:
key_pressed = {"up": False, "down": False, "left": False, "right": False}
for event in pg.event.get():
if event.type == QUIT:
pg.quit()
sys.exit()
elif event.type == pg.KEYDOWN:
if event.key == pg.K_r:
pop = population.Population(pop.SIZE, pop.mutation_rate)
if event.key == pg.K_s:
pickle.dump([settings.FPS, settings.WINDOW_WIDTH, settings.WINDOW_HEIGHT, settings.TIME_MULTIPLIER, pop], open("save.txt", "wb"))
if event.key == pg.K_p:
paused = not paused
elif event.type == pg.MOUSEBUTTONUP:
pos = pg.mouse.get_pos()
pop.food.pop()
food = population.Food(pop)
food.x = pos[0]
food.y = pos[1]
pop.food.append(food)
if paused:
dt = fps_clock.tick(int(settings.FPS / (settings.TIME_MULTIPLIER / 5.0 + 1))) / 1000.0 * int(settings.FPS / (settings.TIME_MULTIPLIER / 5.0 + 1))
continue
if periodically_save and datetime.datetime.now().minute % 30 == 0:
pickle.dump([settings.FPS, settings.WINDOW_WIDTH, settings.WINDOW_HEIGHT, settings.TIME_MULTIPLIER, pop], open("save.txt", "wb"))
keys = pg.key.get_pressed()
if keys[pg.K_UP]:
key_pressed["up"] = True
if keys[pg.K_DOWN]:
key_pressed["down"] = True
if key_pressed["up"] and key_pressed["down"]:
key_pressed["up"] = False
key_pressed["down"] = False
if keys[pg.K_LEFT]:
key_pressed["left"] = True
if keys[pg.K_RIGHT]:
key_pressed["right"] = True
if key_pressed["left"] and key_pressed["right"]:
key_pressed["left"] = False
key_pressed["right"] = False
update(dt, pop, key_pressed)
window.fill((0, 0, 0))
render(window, FONT, pop)
pg.display.update()
dt = fps_clock.tick(int(settings.FPS / (settings.TIME_MULTIPLIER / 5.0 + 1))) / 1000.0 * int(settings.FPS / (settings.TIME_MULTIPLIER / 5.0 + 1))
display_time_remaining = 0.0
def update(dt, pop, key_pressed):
global display_time_remaining
if key_pressed["up"] or key_pressed["down"] or key_pressed["left"] or key_pressed["right"]:
display_time_remaining = 3.0
if key_pressed["up"]:
pop.mutation_rate += 0.001
elif key_pressed["down"]:
pop.mutation_rate -= 0.001
if pop.mutation_rate <= 0:
pop.mutation_rate = 0.001
elif pop.mutation_rate >= 1:
pop.mutation_rate = 0.999
if key_pressed["left"]:
settings.TIME_MULTIPLIER -= 0.1
elif key_pressed["right"]:
settings.TIME_MULTIPLIER += 0.1
if settings.TIME_MULTIPLIER < 1:
settings.TIME_MULTIPLIER = 1.0
else:
display_time_remaining -= 1.0 / settings.FPS * dt
if display_time_remaining < 0:
display_time_remaining = 0.0
pop.update(dt)
def render(window, FONT, pop):
for food in pop.food:
pg.draw.circle(window, food.RGB, (int(food.x), int(food.y)), food.HITBOX_RADIUS)
for bot in pop.bots:
# Draw body.
pg.draw.circle(window, bot.RGB, (int(bot.x), int(bot.y)), bot.HITBOX_RADIUS)
# Draw field-of-vision lines.
LINE_THICKNESS = 1
PROTRUSION = int(bot.HITBOX_RADIUS * 1.5)
to_x = int(bot.x + (bot.HITBOX_RADIUS + PROTRUSION) * np.cos(bot.theta - bot.FIELD_OF_VISION_THETA / 2))
to_y = int(bot.y - (bot.HITBOX_RADIUS + PROTRUSION) * np.sin(bot.theta - bot.FIELD_OF_VISION_THETA / 2))
pg.draw.line(window, bot.RGB, (bot.x, bot.y), (to_x, to_y), LINE_THICKNESS)
to_x = int(bot.x + (bot.HITBOX_RADIUS + PROTRUSION) * np.cos(bot.theta + bot.FIELD_OF_VISION_THETA / 2))
to_y = int(bot.y - (bot.HITBOX_RADIUS + PROTRUSION) * np.sin(bot.theta + bot.FIELD_OF_VISION_THETA / 2))
pg.draw.line(window, bot.RGB, (bot.x, bot.y), (to_x, to_y), LINE_THICKNESS)
if display_time_remaining > 0:
resultSurf = FONT.render("Mutation Rate: %.3f Speed: %.1fx" % (pop.mutation_rate, settings.TIME_MULTIPLIER), True, (255, 255, 255))
resultRect = resultSurf.get_rect()
resultRect.topleft = (25, 25)
window.blit(resultSurf, resultRect)
if __name__ == "__main__":
main()
================================================
FILE: src/neural_network.py
================================================
"""
This module implements the neural network class and all components necessary to
modularize the build/use process of the neural network.
"""
import numpy as np
import pickle
class NNetwork:
"""
The representation of a feed forward neural network with a bias in every
layer (excluding output layer obviously).
"""
def __init__(self, layer_sizes, activation_funcs, bias_neuron = False):
"""
Creates a 'NNetwork'. 'layer_sizes' provides information about the
number of neurons in each layer, as well as the total number of layers
in the neural network. 'activation_funcs' provides information about the
activation functions to use on each respective hidden layers and output
layer. This means that the length of 'activation_funcs' is always one
less than the length of 'layer_sizes'.
"""
assert(len(layer_sizes) >= 2)
assert(len(layer_sizes) - 1 == len(activation_funcs))
assert(min(layer_sizes) >= 1)
self.layers = []
self.connections = []
# Initialize layers.
for i in range(len(layer_sizes)):
# Input layer.
if i == 0:
self.layers.append(Layer(layer_sizes[i], None, bias_neuron))
# Hidden layer.
elif i < len(layer_sizes) - 1:
self.layers.append(Layer(layer_sizes[i], activation_funcs[i - 1], bias_neuron))
# Output layer.
else:
self.layers.append(Layer(layer_sizes[i], activation_funcs[i - 1]))
# Initialize connections.
num_connections = len(layer_sizes) - 1
for i in range(num_connections):
self.connections.append(Connection(self.layers[i], self.layers[i + 1]))
def feed_forward(self, data, one_hot_encoding = True):
"""
Feeds given data through neural network and stores output in output
layer's data field. Output can optionally be one-hot encoded.
"""
if self.layers[0].HAS_BIAS_NEURON:
assert(len(data) == self.layers[0].SIZE - 1)
self.layers[0].data = data
self.layers[0].data.append(1)
else:
assert(len(data) == self.layers[0].SIZE)
self.layers[0].data = data
for i in range(len(self.connections)):
self.connections[i].TO.data = np.dot(self.layers[i].data, self.connections[i].weights)
self.connections[i].TO.activate()
if one_hot_encoding:
this_data = self.layers[len(self.layers) - 1].data
MAX = max(this_data)
for i in range(len(this_data)):
if this_data[i] == MAX:
this_data[i] = 1
else:
this_data[i] = 0
def output(self):
"""
Retrieves data in output layer.
"""
return self.layers[len(self.layers) - 1].data
class Layer:
"""
The representation of a layer in a neural network. Used as a medium for
passing data through the network in an efficent manner.
"""
def __init__(self, num_neurons, activation_func, bias_neuron = False):
"""
Creates a 'Layer' with 'num_neurons' and an additional (optional) bias
neuron (which always has a value of '1'). The layer will utilize the
'activation_func' during activation.
"""
assert(num_neurons > 0)
self.ACTIVATION_FUNC = activation_func
self.HAS_BIAS_NEURON = bias_neuron
if bias_neuron:
self.SIZE = num_neurons + 1
self.data = np.array([0] * num_neurons + [1])
else:
self.SIZE = num_neurons
self.data = np.array([0] * num_neurons)
def activate(self):
"""
Calls activation function on layer's data.
"""
if self.ACTIVATION_FUNC != None:
self.ACTIVATION_FUNC(self.data)
class Connection:
"""
The representation of a connection between layers in a neural network.
"""
def __init__(self, layer_from, layer_to):
""""
Creates a 'Connection' between 'layer_from' and 'layer_to' that contains
all required weights, which are randomly initialized with random numbers
in a guassian distribution of mean '0' and standard deviation '1'.
"""
self.FROM = layer_from
self.TO = layer_to
self.weights = np.zeros((layer_from.SIZE, layer_to.SIZE))
for i in range(layer_from.SIZE):
for j in range(layer_to.SIZE):
self.weights[i][j] = np.random.standard_normal()
def sigmoid(data):
"""
Uses sigmoid transformation on given data. This is an activation function.
"""
for i in range(len(data)):
data[i] = 1 / (1 + np.exp(-data[i]))
def softmax(data):
"""
Uses softmax transformation on given data. This is an activation function.
"""
sum = 0.0
for i in range(len(data)):
sum += np.exp(data[i])
for i in range(len(data)):
data[i] = np.exp(data[i]) / sum
================================================
FILE: src/population.py
================================================
"""
This modules implements the bulk of Bot Evolution.
"""
import numpy as np
import copy
import settings
from utility import seq_is_equal, distance_between, angle_is_between, find_angle
from neural_network import NNetwork, sigmoid, softmax
class Population:
"""
The environment of bots and food.
"""
def __init__(self, size, mutation_rate):
assert(size >= 5)
assert(0 < mutation_rate < 1)
self.SIZE = size
self.mutation_rate = mutation_rate
self.bots = []
self.food = []
self.time_since_last_death = 0.0
# The neural network will have 1 neuron in the input layer, 1 hidden
# layer with 2 neurons, and 4 neurons in the output layer. The sigmoid
# activation function will be used on the hidden layer, and a softmax
# activation function will be used on the output layer. Input consists
# of the bot's direction and if there is or isn't food in the bots field
# of vision. Output consists of whether or not to move foward, turn
# left, turn right, or do nothing.
for i in range(size):
random_rgb = (np.random.randint(30, 256), np.random.randint(30, 256), np.random.randint(30, 256))
self.bots.append(Bot(NNetwork((1, 2, 4), (sigmoid, softmax)), random_rgb, self))
self.food.append(Food(self))
def eliminate(self, bot, replace = False):
self.time_since_last_death = 0.0
self.bots.remove(bot)
if replace:
random_rgb = (np.random.randint(30, 256), np.random.randint(30, 256), np.random.randint(30, 256))
self.bots.append(Bot(NNetwork((1, 2, 4), (sigmoid, softmax)), random_rgb, self))
def feed(self, bot, food):
bot.score = 1.0
self.food.remove(food)
self.food.append(Food(self))
num_to_replace = int(self.SIZE / 7 - 1)
if num_to_replace < 2:
num_to_replace = 2
for i in range(num_to_replace):
weakest = self.bots[0]
for other in self.bots:
if other.score < weakest.score:
weakest = other
self.eliminate(weakest)
for i in range(num_to_replace):
if np.random.uniform(0, 1) <= self.mutation_rate:
new_rgb = [bot.RGB[0], bot.RGB[1], bot.RGB[2]]
new_rgb[np.random.choice((0, 1, 2))] = np.random.uniform(30, 256)
new_bot = Bot(bot.nnet, new_rgb, self)
new_bot.x = bot.x + Bot.HITBOX_RADIUS * 4 * np.random.uniform(0, 1) * np.random.choice((-1, 1))
new_bot.y = bot.y + Bot.HITBOX_RADIUS * 4 * np.random.uniform(0, 1) * np.random.choice((-1, 1))
nb_c = new_bot.nnet.connections
mutated = False
while not mutated:
for k in range(len(nb_c)):
for i in range(nb_c[k].FROM.SIZE):
for j in range(nb_c[k].TO.SIZE):
if np.random.uniform(0, 1) <= self.mutation_rate:
nb_c[k].weights[i][j] = nb_c[k].weights[i][j] * np.random.normal(1, 0.5) + np.random.standard_normal()
mutated = True
self.bots.append(new_bot)
else:
new_bot = Bot(bot.nnet, bot.RGB, self)
new_bot.x = bot.x + Bot.HITBOX_RADIUS * 4 * np.random.uniform(0, 1) * np.random.choice((-1, 1))
new_bot.y = bot.y + Bot.HITBOX_RADIUS * 4 * np.random.uniform(0, 1) * np.random.choice((-1, 1))
self.bots.append(new_bot)
def update(self, dt):
"""
Updates the population's internals. The bulk of event handling for all
bots and food starts here.
"""
self.time_since_last_death += 1.0 / settings.FPS * dt * settings.TIME_MULTIPLIER
for food in self.food[:]:
if food not in self.food:
continue
food.update(dt)
for bot in self.bots[:]:
if bot not in self.bots:
continue
sensory_input = []
# This is where the bot's field of vision is put into action.
min_theta = bot.theta - Bot.FIELD_OF_VISION_THETA / 2
max_theta = bot.theta + Bot.FIELD_OF_VISION_THETA / 2
food_in_sight = False
for food in self.food:
if angle_is_between(find_angle(bot.x, bot.y, food.x, food.y), min_theta, max_theta):
food_in_sight = True
break
if food_in_sight:
sensory_input.append(1.0)
else:
sensory_input.append(0.0)
# Useful debugging outputs.
#print(bot.RGB)
#print(sensory_input)
bot.update(dt, sensory_input)
if self.time_since_last_death >= 5:
weakest = self.bots[0]
for bot in self.bots:
if bot.score < weakest.score:
weakest = bot
self.eliminate(weakest, replace = True)
class Bot:
"""
The representation of the circle thing with probes.
"""
# In pixels/pixels per second/revolutions per second/radians.
SPAWN_RADIUS = int(settings.WINDOW_WIDTH / 20) if settings.WINDOW_WIDTH <= settings.WINDOW_HEIGHT else int(settings.WINDOW_HEIGHT / 20)
HITBOX_RADIUS = 6
SPEED = 350.0
TURN_RATE = 2 * np.pi
FIELD_OF_VISION_THETA = 45 * np.pi / 180
# These lists represent the output from the neural network. Note that the
# output '[0, 0, 0, 1]' means "do nothing".
MOVE_FORWARD = [1, 0, 0, 0]
TURN_LEFT = [0, 1, 0, 0]
TURN_RIGHT = [0, 0, 1, 0]
def __init__(self, nnet, rgb, population):
self.nnet = copy.deepcopy(nnet)
self.RGB = rgb
self.pop = population
self.theta = np.random.uniform(0, 1) * 2 * np.pi
self.x = settings.WINDOW_WIDTH / 2.0 + Bot.SPAWN_RADIUS * np.random.uniform(0, 1) * np.cos(self.theta)
self.y = settings.WINDOW_HEIGHT / 2.0 + Bot.SPAWN_RADIUS * np.random.uniform(0, 1) * np.sin(self.theta)
self.score = 0.0
def _move_forward(self, dt):
self.x += Bot.SPEED / settings.FPS * dt * np.cos(self.theta) * settings.TIME_MULTIPLIER
self.y -= Bot.SPEED / settings.FPS * dt * np.sin(self.theta) * settings.TIME_MULTIPLIER
if self.x < -Bot.HITBOX_RADIUS * 6 or self.x > settings.WINDOW_WIDTH + Bot.HITBOX_RADIUS * 6 \
or self.y < -Bot.HITBOX_RADIUS * 6 or self.y > settings.WINDOW_HEIGHT + Bot.HITBOX_RADIUS * 6:
self.pop.eliminate(self, replace = True)
def _turn_left(self, dt):
self.theta += Bot.TURN_RATE / settings.FPS * dt * settings.TIME_MULTIPLIER
while self.theta >= 2 * np.pi:
self.theta -= 2 * np.pi
def _turn_right(self, dt):
self.theta -= Bot.TURN_RATE / settings.FPS * dt * settings.TIME_MULTIPLIER
while self.theta < 0:
self.theta += 2 * np.pi
def update(self, dt, sensory_input):
"""
Updates the bot's internals. "Hunger" can be thought of as a score
between '-1' and '1' where a greater value means less hungry.
"""
self.score -= 1.0 / settings.FPS / 10.0 * dt * settings.TIME_MULTIPLIER
if self.score < -1:
self.score = -1.0
self.nnet.feed_forward(sensory_input)
output = self.nnet.output()
if seq_is_equal(output, Bot.MOVE_FORWARD):
self._move_forward(dt)
elif seq_is_equal(output, Bot.TURN_LEFT):
self._turn_left(dt)
elif seq_is_equal(output, Bot.TURN_RIGHT):
self._turn_right(dt)
class Food:
"""
The representation of the red circles.
"""
# In pixels.
HITBOX_RADIUS = 5
RGB = (255, 0, 0)
def __init__(self, population):
mid_x = int(settings.WINDOW_WIDTH / 2)
mid_y = int(settings.WINDOW_HEIGHT / 2)
max_left_x = mid_x - (Bot.SPAWN_RADIUS + Bot.HITBOX_RADIUS + 5)
min_right_x = mid_x + (Bot.SPAWN_RADIUS + Bot.HITBOX_RADIUS + 5)
max_top_y = mid_y - (Bot.SPAWN_RADIUS + Bot.HITBOX_RADIUS + 5)
min_bottom_y = mid_y + (Bot.SPAWN_RADIUS + Bot.HITBOX_RADIUS + 5)
self.x = np.random.choice((np.random.uniform(0, max_left_x), np.random.uniform(min_right_x, settings.WINDOW_WIDTH)))
self.y = np.random.choice((np.random.uniform(0, max_top_y), np.random.uniform(min_bottom_y, settings.WINDOW_HEIGHT)))
self.pop = population
def update(self, dt):
"""
Updates the food's internals and handles bot<->food collision.
"""
for bot in self.pop.bots:
if distance_between(self.x, self.y, bot.x, bot.y) <= Bot.HITBOX_RADIUS + Food.HITBOX_RADIUS:
self.pop.feed(bot, self)
break
================================================
FILE: src/settings.py
================================================
"""
This module contains the general settings used across modules.
"""
FPS = 60
WINDOW_WIDTH = 1100
WINDOW_HEIGHT = 600
TIME_MULTIPLIER = 1.0
================================================
FILE: src/utility.py
================================================
"""
This module contains general-use functions.
"""
import numpy as np
def seq_is_equal(a, b):
"""
Helper function that checks if two sequences are equal (assuming they have
the same length).
"""
for i in range(len(a)):
if a[i] != b[i]:
return False
return True
def angle_is_between(angle, a, b):
"""
Takes in 3 angles (in radians) and returns 'a' <= 'angle' <= 'b'.
"""
to_degrees = lambda rads: int(rads * 180 / np.pi)
reduce_degrees = lambda degr: (360 + degr % 360) % 360
angle = to_degrees(angle)
a = to_degrees(a)
b = to_degrees(b)
angle = reduce_degrees(angle)
a = reduce_degrees(a)
b = reduce_degrees(b)
if a < b:
return a <= angle <= b
return a <= angle or angle <= b
def find_angle(x1, y1, x2, y2):
"""
Finds the angle between two points (in radians).
"""
angle = (360 + int(np.arctan2(y1 - y2, x2 - x1) * 180 / np.pi) % 360) % 360
return angle * np.pi / 180
def distance_between(x1, y1, x2, y2):
"""
Calculates the distance between 2 points.
"""
return np.hypot(x1 - x2, y1 - y2)
gitextract_rb342yw4/
├── LICENSE
├── README.md
└── src/
├── main.py
├── neural_network.py
├── population.py
├── settings.py
└── utility.py
SYMBOL INDEX (32 symbols across 4 files)
FILE: src/main.py
function main (line 15) | def main():
function update (line 133) | def update(dt, pop, key_pressed):
function render (line 160) | def render(window, FONT, pop):
FILE: src/neural_network.py
class NNetwork (line 9) | class NNetwork:
method __init__ (line 15) | def __init__(self, layer_sizes, activation_funcs, bias_neuron = False):
method feed_forward (line 47) | def feed_forward(self, data, one_hot_encoding = True):
method output (line 71) | def output(self):
class Layer (line 77) | class Layer:
method __init__ (line 83) | def __init__(self, num_neurons, activation_func, bias_neuron = False):
method activate (line 99) | def activate(self):
class Connection (line 106) | class Connection:
method __init__ (line 111) | def __init__(self, layer_from, layer_to):
function sigmoid (line 124) | def sigmoid(data):
function softmax (line 131) | def softmax(data):
FILE: src/population.py
class Population (line 11) | class Population:
method __init__ (line 16) | def __init__(self, size, mutation_rate):
method eliminate (line 37) | def eliminate(self, bot, replace = False):
method feed (line 44) | def feed(self, bot, food):
method update (line 80) | def update(self, dt):
class Bot (line 124) | class Bot:
method __init__ (line 142) | def __init__(self, nnet, rgb, population):
method _move_forward (line 151) | def _move_forward(self, dt):
method _turn_left (line 158) | def _turn_left(self, dt):
method _turn_right (line 163) | def _turn_right(self, dt):
method update (line 168) | def update(self, dt, sensory_input):
class Food (line 185) | class Food:
method __init__ (line 194) | def __init__(self, population):
method update (line 205) | def update(self, dt):
FILE: src/utility.py
function seq_is_equal (line 7) | def seq_is_equal(a, b):
function angle_is_between (line 17) | def angle_is_between(angle, a, b):
function find_angle (line 33) | def find_angle(x1, y1, x2, y2):
function distance_between (line 40) | def distance_between(x1, y1, x2, y2):
Condensed preview — 7 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (26K chars).
[
{
"path": "LICENSE",
"chars": 1072,
"preview": "MIT License\n\nCopyright (c) 2017 Michael J Welsh\n\nPermission is hereby granted, free of charge, to any person obtaining a"
},
{
"path": "README.md",
"chars": 977,
"preview": "# Bot Evolution\nBot evolution is an interesting display of evolution through neural networks and a genetic algorithm. Bo"
},
{
"path": "src/main.py",
"chars": 7500,
"preview": "\"\"\"\nBot Evolution v1.0.0\n\"\"\"\n\nimport os\nimport sys\nimport pickle\nimport pygame as pg\nfrom pygame.locals import *\nimport "
},
{
"path": "src/neural_network.py",
"chars": 5068,
"preview": "\"\"\"\nThis module implements the neural network class and all components necessary to\nmodularize the build/use process of "
},
{
"path": "src/population.py",
"chars": 8881,
"preview": "\"\"\"\nThis modules implements the bulk of Bot Evolution.\n\"\"\"\n\nimport numpy as np\nimport copy\nimport settings\nfrom utility "
},
{
"path": "src/settings.py",
"chars": 143,
"preview": "\"\"\"\nThis module contains the general settings used across modules.\n\"\"\"\n\nFPS = 60\nWINDOW_WIDTH = 1100\nWINDOW_HEIGHT = 600"
},
{
"path": "src/utility.py",
"chars": 1136,
"preview": "\"\"\"\nThis module contains general-use functions.\n\"\"\"\n\nimport numpy as np\n\ndef seq_is_equal(a, b):\n \"\"\"\n Helper func"
}
]
About this extraction
This page contains the full source code of the MichaelJWelsh/bot-evolution GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 7 files (24.2 KB), approximately 6.3k tokens, and a symbol index with 32 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.