├── Feed_Forward_Neural_Network.py ├── Genetic_Algorithm.py ├── LICENSE ├── README.md ├── Run_Game.py ├── Snake_Game.py └── main.py /Feed_Forward_Neural_Network.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | 3 | n_x = 7 4 | n_h = 9 5 | n_h2 = 15 6 | n_y = 3 7 | W1_shape = (9,7) 8 | W2_shape = (15,9) 9 | W3_shape = (3,15) 10 | 11 | def get_weights_from_encoded(individual): 12 | W1 = individual[0:W1_shape[0] * W1_shape[1]] 13 | W2 = individual[W1_shape[0] * W1_shape[1]:W2_shape[0] * W2_shape[1] + W1_shape[0] * W1_shape[1]] 14 | W3 = individual[W2_shape[0] * W2_shape[1] + W1_shape[0] * W1_shape[1]:] 15 | 16 | return ( 17 | W1.reshape(W1_shape[0], W1_shape[1]), W2.reshape(W2_shape[0], W2_shape[1]), W3.reshape(W3_shape[0], W3_shape[1])) 18 | 19 | 20 | def softmax(z): 21 | s = np.exp(z.T) / np.sum(np.exp(z.T), axis=1).reshape(-1, 1) 22 | 23 | return s 24 | 25 | 26 | def sigmoid(z): 27 | s = 1 / (1 + np.exp(-z)) 28 | 29 | return s 30 | 31 | 32 | def forward_propagation(X, individual): 33 | W1, W2, W3 = get_weights_from_encoded(individual) 34 | 35 | Z1 = np.matmul(W1, X.T) 36 | A1 = np.tanh(Z1) 37 | Z2 = np.matmul(W2, A1) 38 | A2 = np.tanh(Z2) 39 | Z3 = np.matmul(W3, A2) 40 | A3 = softmax(Z3) 41 | return A3 -------------------------------------------------------------------------------- /Genetic_Algorithm.py: -------------------------------------------------------------------------------- 1 | from Run_Game import * 2 | from random import choice, randint 3 | 4 | def cal_pop_fitness(pop): 5 | # calculating the fitness value by playing a game with the given weights in chromosome 6 | fitness = [] 7 | for i in range(pop.shape[0]): 8 | fit = run_game_with_ML(display,clock,pop[i]) 9 | print('fitness value of chromosome '+ str(i) +' : ', fit) 10 | fitness.append(fit) 11 | return np.array(fitness) 12 | 13 | def select_mating_pool(pop, fitness, num_parents): 14 | # Selecting the best individuals in the current generation as parents for producing the offspring of the next generation. 15 | parents = np.empty((num_parents, pop.shape[1])) 16 | for parent_num in range(num_parents): 17 | max_fitness_idx = np.where(fitness == np.max(fitness)) 18 | max_fitness_idx = max_fitness_idx[0][0] 19 | parents[parent_num, :] = pop[max_fitness_idx, :] 20 | fitness[max_fitness_idx] = -99999999 21 | return parents 22 | 23 | def crossover(parents, offspring_size): 24 | # creating children for next generation 25 | offspring = np.empty(offspring_size) 26 | 27 | for k in range(offspring_size[0]): 28 | 29 | while True: 30 | parent1_idx = random.randint(0, parents.shape[0] - 1) 31 | parent2_idx = random.randint(0, parents.shape[0] - 1) 32 | # produce offspring from two parents if they are different 33 | if parent1_idx != parent2_idx: 34 | for j in range(offspring_size[1]): 35 | if random.uniform(0, 1) < 0.5: 36 | offspring[k, j] = parents[parent1_idx, j] 37 | else: 38 | offspring[k, j] = parents[parent2_idx, j] 39 | break 40 | return offspring 41 | 42 | 43 | def mutation(offspring_crossover): 44 | # mutating the offsprings generated from crossover to maintain variation in the population 45 | 46 | for idx in range(offspring_crossover.shape[0]): 47 | for _ in range(25): 48 | i = randint(0,offspring_crossover.shape[1]-1) 49 | 50 | random_value = np.random.choice(np.arange(-1,1,step=0.001),size=(1),replace=False) 51 | offspring_crossover[idx, i] = offspring_crossover[idx, i] + random_value 52 | 53 | return offspring_crossover 54 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 kang & atul 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Training-Snake-Game-Using-Genetic-Algorithm 2 | 3 | main.py - to start training snake game using genetic algorithm 4 | 5 | Snake_Game.py - contains logic of creating snake game using pygame 6 | 7 | Run_Game.py - play snake game using predicted directions from genetic algorithm 8 | 9 | Genetic_Algorithm.py - contains genetic algorithm functions like crossover, mutation etc 10 | 11 | Feed_Forward_Neural_Network.py - contains the functions for calculating the output from feed forward neural network 12 | 13 | To know more about algorithm used in this code you can follow this blog https://theailearner.com/2018/11/09/snake-game-with-genetic-algorithm/ 14 | 15 | 16 | # License & Copyright 17 | 18 | Copyright (c) 2019 kang & atul 19 | 20 | Licensed under the [MIT License](LICENSE). 21 | -------------------------------------------------------------------------------- /Run_Game.py: -------------------------------------------------------------------------------- 1 | from Snake_Game import * 2 | from Feed_Forward_Neural_Network import * 3 | 4 | def run_game_with_ML(display, clock, weights): 5 | max_score = 0 6 | avg_score = 0 7 | test_games = 1 8 | score1 = 0 9 | steps_per_game = 2500 10 | score2 = 0 11 | 12 | for _ in range(test_games): 13 | snake_start, snake_position, apple_position, score = starting_positions() 14 | 15 | count_same_direction = 0 16 | prev_direction = 0 17 | 18 | for _ in range(steps_per_game): 19 | current_direction_vector, is_front_blocked, is_left_blocked, is_right_blocked = blocked_directions( 20 | snake_position) 21 | angle, snake_direction_vector, apple_direction_vector_normalized, snake_direction_vector_normalized = angle_with_apple( 22 | snake_position, apple_position) 23 | predictions = [] 24 | predicted_direction = np.argmax(np.array(forward_propagation(np.array( 25 | [is_left_blocked, is_front_blocked, is_right_blocked, apple_direction_vector_normalized[0], 26 | snake_direction_vector_normalized[0], apple_direction_vector_normalized[1], 27 | snake_direction_vector_normalized[1]]).reshape(-1, 7), weights))) - 1 28 | 29 | if predicted_direction == prev_direction: 30 | count_same_direction += 1 31 | else: 32 | count_same_direction = 0 33 | prev_direction = predicted_direction 34 | 35 | new_direction = np.array(snake_position[0]) - np.array(snake_position[1]) 36 | if predicted_direction == -1: 37 | new_direction = np.array([new_direction[1], -new_direction[0]]) 38 | if predicted_direction == 1: 39 | new_direction = np.array([-new_direction[1], new_direction[0]]) 40 | 41 | button_direction = generate_button_direction(new_direction) 42 | 43 | next_step = snake_position[0] + current_direction_vector 44 | if collision_with_boundaries(snake_position[0]) == 1 or collision_with_self(next_step.tolist(), 45 | snake_position) == 1: 46 | score1 += -150 47 | break 48 | 49 | else: 50 | score1 += 0 51 | 52 | snake_position, apple_position, score = play_game(snake_start, snake_position, apple_position, 53 | button_direction, score, display, clock) 54 | 55 | if score > max_score: 56 | max_score = score 57 | 58 | if count_same_direction > 8 and predicted_direction != 0: 59 | score2 -= 1 60 | else: 61 | score2 += 2 62 | 63 | 64 | return score1 + score2 + max_score * 5000 -------------------------------------------------------------------------------- /Snake_Game.py: -------------------------------------------------------------------------------- 1 | import random 2 | import random 3 | import time 4 | import math 5 | from tqdm import tqdm 6 | import numpy as np 7 | import pygame 8 | 9 | def display_snake(snake_position, display): 10 | for position in snake_position: 11 | pygame.draw.rect(display, (255, 0, 0), pygame.Rect(position[0], position[1], 10, 10)) 12 | 13 | 14 | def display_apple(apple_position, display): 15 | pygame.draw.rect(display, (0, 255, 0), pygame.Rect(apple_position[0], apple_position[1], 10, 10)) 16 | 17 | 18 | def starting_positions(): 19 | snake_start = [100, 100] 20 | snake_position = [[100, 100], [90, 100], [80, 100]] 21 | apple_position = [random.randrange(1, 50) * 10, random.randrange(1, 50) * 10] 22 | score = 0 23 | 24 | return snake_start, snake_position, apple_position, score 25 | 26 | 27 | def apple_distance_from_snake(apple_position, snake_position): 28 | return np.linalg.norm(np.array(apple_position) - np.array(snake_position[0])) 29 | 30 | 31 | def generate_snake(snake_start, snake_position, apple_position, button_direction, score): 32 | if button_direction == 1: 33 | snake_start[0] += 10 34 | elif button_direction == 0: 35 | snake_start[0] -= 10 36 | elif button_direction == 2: 37 | snake_start[1] += 10 38 | else: 39 | snake_start[1] -= 10 40 | 41 | if snake_start == apple_position: 42 | apple_position, score = collision_with_apple(apple_position, score) 43 | snake_position.insert(0, list(snake_start)) 44 | 45 | else: 46 | snake_position.insert(0, list(snake_start)) 47 | snake_position.pop() 48 | 49 | return snake_position, apple_position, score 50 | 51 | 52 | def collision_with_apple(apple_position, score): 53 | apple_position = [random.randrange(1, 50) * 10, random.randrange(1, 50) * 10] 54 | score += 1 55 | return apple_position, score 56 | 57 | 58 | def collision_with_boundaries(snake_start): 59 | if snake_start[0] >= 500 or snake_start[0] < 0 or snake_start[1] >= 500 or snake_start[1] < 0: 60 | return 1 61 | else: 62 | return 0 63 | 64 | 65 | def collision_with_self(snake_start, snake_position): 66 | # snake_start = snake_position[0] 67 | if snake_start in snake_position[1:]: 68 | return 1 69 | else: 70 | return 0 71 | 72 | 73 | def blocked_directions(snake_position): 74 | current_direction_vector = np.array(snake_position[0]) - np.array(snake_position[1]) 75 | 76 | left_direction_vector = np.array([current_direction_vector[1], -current_direction_vector[0]]) 77 | right_direction_vector = np.array([-current_direction_vector[1], current_direction_vector[0]]) 78 | 79 | is_front_blocked = is_direction_blocked(snake_position, current_direction_vector) 80 | is_left_blocked = is_direction_blocked(snake_position, left_direction_vector) 81 | is_right_blocked = is_direction_blocked(snake_position, right_direction_vector) 82 | 83 | return current_direction_vector, is_front_blocked, is_left_blocked, is_right_blocked 84 | 85 | 86 | def is_direction_blocked(snake_position, current_direction_vector): 87 | next_step = snake_position[0] + current_direction_vector 88 | snake_start = snake_position[0] 89 | if collision_with_boundaries(next_step) == 1 or collision_with_self(next_step.tolist(), snake_position) == 1: 90 | return 1 91 | else: 92 | return 0 93 | 94 | 95 | def generate_random_direction(snake_position, angle_with_apple): 96 | direction = 0 97 | if angle_with_apple > 0: 98 | direction = 1 99 | elif angle_with_apple < 0: 100 | direction = -1 101 | else: 102 | direction = 0 103 | 104 | return direction_vector(snake_position, angle_with_apple, direction) 105 | 106 | 107 | def direction_vector(snake_position, angle_with_apple, direction): 108 | current_direction_vector = np.array(snake_position[0]) - np.array(snake_position[1]) 109 | left_direction_vector = np.array([current_direction_vector[1], -current_direction_vector[0]]) 110 | right_direction_vector = np.array([-current_direction_vector[1], current_direction_vector[0]]) 111 | 112 | new_direction = current_direction_vector 113 | 114 | if direction == -1: 115 | new_direction = left_direction_vector 116 | if direction == 1: 117 | new_direction = right_direction_vector 118 | 119 | button_direction = generate_button_direction(new_direction) 120 | 121 | return direction, button_direction 122 | 123 | 124 | def generate_button_direction(new_direction): 125 | button_direction = 0 126 | if new_direction.tolist() == [10, 0]: 127 | button_direction = 1 128 | elif new_direction.tolist() == [-10, 0]: 129 | button_direction = 0 130 | elif new_direction.tolist() == [0, 10]: 131 | button_direction = 2 132 | else: 133 | button_direction = 3 134 | 135 | return button_direction 136 | 137 | 138 | def angle_with_apple(snake_position, apple_position): 139 | apple_direction_vector = np.array(apple_position) - np.array(snake_position[0]) 140 | snake_direction_vector = np.array(snake_position[0]) - np.array(snake_position[1]) 141 | 142 | norm_of_apple_direction_vector = np.linalg.norm(apple_direction_vector) 143 | norm_of_snake_direction_vector = np.linalg.norm(snake_direction_vector) 144 | if norm_of_apple_direction_vector == 0: 145 | norm_of_apple_direction_vector = 10 146 | if norm_of_snake_direction_vector == 0: 147 | norm_of_snake_direction_vector = 10 148 | 149 | apple_direction_vector_normalized = apple_direction_vector / norm_of_apple_direction_vector 150 | snake_direction_vector_normalized = snake_direction_vector / norm_of_snake_direction_vector 151 | angle = math.atan2( 152 | apple_direction_vector_normalized[1] * snake_direction_vector_normalized[0] - apple_direction_vector_normalized[ 153 | 0] * snake_direction_vector_normalized[1], 154 | apple_direction_vector_normalized[1] * snake_direction_vector_normalized[1] + apple_direction_vector_normalized[ 155 | 0] * snake_direction_vector_normalized[0]) / math.pi 156 | return angle, snake_direction_vector, apple_direction_vector_normalized, snake_direction_vector_normalized 157 | 158 | 159 | def play_game(snake_start, snake_position, apple_position, button_direction, score, display, clock): 160 | crashed = False 161 | while crashed is not True: 162 | for event in pygame.event.get(): 163 | if event.type == pygame.QUIT: 164 | crashed = True 165 | display.fill((255, 255, 255)) 166 | 167 | display_apple(apple_position, display) 168 | display_snake(snake_position, display) 169 | 170 | snake_position, apple_position, score = generate_snake(snake_start, snake_position, apple_position, 171 | button_direction, score) 172 | pygame.display.set_caption("SCORE: " + str(score)) 173 | pygame.display.update() 174 | clock.tick(50000) 175 | 176 | return snake_position, apple_position, score 177 | 178 | 179 | 180 | ''' 181 | LEFT -> button_direction = 0 182 | RIGHT -> button_direction = 1 183 | DOWN ->button_direction = 2 184 | UP -> button_direction = 3 185 | ''' 186 | 187 | display_width = 500 188 | display_height = 500 189 | green = (0,255,0) 190 | red = (255,0,0) 191 | black = (0,0,0) 192 | white = (255,255,255) 193 | 194 | pygame.init() 195 | display=pygame.display.set_mode((display_width,display_height)) 196 | clock=pygame.time.Clock() -------------------------------------------------------------------------------- /main.py: -------------------------------------------------------------------------------- 1 | from Genetic_Algorithm import * 2 | from Snake_Game import * 3 | 4 | # n_x -> no. of input units 5 | # n_h -> no. of units in hidden layer 1 6 | # n_h2 -> no. of units in hidden layer 2 7 | # n_y -> no. of output units 8 | 9 | # The population will have sol_per_pop chromosome where each chromosome has num_weights genes. 10 | sol_per_pop = 50 11 | num_weights = n_x*n_h + n_h*n_h2 + n_h2*n_y 12 | 13 | # Defining the population size. 14 | pop_size = (sol_per_pop,num_weights) 15 | #Creating the initial population. 16 | new_population = np.random.choice(np.arange(-1,1,step=0.01),size=pop_size,replace=True) 17 | 18 | num_generations = 100 19 | 20 | num_parents_mating = 12 21 | for generation in range(num_generations): 22 | print('############## GENERATION ' + str(generation)+ ' ###############' ) 23 | # Measuring the fitness of each chromosome in the population. 24 | fitness = cal_pop_fitness(new_population) 25 | print('####### fittest chromosome in gneneration ' + str(generation) +' is having fitness value: ', np.max(fitness)) 26 | # Selecting the best parents in the population for mating. 27 | parents = select_mating_pool(new_population, fitness, num_parents_mating) 28 | 29 | # Generating next generation using crossover. 30 | offspring_crossover = crossover(parents, offspring_size=(pop_size[0] - parents.shape[0], num_weights)) 31 | 32 | # Adding some variations to the offsrping using mutation. 33 | offspring_mutation = mutation(offspring_crossover) 34 | 35 | # Creating the new population based on the parents and offspring. 36 | new_population[0:parents.shape[0], :] = parents 37 | new_population[parents.shape[0]:, :] = offspring_mutation 38 | --------------------------------------------------------------------------------