Skip to content

Commit

Permalink
Added the source code
Browse files Browse the repository at this point in the history
  • Loading branch information
Kofybrek authored Aug 29, 2021
1 parent 6ad96f3 commit 73a0ee4
Show file tree
Hide file tree
Showing 15 changed files with 795 additions and 0 deletions.
246 changes: 246 additions & 0 deletions Source/Bird.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,246 @@
#include <array>
#include <chrono>
#include <random>
#include <SFML/Graphics.hpp>

#include "Headers/Pipes.hpp"
#include "Headers/Bird.hpp"
#include "Headers/Global.hpp"

Bird::Bird() :
mutation_distribution(0, MUTATION_PROBABILITY - 1),
//real_distribution includes the min value and excludes the max.
//And since I'm a crazy perfectionist, I used nextafter.
node_distribution(-1, std::nextafter(1, 2)),
weights(2)
{
reset();
}

bool Bird::do_ai_stuff(std::vector<Pipes> i_pipes)
{
std::vector<std::vector<float>> neural_network(3);

neural_network[0].resize(TOTAL_INPUT_NODES);
neural_network[1].resize(TOTAL_HIDDEN_NODES, 0);
neural_network[2].resize(TOTAL_OUTPUT_NODES, 0);

//Input layer.
neural_network[0][0] = vertical_speed;
neural_network[0][1] = get_gap_difference(i_pipes);

//Here we're getting the direction of the first 2 pipes, that are in front of the bird.
for (Pipes& a : i_pipes)
{
if (x < a.get_x() + 2 * BIRD_SIZE)
{
neural_network[0][2] = a.get_direction();

break;
}
}

//I heard that smart people use matrices in a neural network.
//But I'm not one of them.
for (unsigned char a = 0; a < neural_network.size() - 1; a++)
{
for (unsigned char b = 0; b < neural_network[1 + a].size(); b++)
{
for (unsigned char c = 0; c < neural_network[a].size(); c++)
{
neural_network[1 + a][b] += neural_network[a][c] * weights[a][c][b];
}

if (0 >= neural_network[1 + a][b])
{
neural_network[1 + a][b] = pow<float>(2, neural_network[1 + a][b]) - 1;
}
else
{
neural_network[1 + a][b] = 1 - pow<float>(2, -neural_network[1 + a][b]);
}
}
}

return 0 <= neural_network[2][0];
}

bool Bird::get_dead()
{
return dead;
}

bool Bird::operator>(Bird& i_bird)
{
return get_fitness() > i_bird.get_fitness();
}

bool Bird::operator<(Bird& i_bird)
{
return get_fitness() < i_bird.get_fitness();
}

float Bird::get_gap_difference(std::vector<Pipes> i_pipes)
{
for (Pipes& a : i_pipes)
{
//I made the width of the pipes twice the size of the bird.
if (x < a.get_x() + 2 * BIRD_SIZE)
{
return GAP_SIZE + a.get_y() - BIRD_SIZE - y;
}
}

return 0;
}

float Bird::get_y()
{
return y;
}

unsigned Bird::get_fitness()
{
return fitness;
}

unsigned short Bird::get_score()
{
return score;
}

void Bird::crossover(std::mt19937_64& i_random_engine, const std::vector<std::vector<std::vector<float>>>& i_bird_0_weights, const std::vector<std::vector<std::vector<float>>>& i_bird_1_weights)
{
//I used "Uniform crossover".
//I think...
//Hold on, lemme do a quick Google search.
//(After some time...)
//Yeah, I was correct.

for (unsigned char a = 0; a < weights.size(); a++)
{
for (unsigned char b = 0; b < weights[a].size(); b++)
{
for (unsigned char c = 0; c < weights[a][b].size(); c++)
{
//I didn't wanna use random_engine for this.
//It's like using a katana to cut bread. (Ba dum tss)
if (0 == rand() % 2)
{
weights[a][b][c] = i_bird_0_weights[a][b][c];
}
else
{
weights[a][b][c] = i_bird_1_weights[a][b][c];
}

if (0 == mutation_distribution(i_random_engine))
{
weights[a][b][c] = node_distribution(i_random_engine);
}
}
}
}
}

void Bird::draw(sf::RenderWindow& i_window)
{
sf::Sprite sprite;

sf::Texture texture;
texture.loadFromFile("Resources/Images/Bird" + std::to_string(BIRD_SIZE) + ".png");

sprite.setPosition(x, round(y));
sprite.setTexture(texture);
sprite.setTextureRect(sf::IntRect(BIRD_SIZE * (0 >= vertical_speed), BIRD_SIZE * dead, BIRD_SIZE, BIRD_SIZE));

i_window.draw(sprite);
}

void Bird::generate_weights(std::mt19937_64& i_random_engine)
{
weights[0].resize(TOTAL_INPUT_NODES, std::vector<float>(TOTAL_HIDDEN_NODES));
weights[1].resize(TOTAL_HIDDEN_NODES, std::vector<float>(TOTAL_OUTPUT_NODES));

//This is how I structured the vector of weights.
for (std::vector<std::vector<float>>& layer : weights)
{
for (std::vector<float>& previous_node : layer)
{
for (float& next_node : previous_node)
{
//This is the weight of the connection between the previous node and the next.
next_node = node_distribution(i_random_engine);
}
}
}
}

void Bird::reset()
{
dead = 0;

vertical_speed = 0;
y = 0.5f * (GROUND_Y - BIRD_SIZE);

x = BIRD_START_X;

fitness = 0;

score = 0;
}

