Skip to content

Commit

Permalink
Initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
MaxRohowsky committed Feb 5, 2023
0 parents commit 3b259f6
Show file tree
Hide file tree
Showing 9 changed files with 567 additions and 0 deletions.
89 changes: 89 additions & 0 deletions brain.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
import node
import connection
import random


class Brain:
def __init__(self, inputs, clone=False):
self.connections = []
self.nodes = []
self.inputs = inputs
self.net = []
self.layers = 2

if not clone:
# Create input nodes
for i in range(0, self.inputs):
self.nodes.append(node.Node(i))
self.nodes[i].layer = 0
# Create bias node
self.nodes.append(node.Node(3))
self.nodes[3].layer = 0
# Create output node
self.nodes.append(node.Node(4))
self.nodes[4].layer = 1

# Create connections
for i in range(0, 4):
self.connections.append(connection.Connection(self.nodes[i],
self.nodes[4],
random.uniform(-1, 1)))

def connect_nodes(self):
for i in range(0, len(self.nodes)):
self.nodes[i].connections = []

for i in range(0, len(self.connections)):
self.connections[i].from_node.connections.append(self.connections[i])

def generate_net(self):
self.connect_nodes()
self.net = []
for j in range(0, self.layers):
for i in range(0, len(self.nodes)):
if self.nodes[i].layer == j:
self.net.append(self.nodes[i])

def feed_forward(self, vision):
for i in range(0, self.inputs):
self.nodes[i].output_value = vision[i]

self.nodes[3].output_value = 1

for i in range(0, len(self.net)):
self.net[i].activate()

# Get output value from output node
output_value = self.nodes[4].output_value

# Reset node input values - only node 6 Missing Natural Selection in this case
for i in range(0, len(self.nodes)):
self.nodes[i].input_value = 0

return output_value

def clone(self):
clone = Brain(self.inputs, True)

# Clone all the nodes
for n in self.nodes:
clone.nodes.append(n.clone())

# Clone all connections
for c in self.connections:
clone.connections.append(c.clone(clone.getNode(c.from_node.id), clone.getNode(c.to_node.id)))

clone.layers = self.layers
clone.connect_nodes()
return clone

def getNode(self, id):
for n in self.nodes:
if n.id == id:
return n

# 80 % chance that a connection undergoes mutation
def mutate(self):
if random.uniform(0, 1) < 0.8:
for i in range(0, len(self.connections)):
self.connections[i].mutate_weight()
53 changes: 53 additions & 0 deletions components.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import pygame
import random


class Ground:
ground_level = 500

def __init__(self, win_width):
self.x, self.y = 0, Ground.ground_level
self.rect = pygame.Rect(self.x, self.y, win_width, 5)

def draw(self, window):
pygame.draw.rect(window, (255, 255, 255), self.rect)


class Pipes:
width = 15
opening = 100

def __init__(self, win_width):
self.x = win_width
self.bottom_height = random.randint(10, 300)
self.top_height = Ground.ground_level - self.bottom_height - self.opening
self.bottom_rect, self.top_rect = pygame.Rect(0, 0, 0, 0), pygame.Rect(0, 0, 0, 0)
self.passed = False
self.off_screen = False

def draw(self, window):
self.bottom_rect = pygame.Rect(self.x, Ground.ground_level - self.bottom_height, self.width, self.bottom_height)
pygame.draw.rect(window, (255, 255, 255), self.bottom_rect)

self.top_rect = pygame.Rect(self.x, 0, self.width, self.top_height)
pygame.draw.rect(window, (255, 255, 255), self.top_rect)

def update(self):
self.x -= 1
if self.x + Pipes.width <= 50:
self.passed = True
if self.x <= -self.width:
self.off_screen = True













9 changes: 9 additions & 0 deletions config.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import components
import pygame

win_height = 720
win_width = 550
window = pygame.display.set_mode((win_width, win_height))

ground = components.Ground(win_width)
pipes = []
21 changes: 21 additions & 0 deletions connection.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import random

class Connection:
def __init__(self, from_node, to_node, weight):
self.from_node = from_node
self.to_node = to_node
self.weight = weight

def mutate_weight(self):
if random.uniform(0, 1) < 0.1:
self.weight = random.uniform(-1, 1)
else:
self.weight += random.gauss(0, 1)/10
if self.weight > 1:
self.weight = 1
if self.weight < -1:
self.weight = -1

