├── .gitignore ├── CMakeLists.txt ├── README.md ├── include ├── ai_unit.hpp ├── dna.hpp ├── dna_loader.hpp ├── dna_utils.hpp ├── double_buffer.hpp ├── drone_renderer.hpp ├── drone_ui.hpp ├── gauge_bar.hpp ├── graph.hpp ├── moving_average.hpp ├── neural_network.hpp ├── neural_renderer.hpp ├── number_generator.hpp ├── objective.hpp ├── rocket.hpp ├── rocket_renderer.hpp ├── selection_wheel.hpp ├── selector.hpp ├── smoke.hpp ├── stadium.hpp ├── target.hpp ├── unit.hpp └── utils.hpp ├── lib ├── event_manager.hpp └── swarm.hpp ├── res ├── flame.png ├── font.ttf ├── rocket.png └── smoke.png └── src ├── main.cpp └── utils.cpp /.gitignore: -------------------------------------------------------------------------------- 1 | # Prerequisites 2 | *.d 3 | 4 | # Compiled Object files 5 | *.slo 6 | *.lo 7 | *.o 8 | *.obj 9 | 10 | # Precompiled Headers 11 | *.gch 12 | *.pch 13 | 14 | # Compiled Dynamic libraries 15 | *.so 16 | *.dylib 17 | *.dll 18 | 19 | # Fortran module files 20 | *.mod 21 | *.smod 22 | 23 | # Compiled Static libraries 24 | *.lai 25 | *.la 26 | *.a 27 | *.lib 28 | 29 | # Executables 30 | *.exe 31 | *.out 32 | *.app 33 | 34 | build/ 35 | *.bin 36 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.10) 2 | set(PROJECT_NAME AutoRocket) 3 | project(${PROJECT_NAME} VERSION 1.0.0 LANGUAGES CXX) 4 | find_package(OpenGL) 5 | 6 | file(GLOB source_files 7 | "src/*.cpp" 8 | ) 9 | 10 | set(SOURCES ${source_files}) 11 | 12 | # Detect and add SFML 13 | set(CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/cmake_modules" ${CMAKE_MODULE_PATH}) 14 | find_package(SFML 2 REQUIRED COMPONENTS network audio graphics window system) 15 | 16 | add_executable(${PROJECT_NAME} ${SOURCES}) 17 | target_include_directories(${PROJECT_NAME} PRIVATE "include" "lib") 18 | target_link_libraries(${PROJECT_NAME} sfml-system sfml-window sfml-graphics) 19 | if (UNIX) 20 | target_link_libraries(${PROJECT_NAME} pthread) 21 | endif (UNIX) 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # AutoRocket -------------------------------------------------------------------------------- /include/ai_unit.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "unit.hpp" 4 | #include "neural_network.hpp" 5 | 6 | 7 | struct AiUnit : public Unit 8 | { 9 | AiUnit() 10 | : Unit(0) 11 | {} 12 | 13 | AiUnit(const std::vector& network_architecture) 14 | : Unit(Network::getParametersCount(network_architecture) * 32) 15 | , network(network_architecture) 16 | { 17 | dna.initialize(1.0f); 18 | updateNetwork(); 19 | } 20 | 21 | void execute(const std::vector& inputs) 22 | { 23 | const std::vector& outputs = network.execute(inputs); 24 | process(outputs); 25 | } 26 | 27 | void updateNetwork() 28 | { 29 | uint64_t index = 0; 30 | for (Layer& layer : network.layers) { 31 | const uint64_t neurons_count = layer.getNeuronsCount(); 32 | for (uint64_t i(0); i < neurons_count; ++i) { 33 | layer.bias[i] = dna.get(index++); 34 | } 35 | 36 | for (uint64_t i(0); i < neurons_count; ++i) { 37 | const uint64_t weights_count = layer.getWeightsCount(); 38 | for (uint64_t j(0); j < weights_count; ++j) { 39 | layer.weights[i][j] = dna.get(index++); 40 | } 41 | } 42 | } 43 | } 44 | 45 | void onUpdateDNA() override 46 | { 47 | updateNetwork(); 48 | } 49 | 50 | virtual void process(const std::vector& outputs) = 0; 51 | 52 | Network network; 53 | }; 54 | -------------------------------------------------------------------------------- /include/dna.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include "number_generator.hpp" 7 | 8 | 9 | constexpr float MAX_RANGE = 10.0f; 10 | 11 | 12 | struct DNA 13 | { 14 | using byte = uint8_t; 15 | 16 | DNA(const uint64_t bits_count) 17 | : code(bits_count / 8u + bool(bits_count % 8 && bits_count > 8)) 18 | {} 19 | 20 | template 21 | void initialize(const float range) 22 | { 23 | const uint64_t element_count = getElementsCount(); 24 | for (uint64_t i(element_count); i--;) { 25 | const T value = NumberGenerator<>::getInstance().get(range); 26 | set(i, value); 27 | } 28 | } 29 | 30 | template 31 | T get(const uint64_t offset) const 32 | { 33 | T result; 34 | const uint64_t dna_offset = offset * sizeof(T); 35 | memcpy(&result, &code[dna_offset], sizeof(T)); 36 | return result; 37 | } 38 | 39 | template 40 | void set(const uint64_t offset, const T& value) 41 | { 42 | const float checked_value = clamp(-MAX_RANGE, MAX_RANGE, value); 43 | const uint64_t dna_offset = offset * sizeof(T); 44 | memcpy(&code[dna_offset], &value, sizeof(T)); 45 | } 46 | 47 | uint64_t getBytesCount() const 48 | { 49 | return code.size(); 50 | } 51 | 52 | template 53 | uint64_t getElementsCount() const 54 | { 55 | return code.size() / sizeof(T); 56 | } 57 | 58 | void print() const 59 | { 60 | for (const byte word : code) { 61 | std::cout << std::bitset<8>(word) << ' '; 62 | } 63 | std::cout << std::endl; 64 | } 65 | 66 | void mutateBits(const float probability) 67 | { 68 | for (byte& b : code) { 69 | for (uint64_t i(0); i < 8; ++i) { 70 | if (NumberGenerator<>::getInstance().getUnder(1.0f) < probability) { 71 | const uint8_t mask = 256 >> i; 72 | b ^= mask; 73 | } 74 | } 75 | } 76 | } 77 | 78 | template 79 | void mutate(const float probability) 80 | { 81 | constexpr uint32_t type_size = sizeof(T); 82 | const uint64_t element_count = code.size() / type_size; 83 | for (uint64_t i(0); i < element_count; ++i) { 84 | if (NumberGenerator<>::getInstance().getUnder(1.0f) < probability) { 85 | const T value = NumberGenerator<>::getInstance().get(MAX_RANGE); 86 | set(i, value); 87 | } 88 | } 89 | } 90 | 91 | bool operator==(const DNA& other) const 92 | { 93 | const uint64_t code_length = getBytesCount(); 94 | if (other.getBytesCount() != code_length) { 95 | return false; 96 | } 97 | 98 | for (uint64_t i(0); i < code_length; ++i) { 99 | if (code[i] != other.code[i]) { 100 | return false; 101 | } 102 | } 103 | 104 | return true; 105 | } 106 | 107 | std::vector code; 108 | }; 109 | -------------------------------------------------------------------------------- /include/dna_loader.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include "dna.hpp" 4 | 5 | 6 | struct DnaLoader 7 | { 8 | static std::ifstream::pos_type filesize(const char* filename) 9 | { 10 | std::ifstream in(filename, std::ifstream::ate | std::ifstream::binary); 11 | return in.tellg(); 12 | } 13 | 14 | static uint64_t getDnaCount(const std::string& filename, uint64_t bytes_count) 15 | { 16 | return filesize(filename.c_str()) / bytes_count; 17 | } 18 | 19 | static DNA loadDnaFrom(const std::string& filename, uint64_t bytes_count, uint64_t offset, bool from_end = false) 20 | { 21 | std::ifstream infile(filename, std::ios_base::binary); 22 | 23 | DNA dna(bytes_count * 8); 24 | if (!infile) { 25 | std::cout << "Error when trying to open file." << std::endl; 26 | return dna; 27 | } 28 | 29 | if (!from_end) { 30 | infile.seekg(offset * bytes_count, std::ios_base::beg); 31 | } 32 | else { 33 | infile.seekg(offset * bytes_count, std::ios_base::end); 34 | } 35 | 36 | std::vector test_vec(202); 37 | if (!infile.read((char*)test_vec.data(), bytes_count)) { 38 | std::cout << "Error while reading file." << std::endl; 39 | } 40 | 41 | infile.seekg(offset * bytes_count, std::ios_base::beg); 42 | if (!infile.read((char*)dna.code.data(), bytes_count)) { 43 | std::cout << "Error while reading file." << std::endl; 44 | } 45 | 46 | return dna; 47 | } 48 | 49 | static void writeDnaToFile(const std::string& filename, const DNA& dna) 50 | { 51 | std::ofstream outfile(filename, std::istream::out | std::ios::binary | std::ios::app); 52 | const uint64_t element_count = dna.code.size(); 53 | outfile.write((char*)dna.code.data(), element_count); 54 | outfile.close(); 55 | } 56 | }; 57 | 58 | -------------------------------------------------------------------------------- /include/dna_utils.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "dna.hpp" 3 | 4 | 5 | struct DNAUtils 6 | { 7 | static DNA crossover(const DNA& dna1, const DNA& dna2, const uint64_t cross_point) 8 | { 9 | const uint64_t code_size = dna1.code.size(); 10 | DNA result(code_size * 8); 11 | 12 | for (uint64_t i(0); i < cross_point; ++i) { 13 | result.code[i] = dna1.code[i]; 14 | } 15 | for (uint64_t i(cross_point); i < code_size; ++i) { 16 | result.code[i] = dna2.code[i]; 17 | } 18 | 19 | return result; 20 | } 21 | 22 | template 23 | static DNA makeChild(const DNA& dna1, const DNA& dna2, const float mutation_probability) 24 | { 25 | const uint64_t point1 = getIntUnderNonReset(as(dna1.getBytesCount())); 26 | DNA child_dna = crossover(dna1, dna2, point1); 27 | const uint64_t element_count = dna1.getElementsCount(); 28 | for (uint64_t i(element_count); i--;) { 29 | const float distrib = 1.0f + NumberGenerator<>::getInstance().get(mutation_probability); 30 | child_dna.set(i, child_dna.get(i) * distrib); 31 | } 32 | child_dna.mutate(mutation_probability); 33 | return child_dna; 34 | } 35 | 36 | template 37 | static DNA evolve(const DNA& dna, float mutation_probability, float range) 38 | { 39 | DNA child_dna = dna; 40 | optimize(child_dna, mutation_probability, range); 41 | return child_dna; 42 | } 43 | 44 | template 45 | static void optimize(DNA& dna, float probability, float range) 46 | { 47 | const uint64_t element_count = dna.getElementsCount(); 48 | for (uint64_t i(element_count); i--;) { 49 | if (pass(probability)) { 50 | const T value = dna.get(i); 51 | const T random_offset = NumberGenerator<>::getInstance().get(range * MAX_RANGE); 52 | dna.set(i, value + random_offset); 53 | } 54 | } 55 | } 56 | 57 | static bool pass(float probability) 58 | { 59 | return NumberGenerator<>::getInstance().getUnder(1.0f) < probability; 60 | } 61 | }; 62 | -------------------------------------------------------------------------------- /include/double_buffer.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | template 4 | struct DoubleObject 5 | { 6 | template 7 | DoubleObject(Args&&... args) 8 | : current_buffer(0u) 9 | { 10 | buffers[0] = T(args...); 11 | buffers[1] = T(args...); 12 | } 13 | 14 | void swap() 15 | { 16 | current_buffer = !current_buffer; 17 | } 18 | 19 | T& getCurrent() 20 | { 21 | return buffers[current_buffer]; 22 | } 23 | 24 | T& getLast() 25 | { 26 | return buffers[!current_buffer]; 27 | } 28 | 29 | const T& getCurrent() const 30 | { 31 | return buffers[current_buffer]; 32 | } 33 | 34 | const T& getLast() const 35 | { 36 | return buffers[!current_buffer]; 37 | } 38 | 39 | uint8_t current_buffer; 40 | T buffers[2]; 41 | }; 42 | 43 | 44 | -------------------------------------------------------------------------------- /include/drone_renderer.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include "drone.hpp" 4 | 5 | 6 | struct DroneRenderer 7 | { 8 | sf::Texture flame; 9 | sf::Sprite flame_sprite; 10 | 11 | sf::Texture smoke; 12 | sf::Sprite smoke_sprite; 13 | DroneRenderer() 14 | { 15 | flame.loadFromFile("../flame.png"); 16 | flame_sprite.setTexture(flame); 17 | flame_sprite.setOrigin(118.0f, 67.0f); 18 | flame_sprite.setScale(0.15f, 0.15f); 19 | 20 | smoke.loadFromFile("../smoke.png"); 21 | smoke_sprite.setTexture(smoke); 22 | smoke_sprite.setOrigin(126, 134); 23 | } 24 | 25 | void draw(const Drone::Thruster& thruster, const Drone& drone, sf::RenderTarget& target, sf::Color color, bool right, const sf::RenderStates& state) 26 | { 27 | const float offset_dir = (right ? 1.0f : -1.0f); 28 | 29 | const float thruster_width = 14.0f; 30 | const float thruster_height = 32.0f; 31 | const float ca = cos(drone.angle); 32 | const float sa = sin(drone.angle); 33 | const float angle = drone.angle + offset_dir * thruster.angle; 34 | const float ca_left = cos(angle + HalfPI); 35 | const float sa_left = sin(angle + HalfPI); 36 | const sf::Vector2f drone_dir = sf::Vector2f(ca, sa); 37 | const sf::Vector2f drone_normal = sf::Vector2f(-sa, ca); 38 | const sf::Vector2f thruster_dir = sf::Vector2f(ca_left, sa_left); 39 | const sf::Vector2f thruster_normal = sf::Vector2f(-sa_left, ca_left); 40 | sf::Vector2f position = drone.position + offset_dir * (drone.radius + drone.thruster_offset) * drone_dir; 41 | 42 | // Draw piston 43 | const sf::Vector2f on_thruster_position = position + (offset_dir * thruster_width * 0.4f) * thruster_normal - 7.0f * thruster_dir; 44 | const sf::Vector2f on_body_position = drone.position + 20.0f * offset_dir * drone_dir - 12.0f * drone_normal; 45 | sf::Color push_color(100, 100, 100); 46 | const sf::Vector2f body_to_thruster = on_thruster_position - on_body_position; 47 | const float push_length = getLength(body_to_thruster); 48 | const float push_angle = getAngle(body_to_thruster) + PI; 49 | const float push_width = 22.0f; 50 | const float push_height = 4.0f; 51 | sf::RectangleShape push(sf::Vector2f(1.0f, push_height)); 52 | push.setOrigin(1.0f, push_height * 0.5f); 53 | push.setScale(push_width, 1.0f); 54 | push.setPosition(on_body_position); 55 | push.setRotation(push_angle * RAD_TO_DEG); 56 | push.setFillColor(push_color); 57 | target.draw(push, state); 58 | push.setScale(push_length, 0.5f); 59 | target.draw(push, state); 60 | 61 | const sf::Vector2f on_thruster_position_2 = position + (offset_dir * thruster_width * 0.4f) * thruster_normal + 8.0f * thruster_dir; 62 | const sf::Vector2f on_body_position_2 = drone.position + 10.0f * offset_dir * drone_dir + 10.0f * drone_normal; 63 | const sf::Vector2f body_to_thruster_2 = on_thruster_position_2 - on_body_position_2; 64 | const float push_length_2 = getLength(body_to_thruster_2); 65 | const float push_angle_2 = getAngle(body_to_thruster_2) + PI; 66 | 67 | push.setScale(push_width * 1.25f, 1.0f); 68 | push.setPosition(on_body_position_2); 69 | push.setRotation(push_angle_2 * RAD_TO_DEG); 70 | target.draw(push, state); 71 | push.setScale(push_length_2, 0.5f); 72 | target.draw(push, state); 73 | 74 | // Draw flame 75 | const float rand_pulse_left = (1.0f + rand() % 10 * 0.05f); 76 | const float v_scale_left = thruster.power_ratio * rand_pulse_left; 77 | flame_sprite.setPosition(position + 0.5f * thruster_height * thruster_dir); 78 | flame_sprite.setScale(0.15f * thruster.power_ratio * rand_pulse_left, 0.15f * v_scale_left); 79 | flame_sprite.setRotation(RAD_TO_DEG * angle); 80 | target.draw(flame_sprite, state); 81 | 82 | sf::RectangleShape thruster_body(sf::Vector2f(thruster_width, thruster_height)); 83 | thruster_body.setOrigin(thruster_width * 0.5f, thruster_height * 0.5f); 84 | thruster_body.setFillColor(color); 85 | thruster_body.setPosition(position); 86 | thruster_body.setRotation(RAD_TO_DEG * angle); 87 | target.draw(thruster_body, state); 88 | 89 | const float margin = 2.0f; 90 | const float width = thruster_width * 0.5f; 91 | const float height = (thruster_height - 11.0f * margin) * 0.1f; 92 | sf::RectangleShape power_indicator(sf::Vector2f(width, height)); 93 | power_indicator.setFillColor(sf::Color::Green); 94 | power_indicator.setOrigin(width * 0.5f, height); 95 | power_indicator.setRotation(angle * RAD_TO_DEG); 96 | const uint8_t power_percent = thruster.power_ratio * 10; 97 | sf::Vector2f power_start(position + (0.5f * thruster_height - margin) * thruster_dir); 98 | for (uint8_t i(0); i < power_percent; ++i) { 99 | power_indicator.setPosition(power_start - float(i) * (height + margin) * thruster_dir); 100 | target.draw(power_indicator, state); 101 | } 102 | } 103 | 104 | void draw(const Drone& drone, sf::RenderTarget& target, const sf::RenderStates& state, sf::Color color = sf::Color::White, bool draw_smoke = true) 105 | { 106 | // Draw body 107 | const float drone_width = drone.radius + drone.thruster_offset; 108 | sf::RectangleShape lat(sf::Vector2f(2.0f * drone_width, 6.0f)); 109 | lat.setOrigin(drone_width, 2.0f); 110 | lat.setPosition(drone.position); 111 | lat.setRotation(RAD_TO_DEG * drone.angle); 112 | lat.setFillColor(sf::Color(70, 70, 70)); 113 | target.draw(lat, state); 114 | 115 | draw(drone.left, drone, target, color, false, state); 116 | draw(drone.right, drone, target, color, true, state); 117 | drawBody(drone, color, target, state); 118 | } 119 | 120 | sf::Color getRedGreenRatio(float ratio) const 121 | { 122 | const float r = std::min(1.0f, std::abs(ratio)); 123 | return sf::Color(255 * r, 255 * (1.0f - r), 0); 124 | } 125 | 126 | void drawBody(const Drone& drone, const sf::Color& color, sf::RenderTarget& target, const sf::RenderStates& state) 127 | { 128 | const float angle_ratio = std::min(1.0f, std::abs(sin(drone.angle))); 129 | const sf::Color eye_color = getRedGreenRatio(angle_ratio); 130 | 131 | const float r = drone.radius * 1.25f; 132 | const uint32_t quality = 24; 133 | sf::VertexArray va(sf::TriangleFan, quality + 3); 134 | const float da = 2.0f * PI / float(quality); 135 | va[0].position = drone.position; 136 | va[0].color = eye_color; 137 | for (uint32_t i(1); i < quality / 2 + 2; ++i) { 138 | const float angle = drone.angle - (i - 1) * da; 139 | va[i].position = drone.position + r * sf::Vector2f(cos(angle), sin(angle)); 140 | va[i].color = color; 141 | } 142 | for (uint32_t i(quality / 2 + 1); i < quality + 2; ++i) { 143 | const float angle = drone.angle - (i - 1) * da; 144 | va[i + 1].position = drone.position + 0.65f * r * sf::Vector2f(cos(angle), sin(angle)); 145 | va[i + 1].color = sf::Color(120, 120, 120); 146 | } 147 | 148 | target.draw(va, state); 149 | 150 | const float inner_r = drone.radius * 0.35f; 151 | sf::CircleShape c(inner_r); 152 | c.setOrigin(inner_r, inner_r); 153 | c.setPosition(drone.position); 154 | c.setFillColor(eye_color); 155 | target.draw(c, state); 156 | 157 | const float led_size = 2.0f; 158 | const float led_offset = 12.0f; 159 | sf::CircleShape c_led(led_size); 160 | c_led.setOrigin(led_size + led_offset, led_size); 161 | c_led.setRotation(drone.angle * RAD_TO_DEG); 162 | c_led.setPosition(drone.position); 163 | c_led.setFillColor(getRedGreenRatio(drone.left.angle_ratio)); 164 | target.draw(c_led, state); 165 | 166 | c_led.setOrigin(led_size - led_offset, led_size); 167 | c_led.setFillColor(getRedGreenRatio(drone.right.angle_ratio)); 168 | target.draw(c_led, state); 169 | 170 | c_led.setOrigin(led_size, led_size - 0.45f * r); 171 | c_led.setFillColor(getRedGreenRatio(1.0f - drone.network.last_input[0])); 172 | target.draw(c_led, state); 173 | 174 | c_led.setOrigin(led_size, led_size + 0.5f * r); 175 | c_led.setFillColor(getRedGreenRatio(1.0f - drone.network.last_input[1])); 176 | target.draw(c_led, state); 177 | } 178 | }; 179 | -------------------------------------------------------------------------------- /include/drone_ui.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include "drone.hpp" 4 | 5 | 6 | struct ThrusterUI : sf::Drawable 7 | { 8 | // Link to the underlying objects 9 | const Drone::Thruster& thruster; 10 | // The sprite provided by the renderer 11 | sf::Texture& flame; 12 | // Attributes 13 | float width = 14.0f; 14 | float height = 32.0f; 15 | 16 | sf::VertexArray va; 17 | 18 | ThrusterUI(const Drone::Thruster& thruster_, sf::Texture& flame_) 19 | : thruster(thruster_) 20 | , flame(flame_) 21 | , va(sf::Quads, 0) 22 | {} 23 | 24 | void draw(sf::RenderTarget& target, sf::RenderStates states) const override 25 | { 26 | // Draw flame 27 | //const float rand_pulse_left = getFastRandUnder(0.5f); //(1.0f + rand() % 10 * 0.05f); 28 | //const float v_scale_left = thruster.power_ratio * rand_pulse_left; 29 | ////flame_sprite.setScale(0.15f * thruster.power_ratio * rand_pulse_left, 0.15f * v_scale_left); 30 | //va.append(sf::Vertex(sf::Vector2f(-width * 0.5f, height * 0.5f), )) 31 | 32 | //sf::RectangleShape thruster_body(sf::Vector2f(thruster_width, thruster_height)); 33 | //thruster_body.setOrigin(thruster_width * 0.5f, thruster_height * 0.5f); 34 | //thruster_body.setFillColor(color); 35 | //thruster_body.setPosition(position); 36 | //thruster_body.setRotation(RAD_TO_DEG * angle); 37 | //target.draw(thruster_body, state); 38 | 39 | //const float margin = 2.0f; 40 | //const float width = thruster_width * 0.5f; 41 | //const float height = (thruster_height - 11.0f * margin) * 0.1f; 42 | //sf::RectangleShape power_indicator(sf::Vector2f(width, height)); 43 | //power_indicator.setFillColor(sf::Color::Green); 44 | //power_indicator.setOrigin(width * 0.5f, height); 45 | //power_indicator.setRotation(angle * RAD_TO_DEG); 46 | //const uint8_t power_percent = thruster.power_ratio * 10; 47 | //sf::Vector2f power_start(position + (0.5f * thruster_height - margin) * thruster_dir); 48 | //for (uint8_t i(0); i < power_percent; ++i) { 49 | // power_indicator.setPosition(power_start - float(i) * (height + margin) * thruster_dir); 50 | // target.draw(power_indicator, state); 51 | //} 52 | } 53 | }; 54 | 55 | struct DroneUI 56 | { 57 | 58 | }; 59 | -------------------------------------------------------------------------------- /include/gauge_bar.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | 4 | 5 | struct GaugeBar 6 | { 7 | float max_value; 8 | float value; 9 | 10 | sf::Vector2f size; 11 | sf::Vector2f position; 12 | 13 | GaugeBar() 14 | : max_value(0.0f) 15 | , value(0.0f) 16 | {} 17 | 18 | GaugeBar(float max, sf::Vector2f pos, sf::Vector2f size_) 19 | : max_value(max) 20 | , value(0.0f) 21 | , position(pos) 22 | , size(size_) 23 | {} 24 | 25 | void setValue(float v) 26 | { 27 | value = v; 28 | } 29 | 30 | void render(sf::RenderTarget& target, sf::RenderStates states) const 31 | { 32 | sf::RectangleShape border(size); 33 | border.setPosition(position); 34 | target.draw(border, states); 35 | 36 | const sf::Vector2f padding(1.0f, 1.0f); 37 | sf::RectangleShape inner(size - 2.0f * padding); 38 | inner.setPosition(position + padding); 39 | inner.setFillColor(sf::Color::Black); 40 | target.draw(inner, states); 41 | 42 | const float bar_width = (size.x - 4.0f) * (value / max_value); 43 | sf::RectangleShape bar(sf::Vector2f(bar_width, size.y - 4.0f)); 44 | bar.setPosition(position + 2.0f * padding); 45 | target.draw(bar, states); 46 | } 47 | }; 48 | 49 | -------------------------------------------------------------------------------- /include/graph.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | 4 | 5 | struct Graphic 6 | { 7 | sf::VertexArray va; 8 | std::vector values; 9 | float max_value; 10 | float width; 11 | float height; 12 | float x, y; 13 | 14 | sf::Color color; 15 | 16 | uint32_t current_index; 17 | bool full; 18 | 19 | Graphic(uint32_t values_count, sf::Vector2f size, sf::Vector2f position) 20 | : va(sf::Quads, values_count * 4) 21 | , values(values_count, 0.0f) 22 | , max_value(0.0f) 23 | , width(size.x) 24 | , height(size.y) 25 | , x(position.x) 26 | , y(position.y) 27 | , color(sf::Color::White) 28 | , current_index(0) 29 | , full(false) 30 | { 31 | } 32 | 33 | void addValue(float value) 34 | { 35 | const uint64_t size = values.size(); 36 | values[(current_index++) % size] = value; 37 | max_value = std::max(max_value, value); 38 | if (current_index == size) { 39 | full = true; 40 | } 41 | } 42 | 43 | void next() 44 | { 45 | ++current_index; 46 | if (current_index == values.size()) { 47 | full = true; 48 | current_index = 0; 49 | } 50 | } 51 | 52 | void setLastValue(float value) 53 | { 54 | const uint64_t size = values.size(); 55 | values[current_index % size] = value; 56 | max_value = std::max(max_value, value); 57 | } 58 | 59 | void render(sf::RenderTarget& target) 60 | { 61 | const uint64_t size = values.size(); 62 | const float bw = width / float(size); 63 | const float hf = height / max_value; 64 | for (uint64_t i(0); i < size; ++i) { 65 | const uint64_t index = full ? (current_index + 1 + i) % size : i; 66 | const float bh = values[index] * hf; 67 | va[4 * i + 0].position = sf::Vector2f(x + i * bw , y + height); 68 | va[4 * i + 1].position = sf::Vector2f(x + (i + 1) * bw, y + height); 69 | va[4 * i + 2].position = sf::Vector2f(x + (i + 1) * bw, y + height - bh); 70 | va[4 * i + 3].position = sf::Vector2f(x + i * bw , y + height - bh); 71 | 72 | va[4 * i + 0].color = color; 73 | va[4 * i + 1].color = color; 74 | va[4 * i + 2].color = color; 75 | va[4 * i + 3].color = color; 76 | } 77 | 78 | target.draw(va); 79 | } 80 | }; 81 | 82 | 83 | -------------------------------------------------------------------------------- /include/moving_average.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | 5 | struct MovingAverage 6 | { 7 | uint32_t over; 8 | std::vector values; 9 | uint32_t index; 10 | float sum; 11 | 12 | MovingAverage(uint32_t count) 13 | : over(count) 14 | , values(count, 0.0f) 15 | , index(0) 16 | , sum(0.0f) 17 | {} 18 | 19 | void addValue(float f) 20 | { 21 | if (index >= over) { 22 | sum -= values[index%over]; 23 | } 24 | values[index%over] = f; 25 | sum += f; 26 | ++index; 27 | } 28 | 29 | float get() const { 30 | return sum / float(over); 31 | } 32 | }; -------------------------------------------------------------------------------- /include/neural_network.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include "utils.hpp" 5 | 6 | 7 | struct Layer 8 | { 9 | Layer(const uint64_t neurons_count, const uint64_t prev_count) 10 | : weights(neurons_count) 11 | , values(neurons_count) 12 | , bias(neurons_count) 13 | { 14 | for (std::vector& v : weights) { 15 | v.resize(prev_count); 16 | } 17 | } 18 | 19 | uint64_t getNeuronsCount() const 20 | { 21 | return bias.size(); 22 | } 23 | 24 | uint64_t getWeightsCount() const 25 | { 26 | return weights.front().size(); 27 | } 28 | 29 | void process(const std::vector& inputs) 30 | { 31 | const uint64_t neurons_count = bias.size(); 32 | const uint64_t inputs_count = inputs.size(); 33 | // For each neuron 34 | for (uint64_t i(0); i < neurons_count; ++i) { 35 | float result = bias[i]; 36 | // Compute weighted sum of inputs 37 | for (uint64_t j(0); j < inputs_count; ++j) { 38 | result += weights[i][j] * inputs[j]; 39 | } 40 | // Output result 41 | values[i] = tanh(result); 42 | } 43 | } 44 | 45 | void print() const 46 | { 47 | std::cout << "--- layer ---" << std::endl; 48 | const uint64_t neurons_count = values.size(); 49 | for (uint64_t i(0); i < neurons_count; ++i) { 50 | const uint64_t inputs_count = getWeightsCount(); 51 | // Compute weighted sum of inputs 52 | std::cout << "Neuron " << i << " bias " << bias[i] << std::endl; 53 | for (uint64_t j(0); j < inputs_count; ++j) { 54 | std::cout << weights[i][j] << " "; 55 | } 56 | std::cout << std::endl; 57 | } 58 | std::cout << "--- end ---\n" << std::endl; 59 | } 60 | 61 | std::vector> weights; 62 | std::vector values; 63 | std::vector bias; 64 | }; 65 | 66 | 67 | struct Network 68 | { 69 | Network() 70 | : input_size(0) 71 | , last_input(0) 72 | {} 73 | 74 | Network(const uint64_t input_size_) 75 | : input_size(input_size_) 76 | , last_input(input_size_) 77 | {} 78 | 79 | Network(const std::vector& layers_sizes) 80 | : input_size(layers_sizes[0]) 81 | , last_input(input_size) 82 | { 83 | for (uint64_t i(1); i < layers_sizes.size(); ++i) { 84 | addLayer(layers_sizes[i]); 85 | } 86 | } 87 | 88 | void addLayer(const uint64_t neurons_count) 89 | { 90 | if (!layers.empty()) { 91 | layers.emplace_back(neurons_count, layers.back().getNeuronsCount()); 92 | } 93 | else { 94 | layers.emplace_back(neurons_count, input_size); 95 | } 96 | } 97 | 98 | const std::vector& execute(const std::vector& input) 99 | { 100 | last_input = input; 101 | if (input.size() == input_size) { 102 | layers.front().process(input); 103 | const uint64_t layers_count = layers.size(); 104 | for (uint64_t i(1); i < layers_count; ++i) { 105 | layers[i].process(layers[i - 1].values); 106 | } 107 | } 108 | 109 | return layers.back().values; 110 | } 111 | 112 | uint64_t getParametersCount() const 113 | { 114 | uint64_t result = 0; 115 | for (const Layer& layer : layers) { 116 | result += layer.bias.size() * (1 + layer.weights.front().size()); 117 | } 118 | return result; 119 | } 120 | 121 | static uint64_t getParametersCount(const std::vector& layers_sizes) 122 | { 123 | uint64_t count = 0; 124 | for (uint64_t i(1); i < layers_sizes.size(); ++i) { 125 | count += layers_sizes[i] * (1 + layers_sizes[i - 1]); 126 | } 127 | return count; 128 | } 129 | 130 | uint64_t input_size; 131 | std::vector layers; 132 | std::vector last_input; 133 | }; 134 | -------------------------------------------------------------------------------- /include/neural_renderer.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "neural_network.hpp" 4 | #include "utils.hpp" 5 | 6 | 7 | struct GLayer 8 | { 9 | std::vector neurons_positions; 10 | }; 11 | 12 | 13 | struct NeuralRenderer 14 | { 15 | void render(sf::RenderTarget& target, Network& network, sf::RenderStates states) 16 | { 17 | updateLayers(network); 18 | 19 | const uint64_t layers_count = layers.size(); 20 | for (uint64_t i(1); i max_layer_height) { 104 | max_layer_height = layer_height; 105 | } 106 | } 107 | 108 | float layer_x = position.x; 109 | float layer_height = getLayerHeight(network.input_size); 110 | float neuron_y = position.y + 0.5f * (max_layer_height - layer_height) + neuron_radius; 111 | // Draw inputs 112 | layers.emplace_back(); 113 | for (uint64_t i(0); i < network.input_size; ++i) { 114 | layers.back().neurons_positions.push_back(sf::Vector2f(layer_x, neuron_y)); 115 | neuron_y += 2.0f * neuron_radius + neuron_spacing; 116 | } 117 | layer_x += 2.0f * neuron_radius + layer_spacing; 118 | // Draw layers 119 | for (const Layer& layer : network.layers) { 120 | layers.emplace_back(); 121 | layer_height = getLayerHeight(layer.getNeuronsCount()); 122 | neuron_y = position.y + 0.5f * (max_layer_height - layer_height) + neuron_radius; 123 | for (uint32_t i(0); i < layer.getNeuronsCount(); ++i) { 124 | layers.back().neurons_positions.push_back(sf::Vector2f(layer_x, neuron_y)); 125 | neuron_y += 2.0f * neuron_radius + neuron_spacing; 126 | } 127 | 128 | layer_x += 2.0f * neuron_radius + layer_spacing; 129 | } 130 | } 131 | 132 | float neuron_radius = 20.0f; 133 | float neuron_spacing = 8.0f; 134 | float layer_spacing = 60.0f; 135 | sf::Vector2f position; 136 | std::vector layers; 137 | }; 138 | -------------------------------------------------------------------------------- /include/number_generator.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | 8 | template 9 | struct NumberGenerator 10 | { 11 | NumberGenerator(bool random_seed = true) 12 | : distribution(-1.0f, 1.0f) 13 | { 14 | if (random_seed) { 15 | gen = std::mt19937(rd()); 16 | } 17 | else { 18 | gen = std::mt19937(0); 19 | } 20 | } 21 | 22 | float get(float range = 1.0f) 23 | { 24 | return range * distribution(gen); 25 | } 26 | 27 | float getUnder(float max_value) 28 | { 29 | return (distribution(gen) + 1.0f) * 0.5f * max_value; 30 | } 31 | 32 | float getMaxRange() 33 | { 34 | return std::numeric_limits::max() * distribution(gen); 35 | } 36 | 37 | void reset() 38 | { 39 | // TODO add random seed case 40 | gen = std::mt19937(rd()); 41 | distribution.reset(); 42 | } 43 | 44 | static NumberGenerator& getInstance() 45 | { 46 | return *s_instance; 47 | } 48 | 49 | static void initialize() 50 | { 51 | s_instance = std::make_unique(true); 52 | } 53 | 54 | std::uniform_real_distribution distribution; 55 | std::random_device rd; 56 | std::mt19937 gen; 57 | 58 | static std::unique_ptr s_instance; 59 | }; 60 | 61 | 62 | template 63 | std::unique_ptr> NumberGenerator::s_instance; 64 | 65 | -------------------------------------------------------------------------------- /include/objective.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | 5 | 6 | struct Objective 7 | { 8 | uint32_t target_id; 9 | float time_in; 10 | float time_out; 11 | float points; 12 | 13 | Objective() 14 | {} 15 | 16 | void reset() 17 | { 18 | target_id = 0; 19 | time_in = 0.0f; 20 | time_out = 0.0f; 21 | points = 0.0f; 22 | } 23 | 24 | template 25 | const T& getTarget(const std::vector& targets) 26 | { 27 | return targets[target_id]; 28 | } 29 | 30 | template 31 | void nextTarget(const std::vector& targets) 32 | { 33 | target_id = (target_id + 1) % targets.size(); 34 | time_in = 0.0f; 35 | time_out = 0.0f; 36 | } 37 | 38 | void addTimeIn(float dt) 39 | { 40 | time_in += dt; 41 | } 42 | 43 | void addTimeOut(float dt) 44 | { 45 | time_in = 0.0f; 46 | time_out += dt; 47 | } 48 | }; 49 | -------------------------------------------------------------------------------- /include/rocket.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "ai_unit.hpp" 3 | #include "smoke.hpp" 4 | #include 5 | #include "moving_average.hpp" 6 | 7 | 8 | std::vector architecture{ 7, 9, 9, 2 }; 9 | 10 | struct Rocket : AiUnit 11 | { 12 | struct Thruster 13 | { 14 | float max_angle; 15 | float angle; 16 | float target_angle; 17 | 18 | float max_power; 19 | float power; 20 | 21 | MovingAverage avg_power; 22 | MovingAverage avg_angle; 23 | 24 | Thruster() 25 | : max_power(3000.0f) 26 | , max_angle(HalfPI) 27 | , avg_power(60) 28 | , avg_angle(60) 29 | {} 30 | 31 | void update(float dt) 32 | { 33 | const float angle_speed = 1.0f; 34 | angle += angle_speed * dt * (target_angle - angle); 35 | avg_angle.addValue(angle); 36 | } 37 | 38 | float getNormAngle() const 39 | { 40 | return angle / max_angle; 41 | } 42 | 43 | void setAngle(float a) 44 | { 45 | target_angle = a * max_angle; 46 | } 47 | 48 | void setPower(float p) 49 | { 50 | power = p; 51 | avg_power.addValue(p); 52 | } 53 | 54 | float getPower() const 55 | { 56 | return power * max_power; 57 | } 58 | 59 | float getAvgPowerRatio() const 60 | { 61 | return avg_power.get(); 62 | } 63 | 64 | float getAvgAngle() const 65 | { 66 | return avg_angle.get(); 67 | } 68 | 69 | void reset() 70 | { 71 | power = 0.0f; 72 | angle = 0.0f; 73 | target_angle = 0.0f; 74 | } 75 | }; 76 | 77 | Thruster thruster; 78 | sf::Vector2f position; 79 | sf::Vector2f velocity; 80 | float angle; 81 | float angular_velocity; 82 | float height; 83 | uint32_t index; 84 | float last_power; 85 | std::list smoke; 86 | float time; 87 | bool stop; 88 | bool take_off; 89 | 90 | Rocket() 91 | : AiUnit(architecture) 92 | , height(120.0f) 93 | , last_power(0.0f) 94 | { 95 | reset(); 96 | } 97 | 98 | void reset() 99 | { 100 | velocity = sf::Vector2f(0.0f, 0.0f); 101 | angle = HalfPI; 102 | angular_velocity = 0.0f; 103 | thruster.reset(); 104 | alive = true; 105 | fitness = 0.0f; 106 | time = 0.0f; 107 | stop = false; 108 | } 109 | 110 | sf::Vector2f getThrust() const 111 | { 112 | const float thruster_angle = angle + thruster.angle; 113 | return -thruster.getPower() * sf::Vector2f(cos(thruster_angle), sin(thruster_angle)); 114 | } 115 | 116 | static float cross(sf::Vector2f v1, sf::Vector2f v2) 117 | { 118 | return v1.x * v2.y - v1.y * v2.x; 119 | } 120 | 121 | float getTorque() const 122 | { 123 | const sf::Vector2f v(cos(thruster.angle), sin(thruster.angle)); 124 | const float inertia_coef = 0.8f; 125 | return thruster.getPower() / (0.5f * height) * cross(v, sf::Vector2f(1.0f, 0.0f)); 126 | } 127 | 128 | void update(float dt, bool update_smoke) 129 | { 130 | thruster.update(dt); 131 | const sf::Vector2f gravity(0.0f, 1000.0f); 132 | velocity += (gravity + getThrust()) * dt; 133 | position += velocity * dt; 134 | 135 | angular_velocity += getTorque() * dt; 136 | angle += angular_velocity * dt; 137 | 138 | if (update_smoke) { 139 | const sf::Vector2f rocket_dir(cos(angle), sin(angle)); 140 | const float smoke_vert_offset = 40.0f; 141 | const float smoke_duration = 0.5f; 142 | const float smoke_speed_coef = 0.25f; 143 | const float power_ratio = 4.0f * thruster.getAvgPowerRatio(); 144 | if (power_ratio > 0.15f) { 145 | const float power = thruster.max_power * power_ratio; 146 | const float thruster_angle = angle + thruster.angle; 147 | const sf::Vector2f thruster_direction(cos(thruster_angle), sin(thruster_angle)); 148 | const sf::Vector2f thruster_pos = position + rocket_dir * height * 0.5f + thruster_direction * smoke_vert_offset * power_ratio; 149 | 150 | smoke.push_back(Smoke(thruster_pos, thruster_direction, smoke_speed_coef * power, 0.15f + 0.5f * power_ratio, smoke_duration * power_ratio)); 151 | } 152 | 153 | for (Smoke& s : smoke) { 154 | s.update(dt); 155 | } 156 | 157 | smoke.remove_if([this](const Smoke& s) { return s.done(); }); 158 | } 159 | 160 | time += dt; 161 | } 162 | 163 | void process(const std::vector& outputs) override 164 | { 165 | last_power = thruster.power; 166 | thruster.setPower(0.5f * (outputs[0] + 1.0f)); 167 | thruster.setAngle(outputs[1]); 168 | } 169 | }; 170 | -------------------------------------------------------------------------------- /include/rocket_renderer.hpp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johnBuffer/AutoRocket/1e82ae0e379d8e0d0e469c3c89e68ffa46a2803c/include/rocket_renderer.hpp -------------------------------------------------------------------------------- /include/selection_wheel.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include "utils.hpp" 5 | #include "number_generator.hpp" 6 | 7 | 8 | struct SelectionWheel 9 | { 10 | SelectionWheel(const uint64_t pop_size) 11 | : population_size(pop_size) 12 | , fitness_acc(pop_size + 1) 13 | , current_index(0) 14 | { 15 | fitness_acc[0] = 0.0f; 16 | } 17 | 18 | void reset() 19 | { 20 | current_index = 0; 21 | } 22 | 23 | void addFitnessScore(float score) 24 | { 25 | fitness_acc[current_index + 1] = fitness_acc[current_index] + score; 26 | ++current_index; 27 | } 28 | 29 | template 30 | void addFitnessScores(std::vector& pop) 31 | { 32 | reset(); 33 | const uint64_t count = std::min(population_size, pop.size()); 34 | for (uint64_t i(0); i < count; ++i) { 35 | addFitnessScore(pop[i].fitness); 36 | } 37 | } 38 | 39 | float getAverageFitness() const 40 | { 41 | return fitness_acc.back() / float(population_size); 42 | } 43 | 44 | int64_t findClosestValueUnder(float f) const 45 | { 46 | int64_t b_inf = 0; 47 | int64_t b_sup = population_size; 48 | 49 | do { 50 | const int64_t index = (b_inf + b_sup) >> 1; 51 | if (fitness_acc[index] < f) { 52 | b_inf = index; 53 | } 54 | else { 55 | b_sup = index; 56 | } 57 | } while (b_inf < b_sup - 1); 58 | 59 | return (b_inf + b_sup) >> 1; 60 | } 61 | 62 | int64_t pickTest(float value) 63 | { 64 | int64_t result = population_size - 1; 65 | for (uint64_t i(1); i < population_size + 1; ++i) { 66 | if (fitness_acc[i] > value) { 67 | result = i - 1; 68 | break; 69 | } 70 | } 71 | 72 | return result; 73 | } 74 | 75 | template 76 | const T& pick(const std::vector& population, uint64_t* index = nullptr) 77 | { 78 | const float pick_value = NumberGenerator<>::getInstance().getUnder(fitness_acc.back()); 79 | uint64_t picked_index = pickTest(pick_value); 80 | 81 | if (index) { 82 | *index = picked_index; 83 | } 84 | 85 | return population[picked_index]; 86 | } 87 | 88 | const uint64_t population_size; 89 | std::vector fitness_acc; 90 | uint64_t current_index; 91 | }; 92 | 93 | -------------------------------------------------------------------------------- /include/selector.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "dna_utils.hpp" 3 | #include "neural_network.hpp" 4 | #include "selection_wheel.hpp" 5 | #include "unit.hpp" 6 | #include "double_buffer.hpp" 7 | #include 8 | #include 9 | #include "dna_loader.hpp" 10 | 11 | 12 | const float population_elite_ratio = 0.05f; 13 | const float population_conservation_ratio = 0.25f; 14 | 15 | 16 | template 17 | struct Selector 18 | { 19 | const uint32_t population_size; 20 | const uint32_t survivings_count; 21 | const uint32_t elites_count; 22 | DoubleObject> population; 23 | SelectionWheel wheel; 24 | std::string out_file; 25 | uint32_t dump_frequency = 10; 26 | uint32_t current_iteration; 27 | 28 | Selector(const uint32_t agents_count) 29 | : population(agents_count) 30 | , population_size(agents_count) 31 | , current_iteration(0) 32 | , survivings_count(as(agents_count * population_conservation_ratio)) 33 | , elites_count(as(agents_count * population_elite_ratio)) 34 | , wheel(survivings_count) 35 | { 36 | const std::string base_filename = "../selector_output"; 37 | std::string filename = base_filename + ".bin"; 38 | std::ifstream ifs(filename); 39 | uint32_t try_count = 0; 40 | while (ifs) { 41 | ifs.close(); 42 | ++try_count; 43 | std::stringstream sstr; 44 | sstr << base_filename << "_" << try_count << ".bin"; 45 | filename = sstr.str(); 46 | ifs.open(filename); 47 | } 48 | ifs.close(); 49 | out_file = filename; 50 | 51 | std::cout << "Writing dumps in " << filename << std::endl; 52 | } 53 | 54 | void nextGeneration() 55 | { 56 | // Create selection wheel 57 | sortCurrentPopulation(); 58 | std::vector& current_units = population.getCurrent(); 59 | std::vector& next_units = population.getLast(); 60 | wheel.addFitnessScores(current_units); 61 | // Replace the weakest 62 | std::cout << "Gen: " << current_iteration << " Best: " << current_units[0].fitness << std::endl; 63 | if ((current_iteration%dump_frequency) == 0) { 64 | DnaLoader::writeDnaToFile(out_file, getCurrentPopulation()[0].dna); 65 | } 66 | 67 | // The top best survive; 68 | uint32_t evolve_count = 0; 69 | for (uint32_t i(0); i < elites_count; ++i) { 70 | next_units[i] = current_units[i]; 71 | } 72 | for (uint32_t i(elites_count); i < population_size; ++i) { 73 | const T& unit_1 = wheel.pick(current_units); 74 | const T& unit_2 = wheel.pick(current_units); 75 | const float mutation_proba = 1.0f / sqrt(unit_1.fitness + unit_2.fitness); 76 | if (unit_1.dna == unit_2.dna) { 77 | ++evolve_count; 78 | next_units[i].loadDNA(DNAUtils::evolve(unit_1.dna, mutation_proba, mutation_proba)); 79 | } 80 | else { 81 | next_units[i].loadDNA(DNAUtils::makeChild(unit_1.dna, unit_2.dna, mutation_proba)); 82 | } 83 | } 84 | 85 | switchPopulation(); 86 | } 87 | 88 | void sortCurrentPopulation() 89 | { 90 | std::vector& current_units = population.getCurrent(); 91 | std::sort(current_units.begin(), current_units.end(), [&](const T& a1, const T& a2) {return a1.fitness > a2.fitness; }); 92 | } 93 | 94 | const T& getBest() const 95 | { 96 | return getNextPopulation()[0]; 97 | } 98 | 99 | void switchPopulation() 100 | { 101 | population.swap(); 102 | ++current_iteration; 103 | } 104 | 105 | std::vector& getCurrentPopulation() 106 | { 107 | return population.getCurrent(); 108 | } 109 | 110 | const std::vector& getCurrentPopulation() const 111 | { 112 | return population.getCurrent(); 113 | } 114 | 115 | std::vector& getNextPopulation() 116 | { 117 | return population.getLast(); 118 | } 119 | 120 | const std::vector& getNextPopulation() const 121 | { 122 | return population.getLast(); 123 | } 124 | }; 125 | 126 | -------------------------------------------------------------------------------- /include/smoke.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include "utils.hpp" 5 | 6 | 7 | struct Smoke 8 | { 9 | sf::Vector2f position; 10 | sf::Vector2f direction; 11 | float speed; 12 | float angle; 13 | float scale; 14 | float max_lifetime; 15 | float lifetime; 16 | float angle_var; 17 | bool hit; 18 | 19 | Smoke(sf::Vector2f pos, sf::Vector2f dir, float speed_, float scale_, float duration) 20 | : position(pos) 21 | , direction(dir) 22 | , speed(speed_) 23 | , angle(getFastRandUnder(2.0f * PI)) 24 | , scale(0.25f + scale_ * (0.25f + getFastRandUnder(1.0f))) 25 | , max_lifetime(duration) 26 | , lifetime(0.0f) 27 | , angle_var(1.0f * (getFastRandUnder(2.0f) - 1.0f)) 28 | , hit(false) 29 | { 30 | } 31 | 32 | float getRatio() const 33 | { 34 | return lifetime / max_lifetime; 35 | } 36 | 37 | bool done() const 38 | { 39 | return lifetime >= max_lifetime; 40 | } 41 | 42 | void update(float dt) 43 | { 44 | const float ratio = getRatio(); 45 | const float inv_ratio = (1.0f - ratio); 46 | lifetime += dt; 47 | angle += angle_var * dt; 48 | scale *= 1.0f + 2.5f * dt; 49 | position += (speed * direction * dt) / scale; 50 | 51 | if (!hit && position.y > 990.0f) { 52 | hit = true; 53 | const float old_dir_x = direction.x; 54 | const float new_dir_x = getFastRandUnder(1.0f) > 0.5f ? -1.0f : 1.0f; 55 | position.y = 990.0f; 56 | direction.x = new_dir_x * direction.y; 57 | direction.y = -getFastRandUnder(scale * 0.5f); 58 | } 59 | } 60 | }; 61 | -------------------------------------------------------------------------------- /include/stadium.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include "selector.hpp" 6 | #include "rocket.hpp" 7 | #include "objective.hpp" 8 | 9 | 10 | struct Stadium 11 | { 12 | struct Iteration 13 | { 14 | float time; 15 | float best_fitness; 16 | 17 | void reset() 18 | { 19 | time = 0.0f; 20 | best_fitness = 0.0f; 21 | } 22 | }; 23 | 24 | uint32_t population_size; 25 | Selector selector; 26 | uint32_t targets_count; 27 | std::vector targets; 28 | std::vector objectives; 29 | sf::Vector2f area_size; 30 | Iteration current_iteration; 31 | swrm::Swarm swarm; 32 | 33 | Stadium(uint32_t population, sf::Vector2f size) 34 | : population_size(population) 35 | , selector(population) 36 | , targets_count(8) 37 | , targets(targets_count) 38 | , objectives(population) 39 | , area_size(size) 40 | , swarm(4) 41 | { 42 | initializeTargets(); 43 | } 44 | 45 | void loadDnaFromFile(const std::string& filename) 46 | { 47 | const uint64_t bytes_count = Network::getParametersCount(architecture) * 4; 48 | const uint64_t dna_count = DnaLoader::getDnaCount(filename, bytes_count); 49 | for (uint64_t i(0); i < dna_count && i < population_size; ++i) { 50 | const DNA dna = DnaLoader::loadDnaFrom(filename, bytes_count, i); 51 | selector.getCurrentPopulation()[i].loadDNA(dna); 52 | } 53 | } 54 | 55 | void initializeTargets() 56 | { 57 | // Initialize targets 58 | const float border_x = 360.0f; 59 | const float border_y = 290.0f; 60 | for (uint32_t i(0); i < targets_count - 1; ++i) { 61 | targets[i] = sf::Vector2f(border_x + getRandUnder(area_size.x - 2.0f * border_x), border_y + getRandUnder(area_size.y - 2.0f * border_y)); 62 | } 63 | 64 | targets[targets_count-1] = sf::Vector2f(area_size.x * 0.5f, 900.0f); 65 | } 66 | 67 | void initializeUnits() 68 | { 69 | // Initialize targets 70 | auto& rockets = selector.getCurrentPopulation(); 71 | uint32_t i = 0; 72 | for (Rocket& r : rockets) { 73 | r.index = i++; 74 | Objective& objective = objectives[r.index]; 75 | r.position = sf::Vector2f(area_size.x * 0.5f, area_size.y * 0.75f); 76 | objective.reset(); 77 | objective.points = getLength(r.position - targets[0]); 78 | r.reset(); 79 | } 80 | } 81 | 82 | bool checkAlive(const Rocket& rocket, float tolerance) const 83 | { 84 | const sf::Vector2f tolerance_margin = sf::Vector2f(tolerance, tolerance); 85 | const bool in_window = sf::FloatRect(-tolerance_margin, area_size + tolerance_margin).contains(rocket.position); 86 | return in_window && sin(rocket.angle) > 0.0f; 87 | } 88 | 89 | uint32_t getAliveCount() const 90 | { 91 | uint32_t result = 0; 92 | const auto& rockets = selector.getCurrentPopulation(); 93 | for (const Rocket& r : rockets) { 94 | result += r.alive; 95 | } 96 | 97 | return result; 98 | } 99 | 100 | void updateUnit(uint64_t i, float dt, bool update_smoke) 101 | { 102 | Rocket& r = selector.getCurrentPopulation()[i]; 103 | if (!r.alive) { 104 | // It's too late for it 105 | return; 106 | } 107 | 108 | const float target_radius = 8.0f; 109 | const float max_dist = 500.0f; 110 | const float tolerance_margin = 50.0f; 111 | 112 | Objective& objective = objectives[r.index]; 113 | sf::Vector2f to_target = objective.getTarget(targets) - r.position; 114 | const float to_target_dist = getLength(to_target); 115 | to_target.x /= std::max(to_target_dist, max_dist); 116 | to_target.y /= std::max(to_target_dist, max_dist); 117 | 118 | if (objective.target_id == targets_count - 1) { 119 | objective.time_in = 0.0f; 120 | if (to_target_dist < 1.0f && std::abs(r.angle - HalfPI) < 0.01f) { 121 | r.stop = true; 122 | } 123 | } 124 | 125 | const std::vector inputs = { 126 | to_target.x, 127 | to_target.y, 128 | r.velocity.x * dt, 129 | r.velocity.y * dt, 130 | cos(r.angle), 131 | sin(r.angle), 132 | r.angular_velocity * dt 133 | }; 134 | // The actual update 135 | if (!r.stop) { 136 | r.execute(inputs); 137 | } 138 | r.update(dt, update_smoke); 139 | r.alive = checkAlive(r, tolerance_margin); 140 | // Fitness stuffs 141 | const float jerk_malus = std::abs(r.last_power - r.thruster.power); 142 | const float move_malus = 0.1f * getLength(r.velocity * dt); 143 | r.fitness += 10.0f * jerk_malus / (1.0f + to_target_dist); 144 | // We don't want weirdos 145 | const float score_factor = std::pow(sin(r.angle), 2.0f); 146 | const float target_reward_coef = score_factor * 10.0f; 147 | const float target_time = 1.0f; 148 | if (to_target_dist < target_radius) { 149 | objective.addTimeIn(dt); 150 | if (objective.time_in > target_time) { 151 | r.fitness += target_reward_coef * objective.points / (1.0f + objective.time_out + to_target_dist); 152 | //r.fitness += objective.time_in; 153 | objective.nextTarget(targets); 154 | objective.points = getLength(r.position - objective.getTarget(targets)); 155 | } 156 | } 157 | else { 158 | objective.addTimeOut(dt); 159 | } 160 | 161 | checkBestFitness(r.fitness); 162 | } 163 | 164 | void checkBestFitness(float fitness) 165 | { 166 | current_iteration.best_fitness = std::max(fitness, current_iteration.best_fitness); 167 | } 168 | 169 | void update(float dt, bool update_smoke) 170 | { 171 | const uint64_t population_size = selector.getCurrentPopulation().size(); 172 | auto group_update = swarm.execute([&](uint32_t thread_id, uint32_t max_thread) { 173 | const uint64_t thread_width = population_size / max_thread; 174 | for (uint64_t i(thread_id * thread_width); i < (thread_id + 1) * thread_width; ++i) { 175 | updateUnit(i, dt, update_smoke); 176 | } 177 | }); 178 | group_update.waitExecutionDone(); 179 | current_iteration.time += dt; 180 | } 181 | 182 | void initializeIteration() 183 | { 184 | initializeTargets(); 185 | initializeUnits(); 186 | current_iteration.reset(); 187 | } 188 | 189 | void nextIteration() 190 | { 191 | selector.nextGeneration(); 192 | } 193 | }; 194 | -------------------------------------------------------------------------------- /include/target.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | -------------------------------------------------------------------------------- /include/unit.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "dna.hpp" 4 | 5 | 6 | struct Unit 7 | { 8 | Unit() = default; 9 | Unit(const uint64_t dna_bits_count) 10 | : dna(dna_bits_count) 11 | , fitness(0.0f) 12 | , alive(true) 13 | {} 14 | 15 | void loadDNA(const DNA& new_dna) 16 | { 17 | fitness = 0.0f; 18 | dna = new_dna; 19 | 20 | onUpdateDNA(); 21 | } 22 | 23 | virtual void onUpdateDNA() = 0; 24 | 25 | DNA dna; 26 | float fitness; 27 | bool alive; 28 | }; 29 | -------------------------------------------------------------------------------- /include/utils.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | 8 | 9 | constexpr float PI = 3.14159265f; 10 | constexpr float HalfPI = PI * 0.5f; 11 | constexpr float RAD_TO_DEG = 57.2958f; 12 | 13 | 14 | 15 | void resetRand(); 16 | 17 | 18 | float getRandRange(float width); 19 | 20 | 21 | float getRandUnder(float width); 22 | 23 | 24 | uint32_t getIntUnder(const uint32_t max); 25 | 26 | 27 | float getRandRange(float width, std::mt19937& generator); 28 | 29 | 30 | float getRandUnder(float width, std::mt19937& gen); 31 | 32 | 33 | uint32_t getIntUnder(const uint32_t max, std::mt19937& gen); 34 | 35 | 36 | float getRandRangeNonReset(float width); 37 | 38 | 39 | float getRandUnderNonReset(float width); 40 | 41 | 42 | uint32_t getIntUnderNonReset(const uint32_t max); 43 | 44 | 45 | float normalize(float value, float range); 46 | 47 | 48 | template 49 | float getLength(const sf::Vector2& v) 50 | { 51 | return sqrt(v.x * v.x + v.y * v.y); 52 | } 53 | 54 | 55 | template 56 | T as(const U& u) 57 | { 58 | return static_cast(u); 59 | } 60 | 61 | 62 | float getFastRandUnder(float max); 63 | 64 | 65 | float getAngle(const sf::Vector2f& v); 66 | 67 | 68 | float dot(const sf::Vector2f& v1, const sf::Vector2f& v2); 69 | 70 | 71 | float sign(const float f); 72 | 73 | 74 | float sigm(const float f); 75 | 76 | 77 | template 78 | std::string toString(const T& v, const uint8_t decimals = 2) 79 | { 80 | std::stringstream sx; 81 | sx << std::setprecision(decimals) << std::fixed; 82 | sx << v; 83 | 84 | return sx.str(); 85 | } 86 | 87 | sf::RectangleShape getLine(const sf::Vector2f& point_1, const sf::Vector2f& point_2, const float width, const sf::Color& color); 88 | 89 | 90 | template 91 | sf::Color toColor(const sf::Vector3& v) 92 | { 93 | const uint8_t r = as(v.x); 94 | const uint8_t g = as(v.y); 95 | const uint8_t b = as(v.z); 96 | return sf::Color(std::max(uint8_t(0), std::min(uint8_t(255), r)), 97 | std::max(uint8_t(0), std::min(uint8_t(255), g)), 98 | std::max(uint8_t(0), std::min(uint8_t(255), b)) 99 | ); 100 | } 101 | 102 | 103 | template 104 | T clamp(const T& min_val, const T& max_val, const T& value) 105 | { 106 | return std::min(max_val, std::max(min_val, value)); 107 | } 108 | -------------------------------------------------------------------------------- /lib/event_manager.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | 9 | namespace sfev 10 | { 11 | 12 | 13 | // Helper using for shorter types 14 | using EventCallback = std::function; 15 | 16 | template 17 | using EventCallbackMap = std::unordered_map; 18 | 19 | using CstEv = const sf::Event&; 20 | 21 | 22 | /* 23 | This class handles subtyped events like keyboard or mouse events 24 | The unpack function allows to get relevant information from the processed event 25 | */ 26 | template 27 | class SubTypeManager 28 | { 29 | public: 30 | SubTypeManager(std::function unpack) : 31 | m_unpack(unpack) 32 | {} 33 | 34 | ~SubTypeManager() = default; 35 | 36 | void processEvent(const sf::Event& event) const 37 | { 38 | T sub_value = m_unpack(event); 39 | auto it(m_callmap.find(sub_value)); 40 | if (it != m_callmap.end()) 41 | { 42 | // Call its associated callback 43 | (it->second)(event); 44 | } 45 | } 46 | 47 | void addCallback(const T& sub_value, EventCallback callback) 48 | { 49 | m_callmap[sub_value] = callback; 50 | } 51 | 52 | private: 53 | EventCallbackMap m_callmap; 54 | std::function m_unpack; 55 | }; 56 | 57 | /* 58 | This class handles any type of event and call its associated callbacks if any. 59 | To process key event in a more convenient way its using a KeyManager 60 | */ 61 | class EventManager 62 | { 63 | public: 64 | EventManager(sf::Window& window) : 65 | m_window(window), 66 | m_key_pressed_manager([](const sf::Event& event) {return event.key.code; }), 67 | m_key_released_manager([](const sf::Event& event) {return event.key.code; }), 68 | m_mouse_pressed_manager([](const sf::Event& event) {return event.mouseButton.button; }), 69 | m_mouse_released_manager([](const sf::Event& event) {return event.mouseButton.button; }) 70 | { 71 | // Register key events built in callbacks 72 | this->addEventCallback(sf::Event::EventType::KeyPressed, [&](const sf::Event& event) {m_key_pressed_manager.processEvent(event); }); 73 | this->addEventCallback(sf::Event::EventType::KeyReleased, [&](const sf::Event& event) {m_key_released_manager.processEvent(event); }); 74 | this->addEventCallback(sf::Event::EventType::MouseButtonPressed, [&](const sf::Event& event) {m_mouse_pressed_manager.processEvent(event); }); 75 | this->addEventCallback(sf::Event::EventType::MouseButtonReleased, [&](const sf::Event& event) {m_mouse_released_manager.processEvent(event); }); 76 | } 77 | 78 | // Attach new callback to an event 79 | void addEventCallback(sf::Event::EventType type, EventCallback callback) 80 | { 81 | m_events_callmap[type] = callback; 82 | } 83 | 84 | // Calls events' attached callbacks 85 | std::vector processEvents() const 86 | { 87 | // TODO process events from vector 88 | std::vector non_processed; 89 | // Iterate over events 90 | sf::Event event; 91 | while (m_window.pollEvent(event)) { 92 | // If event type is registred 93 | sf::Event::EventType type = event.type; 94 | auto it(m_events_callmap.find(type)); 95 | if (it != m_events_callmap.end()) { 96 | // Call its associated callback 97 | (it->second)(event); 98 | } 99 | else { 100 | non_processed.push_back(event); 101 | } 102 | } 103 | 104 | return non_processed; 105 | } 106 | 107 | // Removes a callback 108 | void removeCallback(sf::Event::EventType type) 109 | { 110 | // If event type is registred 111 | auto it(m_events_callmap.find(type)); 112 | if (it != m_events_callmap.end()) { 113 | // Remove its associated callback 114 | m_events_callmap.erase(it); 115 | } 116 | } 117 | 118 | // Adds a key pressed callback 119 | void addKeyPressedCallback(sf::Keyboard::Key key_code, EventCallback callback) 120 | { 121 | m_key_pressed_manager.addCallback(key_code, callback); 122 | } 123 | 124 | // Adds a key released callback 125 | void addKeyReleasedCallback(sf::Keyboard::Key key_code, EventCallback callback) 126 | { 127 | m_key_released_manager.addCallback(key_code, callback); 128 | } 129 | 130 | // Adds a mouse pressed callback 131 | void addMousePressedCallback(sf::Mouse::Button button, EventCallback callback) 132 | { 133 | m_mouse_pressed_manager.addCallback(button, callback); 134 | } 135 | 136 | // Adds a mouse released callback 137 | void addMouseReleasedCallback(sf::Mouse::Button button, EventCallback callback) 138 | { 139 | m_mouse_released_manager.addCallback(button, callback); 140 | } 141 | 142 | private: 143 | sf::Window& m_window; 144 | 145 | SubTypeManager m_key_pressed_manager; 146 | SubTypeManager m_key_released_manager; 147 | 148 | SubTypeManager m_mouse_pressed_manager; 149 | SubTypeManager m_mouse_released_manager; 150 | 151 | EventCallbackMap m_events_callmap; 152 | }; 153 | 154 | } // End namespace -------------------------------------------------------------------------------- /lib/swarm.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | namespace swrm 14 | { 15 | 16 | class Swarm; 17 | class ExecutionGroup; 18 | 19 | using WorkerFunction = std::function; 20 | 21 | class Worker 22 | { 23 | public: 24 | Worker(Swarm* swarm); 25 | 26 | void createThread(); 27 | 28 | void lockReady(); 29 | void lockDone(); 30 | void unlockReady(); 31 | void unlockDone(); 32 | 33 | void setJob(uint32_t id, ExecutionGroup* group); 34 | void stop(); 35 | void join(); 36 | 37 | private: 38 | bool m_running; 39 | uint32_t m_id; 40 | uint32_t m_group_size; 41 | 42 | Swarm* m_swarm; 43 | ExecutionGroup* m_group; 44 | std::thread m_thread; 45 | WorkerFunction m_job; 46 | 47 | std::mutex m_ready_mutex; 48 | std::mutex m_done_mutex; 49 | 50 | void run(); 51 | void waitReady(); 52 | void waitDone(); 53 | }; 54 | 55 | class Synchronizer 56 | { 57 | public: 58 | static void lockAtReady(std::list& workers) 59 | { 60 | for (Worker* worker : workers) { 61 | worker->lockReady(); 62 | } 63 | } 64 | 65 | static void unlockAtReady(std::list& workers) 66 | { 67 | for (Worker* worker : workers) { 68 | worker->unlockReady(); 69 | } 70 | } 71 | 72 | static void lockAtDone(std::list& workers) 73 | { 74 | for (Worker* worker : workers) { 75 | worker->lockDone(); 76 | } 77 | } 78 | 79 | static void unlockAtDone(std::list& workers) 80 | { 81 | for (Worker* worker : workers) { 82 | worker->unlockDone(); 83 | } 84 | } 85 | 86 | static void stop(std::list& workers) 87 | { 88 | for (Worker* worker : workers) { 89 | worker->stop(); 90 | } 91 | } 92 | 93 | static void join(std::list& workers) 94 | { 95 | for (Worker* worker : workers) { 96 | worker->join(); 97 | } 98 | } 99 | }; 100 | 101 | class ExecutionGroup 102 | { 103 | public: 104 | ExecutionGroup(WorkerFunction job, uint32_t group_size, std::list& available_workers) 105 | : m_job(job) 106 | , m_group_size(group_size) 107 | , m_done_count(0U) 108 | , m_condition() 109 | , m_condition_mutex() 110 | { 111 | retrieveWorkers(available_workers); 112 | start(); 113 | } 114 | 115 | ~ExecutionGroup() 116 | { 117 | } 118 | 119 | void start() 120 | { 121 | m_done_count = 0U; 122 | Synchronizer::lockAtDone(m_workers); 123 | Synchronizer::unlockAtReady(m_workers); 124 | } 125 | 126 | void waitExecutionDone() 127 | { 128 | waitWorkersDone(); 129 | Synchronizer::lockAtReady(m_workers); 130 | Synchronizer::unlockAtDone(m_workers); 131 | m_workers.clear(); 132 | } 133 | 134 | private: 135 | const uint32_t m_group_size; 136 | const WorkerFunction m_job; 137 | std::list m_workers; 138 | 139 | std::atomic m_done_count; 140 | std::condition_variable m_condition; 141 | std::mutex m_condition_mutex; 142 | 143 | void notifyWorkerDone() 144 | { 145 | { 146 | std::lock_guard lg(m_condition_mutex); 147 | ++m_done_count; 148 | } 149 | m_condition.notify_one(); 150 | } 151 | 152 | void waitWorkersDone() 153 | { 154 | std::unique_lock ul(m_condition_mutex); 155 | m_condition.wait(ul, [this] { return m_done_count == m_group_size; }); 156 | } 157 | 158 | void retrieveWorkers(std::list& available_workers) 159 | { 160 | for (uint32_t i(m_group_size); i--;) { 161 | Worker* worker = available_workers.front(); 162 | available_workers.pop_front(); 163 | worker->setJob(i, this); 164 | m_workers.push_back(worker); 165 | } 166 | } 167 | 168 | friend Worker; 169 | }; 170 | 171 | class WorkGroup 172 | { 173 | public: 174 | WorkGroup() 175 | : m_group(nullptr) 176 | {} 177 | 178 | WorkGroup(std::shared_ptr execution_group) 179 | : m_group(execution_group) 180 | {} 181 | 182 | void waitExecutionDone() 183 | { 184 | if (m_group) { 185 | m_group->waitExecutionDone(); 186 | } 187 | } 188 | 189 | private: 190 | std::shared_ptr m_group; 191 | }; 192 | 193 | class Swarm 194 | { 195 | public: 196 | Swarm(uint32_t thread_count) 197 | : m_thread_count(thread_count) 198 | , m_ready_count(0U) 199 | { 200 | for (uint32_t i(thread_count); i--;) { 201 | createWorker(); 202 | } 203 | 204 | while (m_ready_count < m_thread_count) {} 205 | } 206 | 207 | ~Swarm() 208 | { 209 | Synchronizer::stop(m_workers); 210 | Synchronizer::unlockAtReady(m_workers); 211 | Synchronizer::join(m_workers); 212 | deleteWorkers(); 213 | } 214 | 215 | WorkGroup execute(WorkerFunction job, uint32_t group_size = 0) 216 | { 217 | if (!group_size) { 218 | group_size = m_thread_count; 219 | } 220 | 221 | if (group_size > m_available_workers.size()) { 222 | return WorkGroup(); 223 | } 224 | 225 | return WorkGroup(std::make_unique(job, group_size, m_available_workers)); 226 | } 227 | 228 | 229 | private: 230 | const uint32_t m_thread_count; 231 | 232 | std::atomic m_ready_count; 233 | std::list m_workers; 234 | std::list m_available_workers; 235 | std::mutex m_mutex; 236 | 237 | void createWorker() 238 | { 239 | Worker* new_worker = new Worker(this); 240 | new_worker->createThread(); 241 | m_workers.push_back(new_worker); 242 | } 243 | 244 | void deleteWorkers() 245 | { 246 | for (Worker* worker : m_workers) { 247 | delete worker; 248 | } 249 | } 250 | 251 | void notifyWorkerReady(Worker* worker) 252 | { 253 | std::lock_guard lg(m_mutex); 254 | ++m_ready_count; 255 | m_available_workers.push_back(worker); 256 | } 257 | 258 | friend Worker; 259 | }; 260 | 261 | Worker::Worker(Swarm* swarm) 262 | : m_swarm(swarm) 263 | , m_group(nullptr) 264 | , m_id(0) 265 | , m_group_size(0) 266 | , m_running(true) 267 | , m_ready_mutex() 268 | , m_done_mutex() 269 | { 270 | } 271 | 272 | void Worker::createThread() 273 | { 274 | lockReady(); 275 | m_thread = std::thread(&Worker::run, this); 276 | } 277 | 278 | void Worker::run() 279 | { 280 | while (true) { 281 | waitReady(); 282 | 283 | if (!m_running) { 284 | break; 285 | } 286 | 287 | m_job(m_id, m_group_size); 288 | 289 | waitDone(); 290 | } 291 | } 292 | 293 | void Worker::lockReady() 294 | { 295 | m_ready_mutex.lock(); 296 | } 297 | 298 | void Worker::unlockReady() 299 | { 300 | m_ready_mutex.unlock(); 301 | } 302 | 303 | void Worker::lockDone() 304 | { 305 | m_done_mutex.lock(); 306 | } 307 | 308 | void Worker::unlockDone() 309 | { 310 | m_done_mutex.unlock(); 311 | } 312 | 313 | void Worker::setJob(uint32_t id, ExecutionGroup* group) 314 | { 315 | m_id = id; 316 | m_job = group->m_job; 317 | m_group_size = group->m_group_size; 318 | m_group = group; 319 | } 320 | 321 | void Worker::stop() 322 | { 323 | m_running = false; 324 | } 325 | 326 | void Worker::join() 327 | { 328 | m_thread.join(); 329 | } 330 | 331 | void Worker::waitReady() 332 | { 333 | m_swarm->notifyWorkerReady(this); 334 | lockReady(); 335 | unlockReady(); 336 | } 337 | 338 | void Worker::waitDone() 339 | { 340 | m_group->notifyWorkerDone(); 341 | lockDone(); 342 | unlockDone(); 343 | } 344 | 345 | } 346 | -------------------------------------------------------------------------------- /res/flame.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johnBuffer/AutoRocket/1e82ae0e379d8e0d0e469c3c89e68ffa46a2803c/res/flame.png -------------------------------------------------------------------------------- /res/font.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johnBuffer/AutoRocket/1e82ae0e379d8e0d0e469c3c89e68ffa46a2803c/res/font.ttf -------------------------------------------------------------------------------- /res/rocket.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johnBuffer/AutoRocket/1e82ae0e379d8e0d0e469c3c89e68ffa46a2803c/res/rocket.png -------------------------------------------------------------------------------- /res/smoke.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johnBuffer/AutoRocket/1e82ae0e379d8e0d0e469c3c89e68ffa46a2803c/res/smoke.png -------------------------------------------------------------------------------- /src/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | #include "dna.hpp" 8 | #include "selector.hpp" 9 | #include "number_generator.hpp" 10 | #include "graph.hpp" 11 | #include "rocket.hpp" 12 | #include "stadium.hpp" 13 | #include "rocket_renderer.hpp" 14 | #include "neural_renderer.hpp" 15 | 16 | 17 | int main() 18 | { 19 | NumberGenerator<>::initialize(); 20 | 21 | const uint32_t win_width = 1600; 22 | const uint32_t win_height = 900; 23 | sf::ContextSettings settings; 24 | settings.antialiasingLevel = 4; 25 | sf::RenderWindow window(sf::VideoMode(win_width, win_height), "SpaceY", sf::Style::Default, settings); 26 | window.setVerticalSyncEnabled(false); 27 | window.setFramerateLimit(144); 28 | 29 | bool slow_motion = false; 30 | const float base_dt = 0.007f; 31 | float dt = base_dt; 32 | 33 | sfev::EventManager event_manager(window); 34 | event_manager.addEventCallback(sf::Event::Closed, [&](sfev::CstEv ev) { window.close(); }); 35 | event_manager.addKeyPressedCallback(sf::Keyboard::Escape, [&](sfev::CstEv ev) { window.close(); }); 36 | 37 | // The drones colors 38 | std::vector colors({ sf::Color(36, 123, 160), 39 | sf::Color(161, 88, 86), 40 | sf::Color(249, 160, 97), 41 | sf::Color(80, 81, 79), 42 | sf::Color(121, 85, 83), 43 | sf::Color(242, 95, 92), 44 | sf::Color(255, 224, 102), 45 | sf::Color(146, 174, 131), 46 | sf::Color(74, 158, 170), 47 | sf::Color(112, 193, 179) }); 48 | 49 | sf::Vector2f mouse_target; 50 | 51 | bool show_just_one = true; 52 | bool full_speed = false; 53 | bool manual_control = false; 54 | bool draw_neural = true; 55 | bool draw_rockets = true; 56 | bool draw_fitness = true; 57 | 58 | event_manager.addKeyPressedCallback(sf::Keyboard::E, [&](sfev::CstEv ev) { full_speed = !full_speed; window.setFramerateLimit(!full_speed * 144); }); 59 | event_manager.addKeyPressedCallback(sf::Keyboard::M, [&](sfev::CstEv ev) { manual_control = !manual_control; }); 60 | event_manager.addKeyPressedCallback(sf::Keyboard::S, [&](sfev::CstEv ev) { show_just_one = !show_just_one; }); 61 | event_manager.addKeyPressedCallback(sf::Keyboard::N, [&](sfev::CstEv ev) { draw_neural = !draw_neural; }); 62 | event_manager.addKeyPressedCallback(sf::Keyboard::D, [&](sfev::CstEv ev) { draw_rockets = !draw_rockets; }); 63 | event_manager.addKeyPressedCallback(sf::Keyboard::F, [&](sfev::CstEv ev) { draw_fitness = !draw_fitness; }); 64 | 65 | const float GUI_MARGIN = 10.0f; 66 | Graphic fitness_graph(1000, sf::Vector2f(700, 120), sf::Vector2f(GUI_MARGIN, win_height - 120 - GUI_MARGIN)); 67 | fitness_graph.color = sf::Color(96, 211, 148); 68 | 69 | sf::Font font; 70 | font.loadFromFile("../res/font.ttf"); 71 | sf::Text generation_text; 72 | sf::Text best_score_text; 73 | generation_text.setFont(font); 74 | generation_text.setCharacterSize(42); 75 | generation_text.setFillColor(sf::Color::White); 76 | generation_text.setPosition(GUI_MARGIN * 2.0f, GUI_MARGIN); 77 | 78 | best_score_text = generation_text; 79 | best_score_text.setCharacterSize(32); 80 | best_score_text.setPosition(4.0f * GUI_MARGIN, 64); 81 | 82 | const uint32_t pop_size = 2000; 83 | Stadium stadium(pop_size, sf::Vector2f(win_width, win_height)); 84 | 85 | RocketRenderer rocket_renderer; 86 | NeuralRenderer neural_renderer; 87 | const sf::Vector2f size = neural_renderer.getSize(4, 9); 88 | neural_renderer.position = sf::Vector2f(50.0f, win_height) - sf::Vector2f(0.0f, GUI_MARGIN + size.y); 89 | 90 | sf::RenderStates states; 91 | states.transform.scale(1.2f, 1.2f); 92 | states.transform.translate(-160.0f, -90.0f); 93 | float time = 0.0f; 94 | 95 | while (window.isOpen()) { 96 | event_manager.processEvents(); 97 | 98 | // Initialize rockets 99 | std::vector& population = stadium.selector.getCurrentPopulation(); 100 | stadium.initializeIteration(); 101 | 102 | time = 0.0f; 103 | 104 | while (stadium.getAliveCount() && window.isOpen() && stadium.current_iteration.time < 90.0f) { 105 | event_manager.processEvents(); 106 | 107 | if (manual_control) { 108 | const sf::Vector2i mouse_position = sf::Mouse::getPosition(window); 109 | mouse_target.x = static_cast(mouse_position.x); 110 | mouse_target.y = static_cast(mouse_position.y); 111 | } 112 | 113 | stadium.update(dt, !full_speed); 114 | 115 | fitness_graph.setLastValue(stadium.current_iteration.best_fitness); 116 | generation_text.setString("Generation " + toString(stadium.selector.current_iteration)); 117 | best_score_text.setString("Score " + toString(stadium.current_iteration.best_fitness)); 118 | 119 | // Render 120 | window.clear(); 121 | 122 | uint32_t current_drone_i = 0; 123 | if (draw_rockets) { 124 | for (Rocket& r : population) { 125 | if (r.alive) { 126 | rocket_renderer.render(r, window, states, !full_speed && show_just_one); 127 | if (show_just_one) { 128 | current_drone_i = r.index; 129 | break; 130 | } 131 | } 132 | } 133 | } 134 | 135 | if (show_just_one) { 136 | const float target_radius = 10.0f; 137 | sf::CircleShape target_c(target_radius); 138 | target_c.setFillColor(sf::Color(255, 128, 0)); 139 | target_c.setOrigin(target_radius, target_radius); 140 | const Objective& obj = stadium.objectives[current_drone_i]; 141 | target_c.setPosition(stadium.targets[obj.target_id]); 142 | if (obj.target_id < stadium.targets_count - 1) { 143 | window.draw(target_c, states); 144 | } 145 | 146 | if (!full_speed) { 147 | RocketRenderer::drawPie(target_radius - 3.0f, (obj.time_in / 3.0f) * 2.0f * PI, sf::Color(75, 75, 75), stadium.targets[obj.target_id], window, states); 148 | neural_renderer.render(window, stadium.selector.getCurrentPopulation()[current_drone_i].network, sf::RenderStates()); 149 | } 150 | } 151 | 152 | if (draw_fitness) { 153 | fitness_graph.render(window); 154 | } 155 | 156 | window.display(); 157 | time += dt; 158 | } 159 | 160 | fitness_graph.next(); 161 | stadium.nextIteration(); 162 | } 163 | 164 | return 0; 165 | } -------------------------------------------------------------------------------- /src/utils.cpp: -------------------------------------------------------------------------------- 1 | #include "utils.hpp" 2 | 3 | #include 4 | 5 | std::random_device rd; 6 | std::mt19937 gen(0); 7 | std::mt19937 gen_no_reset(rd()); 8 | 9 | 10 | void resetRand() 11 | { 12 | gen = std::mt19937(0); 13 | } 14 | 15 | float getRandRange(float width) 16 | { 17 | std::uniform_real_distribution distr(-width, width); 18 | return distr(gen); 19 | } 20 | 21 | float getRandUnder(float width) 22 | { 23 | std::uniform_real_distribution distr(0.0f, width); 24 | return distr(gen); 25 | } 26 | 27 | uint32_t getIntUnder(const uint32_t max) 28 | { 29 | std::uniform_int_distribution distr(0, max); 30 | return distr(gen); 31 | } 32 | 33 | float getRandRange(float width, std::mt19937& generator) 34 | { 35 | std::uniform_real_distribution distr(-width, width); 36 | return distr(generator); 37 | } 38 | 39 | float getRandUnder(float width, std::mt19937& generator) 40 | { 41 | std::uniform_real_distribution distr(0.0f, width); 42 | return distr(generator); 43 | } 44 | 45 | uint32_t getIntUnder(const uint32_t max, std::mt19937& generator) 46 | { 47 | std::uniform_int_distribution distr(0, max); 48 | return distr(generator); 49 | } 50 | 51 | float getRandRangeNonReset(float width) 52 | { 53 | std::uniform_real_distribution distr(-width, width); 54 | return distr(gen_no_reset); 55 | } 56 | 57 | float getRandUnderNonReset(float width) 58 | { 59 | std::uniform_real_distribution distr(0.0f, width); 60 | return distr(gen_no_reset); 61 | } 62 | 63 | uint32_t getIntUnderNonReset(const uint32_t max) 64 | { 65 | std::uniform_int_distribution distr(0, max); 66 | return distr(gen_no_reset); 67 | } 68 | 69 | float normalize(float value, float range) 70 | { 71 | return value / range; 72 | } 73 | 74 | float getFastRandUnder(float max) 75 | { 76 | constexpr int32_t m = 10000; 77 | constexpr float im = 1.0f / float(m); 78 | return max * (rand()%m * im); 79 | } 80 | 81 | float getAngle(const sf::Vector2f & v) 82 | { 83 | const float a = acos(v.x / getLength(v)); 84 | return v.y > 0.0f ? a : -a; 85 | } 86 | 87 | float dot(const sf::Vector2f & v1, const sf::Vector2f & v2) 88 | { 89 | return v1.x * v2.x + v1.y * v2.y; 90 | } 91 | 92 | float sign(const float f) 93 | { 94 | return f < 0.0f ? -1.0f : 1.0f; 95 | } 96 | 97 | float sigm(const float f) 98 | { 99 | return 1.0f / (1.0f + exp(-f)); 100 | } 101 | 102 | sf::RectangleShape getLine(const sf::Vector2f& point_1, const sf::Vector2f& point_2, const float width, const sf::Color& color) 103 | { 104 | const sf::Vector2f vec = point_2 - point_1; 105 | const float angle = getAngle(vec); 106 | const sf::Vector2f mid_point = point_1 + 0.5f * vec; 107 | const float dist = getLength(vec); 108 | const float rad_to_deg = 57.2958f; 109 | 110 | sf::RectangleShape line(sf::Vector2f(width, dist)); 111 | line.setOrigin(width * 0.5f, dist * 0.5f); 112 | line.setRotation(angle * rad_to_deg - 90); 113 | line.setFillColor(color); 114 | line.setPosition(mid_point); 115 | 116 | return line; 117 | } 118 | --------------------------------------------------------------------------------