void Bird::update(bool i_move, std::vector<Pipes> i_pipes)
{
vertical_speed += GRAVITY;
y += vertical_speed;

if (0 == dead)
{
if (0 <= vertical_speed && 0 <= y && 1 == do_ai_stuff(i_pipes))
{
vertical_speed = FLAP_SPEED;
}

for (Pipes& a : i_pipes)
{
if (x < a.get_x() + 2 * BIRD_SIZE && x > a.get_x() - BIRD_SIZE)
{
if (y < a.get_y() || y > GAP_SIZE + a.get_y() - BIRD_SIZE)
{
dead = 1;

vertical_speed = 0;
}
}
//This doesn't work when the bird is faster.
//So don't make it faster.
else if (x == a.get_x() + 2 * BIRD_SIZE)
{
score++;
}
}
}

if (y >= GROUND_Y - BIRD_SIZE)
{
dead = 1;

vertical_speed = 0;
y = GROUND_Y - BIRD_SIZE;
}

if (0 == dead)
{
fitness += BIRD_SPEED;
}
else if (1 == i_move)
{
x = std::max(-BIRD_SIZE, x - BIRD_SPEED);
}
}

std::vector<std::vector<std::vector<float>>> Bird::get_weights()
{
return weights;
}
66 changes: 66 additions & 0 deletions Source/DrawText.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
#include <chrono>
#include <SFML/Graphics.hpp>

#include "Headers/DrawText.hpp"
#include "Headers/Global.hpp"

void draw_text(bool i_black, bool i_horizontal_center, bool i_vertical_center, unsigned short i_x, unsigned short i_y, const std::string& i_text, sf::RenderWindow& i_window)
{
short character_x = i_x;
short character_y = i_y;

unsigned char character_width;

sf::Sprite character_sprite;

sf::Texture font_texture;
font_texture.loadFromFile("Resources/Images/Font.png");

//There are 96 characters in the texture.
character_width = font_texture.getSize().x / 96;

character_sprite.setTexture(font_texture);

if (1 == i_black)
{
character_sprite.setColor(sf::Color(0, 0, 0));
}

if (1 == i_horizontal_center)
{
//I'd love to explain this, but I don't know how it works myself.
character_x += static_cast<short>(round(0.5f * (SCREEN_WIDTH - character_width * i_text.substr(0, i_text.find_first_of('\n')).size())));
}

if (1 == i_vertical_center)
{
//If you know any better way to do this, please let me know.
character_y += static_cast<short>(round(0.5f * (SCREEN_HEIGHT - FONT_HEIGHT * (1 + std::count(i_text.begin(), i_text.end(), '\n')))));
}

for (std::string::const_iterator a = i_text.begin(); a != i_text.end(); a++)
{
if ('\n' == *a)
{
if (1 == i_horizontal_center)
{
character_x = i_x + static_cast<short>(round(0.5f * (SCREEN_WIDTH - character_width * i_text.substr(1 + a - i_text.begin(), i_text.find_first_of('\n', 1 + a - i_text.begin()) - (1 + a - i_text.begin())).size())));
}
else
{
character_x = i_x;
}

character_y += FONT_HEIGHT;

continue;
}

character_sprite.setPosition(character_x, character_y);
character_sprite.setTextureRect(sf::IntRect(character_width * (*a - 32), 0, character_width, FONT_HEIGHT));

character_x += character_width;

i_window.draw(character_sprite);
}
}
53 changes: 53 additions & 0 deletions Source/Headers/Bird.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
#pragma once

class Bird
{
bool dead;

float vertical_speed;
float y;

//Why is x short and y float?
//Because gravity isn't horizontal.
short x;

unsigned fitness;

unsigned short score;

std::uniform_int_distribution<unsigned short> mutation_distribution;

//This is the range in which the weights can be.
std::uniform_real_distribution<float> node_distribution;

//I didn't know how to use arrays here.
std::vector<std::vector<std::vector<float>>> weights;
public:
Bird();

//I'm pretty sure some of you will hate me for naming this function like that.
//"iT DoEsN'T MaKe aNy sEnSe!"
//"iT MaKeS ThE CoDe hArD To uNdErStAnD!"
//"wHy cAn't yOu bE NoRmAl?"
bool do_ai_stuff(std::vector<Pipes> i_pipes);
bool get_dead();
bool operator>(Bird& i_bird);
bool operator<(Bird& i_bird);

//The difference between the bird and the gap.
float get_gap_difference(std::vector<Pipes> i_pipes);
float get_y();

unsigned get_fitness();

unsigned short get_score();

void crossover(std::mt19937_64& i_random_engine, const std::vector<std::vector<std::vector<float>>>& i_bird_0_weights, const std::vector<std::vector<std::vector<float>>>& i_bird_1_weights);
void draw(sf::RenderWindow& i_window);
void generate_weights(std::mt19937_64& i_random_engine);
void reset();
//We'll use i_move to move the bird when it's dead and there are still birds alive.
void update(bool i_move, std::vector<Pipes> i_pipes);

std::vector<std::vector<std::vector<float>>> get_weights();
};
3 changes: 3 additions & 0 deletions Source/Headers/DrawText.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
#pragma once

void draw_text(bool i_black, bool i_horizontal_center, bool i_vertical_center, unsigned short i_x, unsigned short i_y, const std::string& i_text, sf::RenderWindow& i_window);
Loading

0 comments on commit 73a0ee4

Please sign in to comment.