def clone(self, from_node, to_node):
clone = Connection(from_node, to_node, self.weight)
return clone
52 changes: 52 additions & 0 deletions main.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import pygame
from sys import exit
import config
import components
import population

pygame.init()
clock = pygame.time.Clock()
population = population.Population(100)

def generate_pipes():
config.pipes.append(components.Pipes(config.win_width))

def quit_game():
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
exit()

def main():
pipes_spawn_time = 10

while True:
quit_game()

config.window.fill((0, 0, 0))

# Spawn Ground
config.ground.draw(config.window)

# Spawn Pipes
if pipes_spawn_time <= 0:
generate_pipes()
pipes_spawn_time = 200
pipes_spawn_time -= 1

for p in config.pipes:
p.draw(config.window)
p.update()
if p.off_screen:
config.pipes.remove(p)

if not population.extinct():
population.update_live_players()
else:
config.pipes.clear()
population.natural_selection()

clock.tick(60)
pygame.display.flip()

main()
37 changes: 37 additions & 0 deletions node.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import math


class Node:
def __init__(self, id_number):
self.id = id_number
self.layer = 0
self.input_value = 0
self.output_value = 0
self.connections = []

def activate(self):
def sigmoid(x):
return 1/(1+math.exp(-x))

if self.layer == 1:
self.output_value = sigmoid(self.input_value)

for i in range(0, len(self.connections)):
self.connections[i].to_node.input_value += \
self.connections[i].weight * self.output_value

def clone(self):
clone = Node(self.id)
clone.id = self.id
clone.layer = self.layer
return clone










111 changes: 111 additions & 0 deletions player.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
import brain
import random
import pygame
import config


class Player:
def __init__(self):
# Bird
self.x, self.y = 50, 200
self.rect = pygame.Rect(self.x, self.y, 20, 20)
self.color = random.randint(100, 255), random.randint(100, 255), random.randint(100, 255)
self.vel = 0
self.flap = False
self.alive = True
self.lifespan = 0

# AI
self.decision = None
self.vision = [0.5, 1, 0.5]
self.fitness = 0
self.inputs = 3
self.brain = brain.Brain(self.inputs)
self.brain.generate_net()

# Game related functions
def draw(self, window):
pygame.draw.rect(window, self.color, self.rect)

def ground_collision(self, ground):
return pygame.Rect.colliderect(self.rect, ground)

def sky_collision(self):
return bool(self.rect.y < 30)

def pipe_collision(self):
for p in config.pipes:
return pygame.Rect.colliderect(self.rect, p.top_rect) or \
pygame.Rect.colliderect(self.rect, p.bottom_rect)

def update(self, ground):
if not (self.ground_collision(ground) or self.pipe_collision()):
# Gravity
self.vel += 0.25
self.rect.y += self.vel
if self.vel > 5:
self.vel = 5
# Increment lifespan
self.lifespan += 1
else:
self.alive = False
self.flap = False
self.vel = 0

def bird_flap(self):
if not self.flap and not self.sky_collision():
self.flap = True
self.vel = -5
if self.vel >= 3:
self.flap = False

@staticmethod
def closest_pipe():
for p in config.pipes:
if not p.passed:
return p

# AI related functions
def look(self):
if config.pipes:

# Line to top pipe
self.vision[0] = max(0, self.rect.center[1] - self.closest_pipe().top_rect.bottom) / 500
pygame.draw.line(config.window, self.color, self.rect.center,
(self.rect.center[0], config.pipes[0].top_rect.bottom))

# Line to mid pipe
self.vision[1] = max(0, self.closest_pipe().x - self.rect.center[0]) / 500
pygame.draw.line(config.window, self.color, self.rect.center,
(config.pipes[0].x, self.rect.center[1]))

# Line to bottom pipe
self.vision[2] = max(0, self.closest_pipe().bottom_rect.top - self.rect.center[1]) / 500
pygame.draw.line(config.window, self.color, self.rect.center,
(self.rect.center[0], config.pipes[0].bottom_rect.top))

def think(self):
self.decision = self.brain.feed_forward(self.vision)
if self.decision > 0.73:
self.bird_flap()

def calculate_fitness(self):
self.fitness = self.lifespan

def clone(self):
clone = Player()
clone.fitness = self.fitness
clone.brain = self.brain.clone()
clone.brain.generate_net()
return clone











Loading

0 comments on commit 3b259f6

Please sign in to comment.