diff options
-rw-r--r-- | CMakeLists.txt | 2 | ||||
-rw-r--r-- | bot.json | 5 | ||||
-rw-r--r-- | include/brain/neural_network.h | 4 | ||||
-rw-r--r-- | include/brain/neural_node.h | 1 | ||||
-rw-r--r-- | include/brain/neuron.h | 4 | ||||
-rw-r--r-- | include/building.h | 12 | ||||
-rw-r--r-- | include/game_state.h | 16 | ||||
-rw-r--r-- | src/brain/neural_network.cpp | 81 | ||||
-rw-r--r-- | src/brain/neuron.cpp | 7 | ||||
-rw-r--r-- | src/building.cpp | 6 | ||||
-rw-r--r-- | src/game_state.cpp | 154 | ||||
-rw-r--r-- | src/spacebot.cpp | 6 | ||||
-rw-r--r-- | test/game_state.cpp | 4 |
13 files changed, 212 insertions, 90 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt index 2a84f19..fd6a32d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,7 +1,7 @@ cmake_minimum_required(VERSION 3.2.2) project(Spacebot) -set(CMAKE_CXX_FLAGS "-std=c++11") +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11 -Wall -Wpedantic -Wextra") set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin) diff --git a/bot.json b/bot.json new file mode 100644 index 0000000..d70d03c --- /dev/null +++ b/bot.json @@ -0,0 +1,5 @@ +{ + "nickName": "Spacebot", + "author": "Justin Worthe", + "email", "justin.worthe@gmail.com" +} diff --git a/include/brain/neural_network.h b/include/brain/neural_network.h index 22e5e42..f75fdea 100644 --- a/include/brain/neural_network.h +++ b/include/brain/neural_network.h @@ -26,8 +26,8 @@ public: private: std::map<int, std::shared_ptr<Sensor>> _sensors; std::shared_ptr<BiasNode> _biasNode; - std::map<int, std::shared_ptr<Neuron>> _hiddenNodes; - std::map<int, std::shared_ptr<Neuron>> _outputs; + std::map<int, std::shared_ptr<Neuron>> _neurons; + std::vector<std::shared_ptr<Neuron>> _outputs; void parseFile(std::istream &&file); diff --git a/include/brain/neural_node.h b/include/brain/neural_node.h index 3c2a6cb..c7b4335 100644 --- a/include/brain/neural_node.h +++ b/include/brain/neural_node.h @@ -4,7 +4,6 @@ class NeuralNode { public: NeuralNode(char type, int id); - virtual ~NeuralNode() {}; virtual double activation() const = 0; diff --git a/include/brain/neuron.h b/include/brain/neuron.h index 810ce5b..d017b0d 100644 --- a/include/brain/neuron.h +++ b/include/brain/neuron.h @@ -13,9 +13,9 @@ public: virtual ~Neuron() {} virtual double activation() const; - void addInput(std::shared_ptr<NeuralLink> link); + void addInput(std::unique_ptr<NeuralLink>&& link); private: - std::vector<std::shared_ptr<NeuralLink>> _inputLinks; + std::vector<std::unique_ptr<NeuralLink>> _inputLinks; double sigmoid(double input) const; }; diff --git a/include/building.h b/include/building.h new file mode 100644 index 0000000..76d78fd --- /dev/null +++ b/include/building.h @@ -0,0 +1,12 @@ +#pragma once + +#include "game_entity.h" + +class Building : public GameEntity +{ +public: + Building(int x, int y); + const static char MISSILE_CONTROLLER_CHAR = 'M'; + const static char ALIEN_FACTORY_CHAR = 'X'; +}; + diff --git a/include/game_state.h b/include/game_state.h index 482dd7d..c129d28 100644 --- a/include/game_state.h +++ b/include/game_state.h @@ -5,21 +5,27 @@ #include "player_missile.h" #include "shield.h" #include "spaceship.h" +#include "building.h" #include <vector> #include <string> #include <istream> +#include <memory> class GameState { public: GameState(std::istream &&mapFile); - void logState(); + void logState() const; const std::vector<Alien>& aliens() const { return _aliens; } const std::vector<EnemyBullet>& bullets() const { return _bullets; } const std::vector<PlayerMissile>& missiles() const { return _missiles; } const std::vector<Shield>& shields() const { return _shields; } - const std::vector<Spaceship>& spaceships() const { return _spaceships; } + const std::vector<Building>& missileControllers() const { return _missileControllers; } + const std::vector<Building>& alienFactories() const { return _alienFactories; } + + const std::unique_ptr<Spaceship>& playerSpaceship() const { return _playerSpaceship; } + const std::unique_ptr<Spaceship>& enemySpaceship() const { return _enemySpaceship; } std::vector<bool> toBitArray() const; @@ -28,7 +34,11 @@ private: std::vector<EnemyBullet> _bullets; std::vector<PlayerMissile> _missiles; std::vector<Shield> _shields; - std::vector<Spaceship> _spaceships; + std::vector<Building> _missileControllers; + std::vector<Building> _alienFactories; + std::unique_ptr<Spaceship> _playerSpaceship; + std::unique_ptr<Spaceship> _enemySpaceship; + int addEntity(int x, int y, char type); }; diff --git a/src/brain/neural_network.cpp b/src/brain/neural_network.cpp index d6a5e15..9061194 100644 --- a/src/brain/neural_network.cpp +++ b/src/brain/neural_network.cpp @@ -1,5 +1,6 @@ #include "brain/neural_network.h" #include "brain/neuron.h" +#include <limits> NeuralNetwork::NeuralNetwork(std::istream &&networkConfigFile, int numberOfSensors, int numberOfOutputs) { @@ -11,7 +12,9 @@ NeuralNetwork::NeuralNetwork(std::istream &&networkConfigFile, int numberOfSenso } for (int i=0; i<numberOfOutputs; ++i) { - _outputs[i] = std::make_shared<Neuron>(i); + auto output = std::make_shared<Neuron>(i); + _outputs.push_back(output); + _neurons[i] = output; } parseFile(std::move(networkConfigFile)); @@ -21,15 +24,17 @@ NeuralNetwork::NeuralNetwork(std::istream &&networkConfigFile, std::vector<bool> { _biasNode = std::make_shared<BiasNode>(); - for (int i=0; i<sensorInitialValues.size(); ++i) + for (unsigned int i=0; i<sensorInitialValues.size(); ++i) { auto sensor = std::make_shared<Sensor>(i); - sensor->setActivation(sensorInitialValues.at(i) ? 1 : 0); + sensor->setActivation(sensorInitialValues[i] ? 1 : 0); _sensors[i] = sensor; } for (int i=0; i<numberOfOutputs; ++i) { - _outputs[i] = std::make_shared<Neuron>(i); + auto output = std::make_shared<Neuron>(i); + _outputs.push_back(output); + _neurons[i] = output; } parseFile(std::move(networkConfigFile)); @@ -38,14 +43,18 @@ NeuralNetwork::NeuralNetwork(std::istream &&networkConfigFile, std::vector<bool> void NeuralNetwork::parseFile(std::istream &&file) { double weight; - for (std::string src, dest; - file >> src && file >> dest && file >> weight; ) - { - char srcType = src.at(0); - int srcId = std::stoi(src.substr(1)); - char destType = dest.at(0); - int destId = std::stoi(dest.substr(1)); + char srcType; + int srcId; + int destId; + while (file.get(srcType) && + file >> srcId && + file.ignore(std::numeric_limits<std::streamsize>::max(), 'n') && + file >> destId && + file >> weight && + file.ignore(std::numeric_limits<std::streamsize>::max(), '\n')) + { + std::shared_ptr<NeuralNode> source; std::shared_ptr<Neuron> destination; switch (srcType) @@ -56,21 +65,11 @@ void NeuralNetwork::parseFile(std::istream &&file) case 'b': source = _biasNode; break; - case 'n': - source = findOrAddNeuron(srcId); - break; default: - throw 1; - } - switch (destType) - { - case 'n': - destination = findOrAddNeuron(destId); - break; - default: - throw 1; + source = findOrAddNeuron(srcId); } - + destination = findOrAddNeuron(destId); + addLink(source, destination, weight); } @@ -78,34 +77,30 @@ void NeuralNetwork::parseFile(std::istream &&file) void NeuralNetwork::addLink(std::shared_ptr<NeuralNode> source, std::shared_ptr<Neuron> destination, double weight) { - auto link = std::make_shared<NeuralLink>(source, weight); - destination->addInput(link); + std::unique_ptr<NeuralLink> link(new NeuralLink(source, weight)); + destination->addInput(std::move(link)); } std::shared_ptr<Sensor> NeuralNetwork::findOrAddSensor(int id) { - bool sensorExists = _sensors.count(id) > 0; - if (!sensorExists) + auto sensor = _sensors[id]; + if (!sensor) { - _sensors[id] = std::make_shared<Sensor>(id); + sensor = std::make_shared<Sensor>(id); + _sensors[id] = sensor; } - return _sensors.at(id); + return sensor; } std::shared_ptr<Neuron> NeuralNetwork::findOrAddNeuron(int id) { - bool isOutput = _outputs.count(id) > 0; - if (isOutput) - { - return _outputs.at(id); - } - - bool hiddenNeuronExists = _hiddenNodes.count(id) > 0; - if (!hiddenNeuronExists) + auto neuron = _neurons[id]; + if (!neuron) { - _hiddenNodes[id] = std::make_shared<Neuron>(id); + neuron = std::make_shared<Neuron>(id); + _neurons[id] = neuron; } - return _hiddenNodes.at(id); + return neuron; } void NeuralNetwork::setInput(int inputIndex, double activation) @@ -117,13 +112,13 @@ int NeuralNetwork::findMaxOutputIndex() const { double currentMaxActivation = 0; int currentMaxIndex = 0; - for (std::pair<const int, std::shared_ptr<Neuron>> outputPair : _outputs) + for (unsigned int i=0; i<_outputs.size(); ++i) { - double activation = outputPair.second->activation(); + double activation = _outputs[i]->activation(); if (activation >= currentMaxActivation) { currentMaxActivation = activation; - currentMaxIndex = outputPair.first; + currentMaxIndex = i; } } return currentMaxIndex; diff --git a/src/brain/neuron.cpp b/src/brain/neuron.cpp index 7ea02c6..527e7da 100644 --- a/src/brain/neuron.cpp +++ b/src/brain/neuron.cpp @@ -9,21 +9,20 @@ Neuron::Neuron(int id) double Neuron::sigmoid(double input) const { double slope = 4.924273; - double constant = 2.4621365; return (1/(1+(std::exp(-(slope*input))))); } double Neuron::activation() const { double activationSum = 0; - for (auto link : _inputLinks) + for (auto const& link : _inputLinks) { activationSum += link->weightedActivation(); } return sigmoid(activationSum); } -void Neuron::addInput(std::shared_ptr<NeuralLink> link) +void Neuron::addInput(std::unique_ptr<NeuralLink>&& link) { - _inputLinks.push_back(link); + _inputLinks.push_back(std::move(link)); } diff --git a/src/building.cpp b/src/building.cpp new file mode 100644 index 0000000..8da322d --- /dev/null +++ b/src/building.cpp @@ -0,0 +1,6 @@ +#include "building.h" + +Building::Building(int x, int y) + :GameEntity(x, y) +{ +} diff --git a/src/game_state.cpp b/src/game_state.cpp index e6deae7..eb02005 100644 --- a/src/game_state.cpp +++ b/src/game_state.cpp @@ -3,6 +3,7 @@ #include <fstream> #include <limits> #include <bitset> +#include <cmath> const int OPENING_LINES = 6; const int GAME_AREA_LINES = 25; @@ -60,75 +61,170 @@ int GameState::addEntity(int x, int y, char type) _shields.push_back(Shield(x,y)); return 1; case Spaceship::ENEMY_MAP_CHAR: + _enemySpaceship = std::unique_ptr<Spaceship>(new Spaceship(x+1, y)); + return 3; case Spaceship::PLAYER_MAP_CHAR: - _spaceships.push_back(Spaceship(x+1,y)); + _playerSpaceship = std::unique_ptr<Spaceship>(new Spaceship(x+1, y)); return 3; + case Building::MISSILE_CONTROLLER_CHAR: + _missileControllers.push_back(Building(x+1, y)); + return 3; + case Building::ALIEN_FACTORY_CHAR: + _alienFactories.push_back(Building(x+1, y)); + return 3; } return 1; } -void GameState::logState() +void GameState::logState() const { - for (auto alien : _aliens) + for (auto const& alien : _aliens) { std::cout << "Alien " << alien.coords() << std::endl; } - for (auto bullet : _bullets) + for (auto const& bullet : _bullets) { std::cout << "Enemy Bullet" << bullet.coords() << std::endl; } - for (auto missile : _missiles) + for (auto const& missile : _missiles) { std::cout << "Player Missile" << missile.coords() << std::endl; } - for (auto shield : _shields) + for (auto const& shield : _shields) { std::cout << "Shield" << shield.coords() << std::endl; } - for (auto spaceship : _spaceships) + if (_playerSpaceship) { - std::cout << "Spaceship" << spaceship.coords() << std::endl; + std::cout << "Player Spaceship" << _playerSpaceship->coords() << std::endl; } + if (_enemySpaceship) + { + std::cout << "Enemy Spaceship" << _enemySpaceship->coords() << std::endl; + } +} + +int getPyramidOffset(int bottomWidth, int x, int y, int resultIfLeft, int resultIfRight) +{ + int currentRowWidth = bottomWidth; + int currentRowOffset = 0; + for (int i=0; i<y; ++i) + { + currentRowOffset += currentRowWidth; + currentRowWidth += 2; + } + + if (x > currentRowWidth/2) + { + return resultIfRight; + } + else if (x < -currentRowWidth/2) + { + return resultIfLeft; + } + else + { + return currentRowOffset + currentRowWidth/2 + x; + } +} + +int getRectangularOffset(int width, int x, int y) +{ + int currentRowOffset = width*y; + return currentRowOffset + width/2 + x; } std::vector<bool> GameState::toBitArray() const { - std::vector<bool> result; + std::vector<bool> result(172); - std::vector<bool> alienNodes((GAME_AREA_LINES-2) * (GAME_WIDTH-2), false); - for (auto alien : _aliens) + int alienOffset = 0; //Aliens are 0 to 100 + int bulletOffset = 101; //Bullets are 101 to 135 + int shieldOffset = 136; //Shields are 136 to 150 + int missileOffset = 151; //Missile info is 151 to 152 + int positionOffset = 153; //Position is 153 to 168 + int existingBuildingsOffset = 169; //Existing buildings is 169 to 170 + int canBuildOffset = 171; //Can build here is 171 + + int playerX = GAME_WIDTH/2; + int playerY = GAME_AREA_LINES-3; + if (_playerSpaceship) { - alienNodes.at((alien.y()-1)*(GAME_WIDTH-2)+(alien.x()-1)) = true; + playerX = _playerSpaceship->x(); } - result.insert(result.end(), alienNodes.begin(), alienNodes.end()); - std::vector<bool> bulletNodes((GAME_AREA_LINES-2) * (GAME_WIDTH-2), false); - for (auto bullet : _bullets) + + for (auto const& alien : _aliens) { - bulletNodes.at((bullet.y()-1)*(GAME_WIDTH-2)+(bullet.x()-1)) = true; + if (alien.y() >= playerY || alien.y() < playerY-9) + { + continue; + } + + result.at(alienOffset + getPyramidOffset(3, alien.x()-playerX, playerY-1-alien.y(), 99, 100)) = true; } - result.insert(result.end(), bulletNodes.begin(), bulletNodes.end()); - std::vector<bool> missileNodes((GAME_AREA_LINES-2) * (GAME_WIDTH-2), false); - for (auto missile : _missiles) + for (auto const& bullet : _bullets) { - missileNodes.at((missile.y()-1)*(GAME_WIDTH-2)+(missile.x()-1)) = true; + if (bullet.y() >= playerY || bullet.y() < playerY-5 || bullet.x() > playerX+3 || bullet.x() < playerX-3) + { + continue; + } + + result.at(bulletOffset + getRectangularOffset(7, bullet.x()-playerX, playerY-1-bullet.y())) = true; } - result.insert(result.end(), missileNodes.begin(), missileNodes.end()); - std::vector<bool> shieldNodes(2 * (GAME_WIDTH-2), false); - for (auto shield : _shields) + for (auto const& shield : _shields) { - int row = shield.y() > GAME_AREA_LINES/2 ? 1 : 0; - shieldNodes.at(row*(GAME_WIDTH-2)+(shield.x()-1)) = true; + if (shield.y() < playerY-3 || shield.x() < playerX-2 || shield.x() > playerX+2) + { + continue; + } + + result.at(shieldOffset + getRectangularOffset(5, shield.x()-playerX, playerY-1-shield.y())) = true; } - for (auto spaceship : _spaceships) + if (_missiles.size() > 0) + { + result.at(missileOffset) = true; + } + if (_missiles.size() < 1) + { + result.at(missileOffset + 1) = true; + } + + if (_playerSpaceship) { - std::bitset<5> spaceshipBits(spaceship.x()); - for (int i=0; i<spaceshipBits.size(); ++i) + result.at(positionOffset + playerX-2) = true; + } + + result.at(canBuildOffset) = true; + for (auto const& missileController : _missileControllers) + { + if (missileController.y() < playerY) + { + continue; + } + result.at(existingBuildingsOffset + 0) = true; + if (_missiles.size() < 2) + { + result.at(missileOffset+1) = true; + } + if (abs(missileController.x() - playerX) < 3) + { + result.at(canBuildOffset) = false; + } + } + for (auto const& alienFactory : _alienFactories) + { + if (alienFactory.y() < playerY) + { + continue; + } + result.at(existingBuildingsOffset + 1) = true; + if (abs(alienFactory.x() - playerX) < 3) { - result.push_back(spaceshipBits[i]); + result.at(canBuildOffset) = false; } } diff --git a/src/spacebot.cpp b/src/spacebot.cpp index 418756c..15f2221 100644 --- a/src/spacebot.cpp +++ b/src/spacebot.cpp @@ -2,6 +2,7 @@ #include "move_string_mapper.h" #include "brain/neural_network.h" #include <fstream> +#include <iostream> Spacebot::Spacebot(std::string outputPath, std::string brainFilename) : _outputFilename(outputPath+"/move.txt"), @@ -22,10 +23,9 @@ Move Spacebot::chooseMove() NeuralNetwork network(std::ifstream(_brainFilename), sensorInputs, - static_cast<int>(Move::BUILD_SHIELD)); + 7); - int moveInt = network.findMaxOutputIndex(); - return static_cast<Move>(moveInt); + return static_cast<Move>(network.findMaxOutputIndex()); } void Spacebot::writeMove(const Move& move) diff --git a/test/game_state.cpp b/test/game_state.cpp index 467a665..a9975f2 100644 --- a/test/game_state.cpp +++ b/test/game_state.cpp @@ -82,8 +82,8 @@ SCENARIO("game state is read from istream") THEN("the spaceships are read correctly") { - auto spaceships = state.spaceships(); - REQUIRE(spaceships.size() == 2); + REQUIRE(state.playerSpaceship()->x() == 9); + REQUIRE(state.playerSpaceship()->y() == 22); } } } |