diff options
author | Paul Oliver <contact@pauloliver.dev> | 2024-02-29 19:27:35 +0100 |
---|---|---|
committer | Paul Oliver <contact@pauloliver.dev> | 2024-02-29 19:27:49 +0100 |
commit | 17909d029c6a8872b2fddf4e171d7925bbbe9c5c (patch) | |
tree | cbb08af84cd68d24acc362d593a2048b0fa79689 /Simulation |
Diffstat (limited to 'Simulation')
30 files changed, 2747 insertions, 0 deletions
diff --git a/Simulation/Camera.cpp b/Simulation/Camera.cpp new file mode 100644 index 0000000..84b98a8 --- /dev/null +++ b/Simulation/Camera.cpp @@ -0,0 +1,241 @@ +#include "Camera.hpp" +#include "SimBase.hpp" + +void Camera::create(SimBase *sim) +{ + hSim = sim; + + defaultZoom = hSim->prms.worldRad / 200.f; + zoom = defaultZoom; + prevZoom = zoom; + trgtZoom = zoom; + + step = 1; + update(); +} + + +void Camera::onResize() +{ + sf::View view = hSim->window.getView(); + sf::Vector2u winSize = hSim->window.getSize(); + view.setSize(winSize.x * zoom, winSize.y * zoom); + view.setCenter(currentCrds); + hSim->window.setView(view); +} + + +void Camera::onClick() +{ + prevZoom = zoom; + trgtZoom = zoom; + + prevCrds = currentCrds; + + sf::Vector2f mousePos = hSim->window.mapPixelToCoords(sf::Mouse::getPosition(hSim->window)); + b2Vec2 b2MousePos(mousePos.x, mousePos.y); + b2Vec2 additive(0.001f, 0.001f); + b2AABB aabb; + aabb.upperBound = b2MousePos + additive; + aabb.lowerBound = b2MousePos - additive; + + QueryCallback callback; + callback.m_point = b2MousePos; + hSim->tank.world.QueryAABB(&callback, aabb); + + if (callback.m_fixture) + { + trgtBody = callback.m_fixture->GetBody(); + } + else + { + trgtCrds = mousePos; + trgtBody = nullptr; + } + + isOnFollow = false; + step = Params::CAM_STEPS; +} + + +void Camera::shift(const sf::Vector2f &newTrgtCrds, int zoomInOut, bool release) +{ + if (!release && trgtBody) + { + prevCrds = sf::Vector2f(trgtBody->GetPosition().x, trgtBody->GetPosition().y); + currentCrds = sf::Vector2f(trgtBody->GetPosition().x, trgtBody->GetPosition().y); + trgtCrds = sf::Vector2f(trgtBody->GetPosition().x, trgtBody->GetPosition().y); + isOnFollow = true; + } + else + { + trgtBody = nullptr; + prevCrds = currentCrds; + trgtCrds = newTrgtCrds; + isOnFollow = false; + } + + prevZoom = zoom; + if (zoomInOut) + { + if (zoomInOut == 1) + { + trgtZoom = zoom - 0.2f * zoom; + } + else if (zoomInOut == -1) + { + trgtZoom = zoom + 0.2f * zoom; + } + else if (zoomInOut == -2) + { + trgtZoom = defaultZoom; + } + } + else + { + trgtZoom = zoom; + } + + step = Params::CAM_STEPS; +} + +void Camera::update() +{ + if (trgtBody || step) + { + if (!step) + { + currentCrds = sf::Vector2f(trgtBody->GetPosition().x, trgtBody->GetPosition().y); + } + else + { + if (trgtBody) + { + trgtCrds = sf::Vector2f(trgtBody->GetPosition().x, trgtBody->GetPosition().y); + if (isOnFollow) + { + prevCrds = trgtCrds; + } + } + + float f1 = (step - 1.f) / (float)Params::CAM_STEPS; + float f2 = 1.f - f1; + + currentCrds = prevCrds * f1 + trgtCrds * f2; + zoom = prevZoom * f1 + trgtZoom * f2; + + if (currentCrds.x * currentCrds.x + currentCrds.y * currentCrds.y > hSim->prms.worldRad * hSim->prms.worldRad) + { + float angle = atanf(currentCrds.y / currentCrds.x); + currentCrds.y = sinf(angle) * hSim->prms.worldRad * (currentCrds.x > 1.f ? 1.f : -1.f); + currentCrds.x = cosf(angle) * hSim->prms.worldRad * (currentCrds.x > 1.f ? 1.f : -1.f); + } + + if (zoom > defaultZoom) + { + zoom = defaultZoom; + } + if (zoom < Params::MAX_ZOOM) + { + zoom = Params::MAX_ZOOM; + } + + --step; + } + + sf::View view = hSim->window.getView(); + view.setCenter(currentCrds); + view.setSize(hSim->window.getSize().x * zoom, hSim->window.getSize().y * zoom); + hSim->window.setView(view); + } + + // Prepare low zoom graphics + hSim->tank.worldEdges.setOutlineThickness(zoom * 2.f); + + if (zoom > Params::ZAPPER_RAD / 2.f) + { + if (zoom > Params::ZAPPER_RAD * 2.f) + { + setCenterRad(hSim->zapperPoint, zoom); + hSim->zapperPoint.setFillColor(hSim->prms.zapperColor); + } + else + { + setCenterRad(hSim->zapperPoint, Params::ZAPPER_RAD); + hSim->zapperPoint.setFillColor(hSim->prms.zapperColor); + } + } + else + { + setCenterRad(hSim->zapperShell, hSim->prms.ZAPPER_RAD - zoom * 2.f); + hSim->zapperShell.setOutlineThickness(zoom * 2.f); + } + + if (zoom > Params::PELLET_RAD / 2.f) + { + if (zoom > Params::PELLET_RAD * 2.f) + { + setCenterRad(hSim->pelletPoint, zoom); + hSim->pelletPoint.setFillColor(hSim->prms.pelletColor); + } + else + { + setCenterRad(hSim->pelletPoint, Params::PELLET_RAD); + hSim->pelletPoint.setFillColor(hSim->prms.pelletColor); + } + } + else + { + setCenterRad(hSim->pelletShell, hSim->prms.PELLET_RAD - zoom * 2.f); + hSim->pelletShell.setOutlineThickness(zoom * 2.f); + } + + if (zoom > Params::CORPSE_RAD / 2.f) + { + if (zoom > Params::CORPSE_RAD * 2.f) + { + setCenterRad(hSim->corpsePoint, zoom); + hSim->corpsePoint.setFillColor(hSim->prms.corpseColor); + } + else + { + setCenterRad(hSim->corpsePoint, Params::CORPSE_RAD); + hSim->corpsePoint.setFillColor(hSim->prms.corpseColor); + } + } + else + { + setCenterRad(hSim->corpseShell, hSim->prms.CORPSE_RAD - zoom * 2.f); + hSim->corpseShell.setOutlineThickness(zoom * 2.f); + } + + if (zoom > Params::EGG_RAD / 2.f) + { + if (zoom > Params::EGG_RAD * 2.f) + { + setCenterRad(hSim->eggPoint, zoom); + hSim->eggPoint.setFillColor(hSim->guppN); + } + else + { + setCenterRad(hSim->eggPoint, Params::EGG_RAD); + hSim->eggPoint.setFillColor(hSim->guppN); + } + } + else + { + setCenterRad(hSim->guppieEgg, hSim->prms.EGG_RAD - zoom * 2.f); + hSim->guppieEgg.setOutlineThickness(zoom * 2.f); + } + + if (zoom > Params::GUPPIE_RAD) + { + setCenterRad(hSim->guppiePoint, zoom); + } + + else if (zoom > 0.02f) + { + setCenterRad(hSim->guppieShell, Params::GUPPIE_RAD - zoom); + hSim->guppieShell.setOutlineThickness(zoom); + } +} diff --git a/Simulation/Camera.hpp b/Simulation/Camera.hpp new file mode 100644 index 0000000..b6ee8a9 --- /dev/null +++ b/Simulation/Camera.hpp @@ -0,0 +1,42 @@ +#ifndef __CAMERA_HPP__ +#define __CAMERA_HPP__ + +#include <SFML/Graphics.hpp> +#include <Box2D.h> + +class SimBase; + +class Camera +{ +public: + void create(SimBase *sim); + void onResize(); + void onClick(); + void shift(const sf::Vector2f &newTrgtCrds, int zoomInOut = 0, bool release = true); + void update(); + + sf::Vector2f getCrds() const { return currentCrds; } + float getZoom() const { return zoom; } + +private: + SimBase *hSim = nullptr; + + float defaultZoom = 0.f; + float zoom = 0.f; + float prevZoom = 0.f; + float trgtZoom = 0.f; + sf::Vector2f currentCrds = { 0.f, 0.f }; + sf::Vector2f prevCrds = { 0.f, 0.f }; + sf::Vector2f trgtCrds = { 0.f, 0.f }; + unsigned step = 0; + bool isOnFollow = false; + b2Body *trgtBody = nullptr; + + friend class ContactListener; + friend class Zapper; + friend class Pellet; + friend class Guppie; + friend class Corpse; +}; + +#endif // __CAMERA_HPP__ diff --git a/Simulation/ContactListener.cpp b/Simulation/ContactListener.cpp new file mode 100644 index 0000000..6c42365 --- /dev/null +++ b/Simulation/ContactListener.cpp @@ -0,0 +1,168 @@ +#include "ContactListener.hpp" +#include "SimBase.hpp" + +void ContactListener::PreSolve(b2Contact *contact, const b2Manifold *oldManifold) +{ + b2Body *bodyA = contact->GetFixtureA()->GetBody(); + b2Body *bodyB = contact->GetFixtureB()->GetBody(); + + Guppie *gp1 = nullptr; + Guppie *gp2 = nullptr; + + if (bodyA->GetType() == b2_dynamicBody) + { + if (((Entity *)bodyA->GetUserData())->radius == Params::GUPPIE_RAD) + { + if (((Guppie *)bodyA->GetUserData())->isActive) + { + gp1 = (Guppie *)bodyA->GetUserData(); + } + } + } + if (bodyB->GetType() == b2_dynamicBody) + { + if (((Entity *)bodyB->GetUserData())->radius == Params::GUPPIE_RAD) + { + if (((Guppie *)bodyB->GetUserData())->isActive) + { + gp2 = (Guppie *)bodyB->GetUserData(); + } + } + } + + // If only one body is a Guppie + if ((gp1 && !gp2) || (!gp1 && gp2)) + { + Guppie *gp = gp1 ? gp1 : gp2; + b2Body *bd = gp1 ? bodyB : bodyA; + + if (bd->GetType() == b2_staticBody) + { + gp->lastContactStep = hSim->prms.contactSteps; + gp->lastContact = Guppie::ZAPPER; + gp->energy += hSim->prms.etFromZapper; + } + else + { + if (((Entity *)bd->GetUserData())->radius == Params::ZAPPER_RAD) + { + gp->lastContactStep = hSim->prms.contactSteps; + gp->lastContact = Guppie::ZAPPER; + gp->energy += hSim->prms.etFromZapper; + } + else + { + sf::Transform trans; + trans.translate(gp->body->GetPosition().x, gp->body->GetPosition().y); + trans.rotate(gp->body->GetAngle() * Params::RAD_DGRS); + sf::Vector2f beak1 = trans.transformPoint(hSim->beak1.getPoint(1)); + sf::Vector2f beak2 = trans.transformPoint(hSim->beak2.getPoint(1)); + float cateteX1 = beak1.x - bd->GetPosition().x; + float cateteY1 = beak1.y - bd->GetPosition().y; + float cateteX2 = beak2.x - bd->GetPosition().x; + float cateteY2 = beak2.y - bd->GetPosition().y; + if (((Entity *)bd->GetUserData())->radius == Params::PELLET_RAD) + { + if (cateteX1 * cateteX1 + cateteY1 * cateteY1 < hSim->prms.PELLET_RAD * hSim->prms.PELLET_RAD || + cateteX2 * cateteX2 + cateteY2 * cateteY2 < hSim->prms.PELLET_RAD * hSim->prms.PELLET_RAD) + { + gp->lastContactStep = hSim->prms.contactSteps; + gp->lastContact = Guppie::PELLET; + gp->energy += hSim->prms.etFromPellet; + gp->fitness += hSim->prms.forPellet; + ((Pellet *)bd->GetUserData())->toBeDestroyed = true; + if (hSim->camera.trgtBody == bd) + { + hSim->camera.prevCrds = hSim->camera.currentCrds; + hSim->camera.step = Params::CAM_STEPS; + hSim->camera.trgtBody = gp->body; + } + } + } + else if (((Entity *)bd->GetUserData())->radius == Params::CORPSE_RAD) + { + if (cateteX1 * cateteX1 + cateteY1 * cateteY1 < hSim->prms.CORPSE_RAD * hSim->prms.CORPSE_RAD || + cateteX2 * cateteX2 + cateteY2 * cateteY2 < hSim->prms.CORPSE_RAD * hSim->prms.CORPSE_RAD) + { + gp->lastContactStep = hSim->prms.contactSteps; + gp->lastContact = Guppie::CORPSE; + gp->energy += ((Corpse *)bd->GetUserData())->energy; + gp->fitness += hSim->prms.forCorpse * (((Corpse *)bd->GetUserData())->energy / hSim->prms.etFromCorpse); + ((Corpse *)bd->GetUserData())->toBeDestroyed = true; + if (hSim->camera.trgtBody == bd) + { + hSim->camera.prevCrds = hSim->camera.currentCrds; + hSim->camera.step = Params::CAM_STEPS; + hSim->camera.trgtBody = gp->body; + } + } + } + } + } + + if (gp->energy > hSim->prms.maxEnergy) + { + gp->energy = hSim->prms.maxEnergy; + } + } + // If both bodies are guppies + else if (gp1 && gp2) + { + sf::Transform trans1; + sf::Transform trans2; + trans1.translate(gp1->body->GetPosition().x, gp1->body->GetPosition().y); + trans1.rotate(gp1->body->GetAngle() * Params::RAD_DGRS); + trans2.translate(gp2->body->GetPosition().x, gp2->body->GetPosition().y); + trans2.rotate(gp2->body->GetAngle() * Params::RAD_DGRS); + + sf::Vector2f beak1_1 = trans1.transformPoint(hSim->beak1.getPoint(1)); + sf::Vector2f beak1_2 = trans1.transformPoint(hSim->beak2.getPoint(1)); + sf::Vector2f beak2_1 = trans2.transformPoint(hSim->beak1.getPoint(1)); + sf::Vector2f beak2_2 = trans2.transformPoint(hSim->beak2.getPoint(1)); + + float cateteX1_1 = beak1_1.x - gp2->body->GetPosition().x; + float cateteY1_1 = beak1_1.y - gp2->body->GetPosition().y; + float cateteX2_1 = beak2_1.x - gp2->body->GetPosition().x; + float cateteY2_1 = beak2_1.y - gp2->body->GetPosition().y; + float cateteX1_2 = beak1_2.x - gp1->body->GetPosition().x; + float cateteY1_2 = beak1_2.y - gp1->body->GetPosition().y; + float cateteX2_2 = beak2_2.x - gp1->body->GetPosition().x; + float cateteY2_2 = beak2_2.y - gp1->body->GetPosition().y; + + if (cateteX1_1 * cateteX1_1 + cateteY1_1 * cateteY1_1 < hSim->prms.GUPPIE_RAD * hSim->prms.GUPPIE_RAD || + cateteX2_1 * cateteX2_1 + cateteY2_1 * cateteY2_1 < hSim->prms.GUPPIE_RAD * hSim->prms.GUPPIE_RAD) + { + gp1->lastContactStep = hSim->prms.contactSteps; + gp1->lastContact = Guppie::GUPPIE; + gp1->energy += hSim->prms.etFromGuppie; + gp1->fitness += hSim->prms.forGuppie; + + gp2->lastContactStep = hSim->prms.contactSteps; + gp2->lastContact = Guppie::GUPPIE; + gp2->energy -= hSim->prms.etFromGuppie; + } + + if (cateteX1_2 * cateteX1_2 + cateteY1_2 * cateteY1_2 < hSim->prms.GUPPIE_RAD * hSim->prms.GUPPIE_RAD || + cateteX2_2 * cateteX2_2 + cateteY2_2 * cateteY2_2 < hSim->prms.GUPPIE_RAD * hSim->prms.GUPPIE_RAD) + { + gp1->lastContactStep = hSim->prms.contactSteps; + gp1->lastContact = Guppie::GUPPIE; + gp1->energy -= hSim->prms.etFromGuppie; + + gp2->lastContactStep = hSim->prms.contactSteps; + gp2->lastContact = Guppie::GUPPIE; + gp2->energy += hSim->prms.etFromGuppie; + gp2->fitness += hSim->prms.forGuppie; + } + + if (gp1->energy > hSim->prms.maxEnergy) + { + gp1->energy = hSim->prms.maxEnergy; + } + + if (gp2->energy > hSim->prms.maxEnergy) + { + gp2->energy = hSim->prms.maxEnergy; + } + } +} diff --git a/Simulation/ContactListener.hpp b/Simulation/ContactListener.hpp new file mode 100644 index 0000000..4b4d472 --- /dev/null +++ b/Simulation/ContactListener.hpp @@ -0,0 +1,16 @@ +#ifndef __CONTACTLISTENER_HPP__ +#define __CONTACTLISTENER_HPP__ + +#include <Box2D.h> + +class SimBase; + +class ContactListener : public b2ContactListener +{ +public: + void PreSolve(b2Contact *contact, const b2Manifold *oldManifold); + + SimBase *hSim = nullptr; +}; + +#endif // __CONTACTLISTENER_HPP__ diff --git a/Simulation/Corpse.cpp b/Simulation/Corpse.cpp new file mode 100644 index 0000000..82879ed --- /dev/null +++ b/Simulation/Corpse.cpp @@ -0,0 +1,99 @@ +#include "Corpse.hpp" +#include "SimBase.hpp" + +void Corpse::startup(SimBase *sim) +{ + hSim = sim; + radius = Params::CORPSE_RAD; +} + +void Corpse::setup(b2Vec2 pos, b2Vec2 lVel, float angle, float aVel) +{ + if (!body) + { + return; + } + + lifetime = hSim->prms.corpseDecayTime; + energy = hSim->prms.etFromCorpse; + + body->SetTransform(pos, angle); + body->SetLinearVelocity(lVel); + body->SetAngularVelocity(aVel); +} + + +void Corpse::update() +{ + if (!body) + { + return; + } + + if (hSim->prms.corpseDecay) + { + --lifetime; + energy = hSim->prms.etFromCorpse * ((float)lifetime / (float)hSim->prms.corpseDecayTime); + } + + if (!lifetime || toBeDestroyed) + { + destroy(); + } +} + + +void Corpse::destroy() +{ + if (hSim->camera.trgtBody == body) + { + hSim->camera.prevCrds = hSim->camera.currentCrds; + hSim->camera.trgtCrds = hSim->camera.currentCrds; + hSim->camera.step = 0; + hSim->camera.trgtBody = nullptr; + } + + toBeDestroyed = false; + hSim->tank.world.DestroyBody(body); + body = nullptr; + + --hSim->corpseCount; + hSim->text.corpseCnt.setString("Corpse count: " + nts(hSim->corpseCount)); +} + + +void Corpse::draw() +{ + if (!body) + { + return; + } + + sf::Vector2f vSize = hSim->window.getView().getSize(); + sf::Vector2f vCent = hSim->window.getView().getCenter(); + if ( body->GetPosition().x + radius < vCent.x - vSize.x / 2.f || + body->GetPosition().y + radius < vCent.y - vSize.y / 2.f || + body->GetPosition().x - radius > vCent.x + vSize.x / 2.f || + body->GetPosition().y - radius > vCent.y + vSize.y / 2.f ) + { + return; + } + + sf::Color corpseColor = mix(hSim->prms.corpseColor, hSim->prms.worldColor, ((float)lifetime / (float)hSim->prms.corpseDecayTime)); + if (hSim->camera.zoom > Params::CORPSE_RAD / 2.f) + { + hSim->corpsePoint.setFillColor(corpseColor); + hSim->corpsePoint.setPosition(body->GetPosition().x, body->GetPosition().y); + hSim->window.draw(hSim->corpsePoint); + } + else + { + hSim->corpseShell.setOutlineColor(corpseColor); + hSim->corpseShell.setPosition(body->GetPosition().x, body->GetPosition().y); + hSim->corpseNucleus.setFillColor(corpseColor); + hSim->corpseNucleus.setPosition(body->GetPosition().x, body->GetPosition().y); + hSim->corpseNucleus.setRotation(body->GetAngle() * Params::RAD_DGRS); + hSim->window.draw(hSim->corpseShell); + hSim->window.draw(hSim->corpseNucleus); + } +} diff --git a/Simulation/Corpse.hpp b/Simulation/Corpse.hpp new file mode 100644 index 0000000..0153c24 --- /dev/null +++ b/Simulation/Corpse.hpp @@ -0,0 +1,28 @@ +#ifndef __CORPSE_HPP__ +#define __CORPSE_HPP__ + +#include "Entity.hpp" + +class Corpse : public Entity +{ +public: + void startup(SimBase *sim); + void update(); + void draw(); + + bool isCreated() const { return body ? true : false; } + bool toBeDestroyed = false; + + void setup(b2Vec2 pos, b2Vec2 lVel, float angle, float aVel); + +private: + void destroy(); + + unsigned lifetime = 0; + float energy = 0.f; + + friend class ContactListener; + friend class Guppie; +}; + +#endif // __CORPSE_HPP__ diff --git a/Simulation/Entity.cpp b/Simulation/Entity.cpp new file mode 100644 index 0000000..d5ccb75 --- /dev/null +++ b/Simulation/Entity.cpp @@ -0,0 +1,59 @@ +#include "Entity.hpp" +#include "SimBase.hpp" + +void Entity::create() +{ + if (!hSim || body || !radius) + { + return; + } + + b2BodyDef bodyDef; + bodyDef.type = b2_dynamicBody; + bodyDef.position = { hSim->prms.worldRad, hSim->prms.worldRad }; + float hypotenuse = hSim->prms.worldRad - radius; + while (bodyDef.position.x * bodyDef.position.x + bodyDef.position.y * bodyDef.position.y > hypotenuse * hypotenuse) + { + bodyDef.position.x = realRand(-hSim->prms.worldRad, hSim->prms.worldRad); + bodyDef.position.y = realRand(-hSim->prms.worldRad, hSim->prms.worldRad); + } + bodyDef.angle = realRand(0.f, 360.f / Params::RAD_DGRS); + bodyDef.linearDamping = 0.1f; + bodyDef.angularDamping = 0.1f; + bodyDef.allowSleep = true; + bodyDef.userData = this; + + body = hSim->tank.world.CreateBody(&bodyDef); + + b2CircleShape shape; + shape.m_radius = radius; + + b2FixtureDef fixture; + fixture.shape = &shape; + fixture.friction = 1.f; + fixture.restitution = 1.f; + fixture.density = 1.f; + + body->CreateFixture(&fixture); + + if (radius == Params::PELLET_RAD) + { + ++hSim->pelletCount; + hSim->text.pelletCnt.setString("Pellet count: " + nts(hSim->pelletCount) + " / " + nts(hSim->prms.pelletQtty)); + } + else if (radius == Params::CORPSE_RAD) + { + ++hSim->corpseCount; + hSim->text.corpseCnt.setString("Corpse count: " + nts(hSim->corpseCount)); + } + else if (radius == Params::GUPPIE_RAD) + { + ++hSim->guppieCount; + hSim->text.guppieCnt.setString("Guppie count: " + nts(hSim->guppieCount)); + ((Guppie *)this)->energy = hSim->prms.initEnergy; + body->SetLinearDamping(2.f); + body->SetAngularDamping(2.f); + + ((b2CircleShape *)body->GetFixtureList()->GetShape())->m_radius = hSim->prms.EGG_RAD; + } +} diff --git a/Simulation/Entity.hpp b/Simulation/Entity.hpp new file mode 100644 index 0000000..0374611 --- /dev/null +++ b/Simulation/Entity.hpp @@ -0,0 +1,34 @@ +#ifndef __ENTITY_HPP__ +#define __ENTITY_HPP__ + +#include <SFML/Graphics.hpp> +#include <Box2D.h> + +class SimBase; + +class Entity +{ +public: + virtual void startup(SimBase *sim) = 0; + + void create(); + void destroy(); + + virtual void update() = 0; + virtual void draw() = 0; + + float getRadius() const { return radius; } + +protected: + SimBase *hSim = nullptr; + + b2Body *body = nullptr; + float radius = 0.f; + float resistance = 0.f; + + friend class ContactListener; + friend class Guppie; + friend class Camera; +}; + +#endif // __ENTITY_HPP__ diff --git a/Simulation/GraphicObjs.hpp b/Simulation/GraphicObjs.hpp new file mode 100644 index 0000000..8fab10e --- /dev/null +++ b/Simulation/GraphicObjs.hpp @@ -0,0 +1,56 @@ +#ifndef __GRAPHICOBJS_HPP__ +#define __GRAPHICOBJS_HPP__ + +#include <SFML/Graphics.hpp> + +class GraphicObjs +{ +protected: + sf::CircleShape zapperPoint = sf::CircleShape(0, 12); + sf::CircleShape zapperShell; + sf::CircleShape zapperNucleus = sf::CircleShape(0, 5); + + sf::CircleShape pelletPoint = sf::CircleShape(0, 6); + sf::CircleShape pelletShell; + sf::CircleShape pelletNucleus = sf::CircleShape(0, 3); + + sf::CircleShape guppiePoint = sf::CircleShape(0, 6); + sf::CircleShape guppieShell = sf::CircleShape(0, 30); + /////////////// + sf::CircleShape eggPoint = sf::CircleShape(0, 6); + sf::CircleShape guppieEgg = sf::CircleShape(0, 60); + /////////////// + sf::Color guppN; + sf::Vector2f visionCone[31]; + sf::Vector2f skinRadius[180]; + sf::Vector2f senseRadius[180]; + ///////////////////////////// + sf::CircleShape gOuterSkin; + /////////////////////////// + sf::ConvexShape beak1; + sf::ConvexShape beak2; + /////////////////////////// + sf::ConvexShape touchCells[30]; + sf::ConvexShape smellCells[30]; + sf::ConvexShape tailMask; + ////////////////////////////// + sf::CircleShape innerBodyMask; + ///////////////////////////// + sf::ConvexShape thrusters[4]; + sf::ConvexShape thrusterMask; + sf::ConvexShape heart; + sf::ConvexShape heartMask; + sf::ConvexShape heartTop; + sf::ConvexShape heartTri; + sf::ConvexShape glandMask; + sf::ConvexShape gland; + sf::ConvexShape eyeMask; + sf::ConvexShape eyeCells[15]; + sf::ConvexShape eyeCavity; + + sf::CircleShape corpsePoint = sf::CircleShape(0, 6); + sf::CircleShape corpseShell; + sf::CircleShape corpseNucleus = sf::CircleShape(0, 4); +}; + +#endif // __GRAPHICOBJS_HPP__ diff --git a/Simulation/Guppie.cpp b/Simulation/Guppie.cpp new file mode 100644 index 0000000..91788c2 --- /dev/null +++ b/Simulation/Guppie.cpp @@ -0,0 +1,584 @@ +#include "Guppie.hpp" +#include "SimFitness.hpp" + +void Guppie::startup(SimBase *sim) +{ + hSim = sim; + radius = Params::GUPPIE_RAD; +} + +void Guppie::update() +{ + if (!body) + { + return; + } + + // Update states + ++lifetime; + + if (lifetime < hSim->prms.activationDelay) + { + return; + } + + if (lifetime == hSim->prms.activationDelay) + { + isActive = true; + skinColor = hSim->guppN; + glandState = ALPHA; + + ((b2CircleShape *)body->GetFixtureList()->GetShape())->m_radius = hSim->prms.GUPPIE_RAD; + } + + fitness += energy; + + energy -= (1.f / 60.f) + (float)lifetime / (hSim->prms.agingRate * 3600.f); + + if (energy < 0.f) + { + destroy(); + return; + } + + if (lastContactStep) + { + --lastContactStep; + } + + if (skinColor.r) + { + skinColor.r -= 1; + } + if (skinColor.g) + { + skinColor.g -= 1; + } + if (skinColor.b) + { + skinColor.b -= 1; + } + + // Update sensors + sf::Transform trans; + trans.translate(body->GetPosition().x, body->GetPosition().y); + trans.rotate(body->GetAngle() * Params::RAD_DGRS); + // Eye sensors + sf::Vector2f eyePoint = trans.transformPoint(hSim->visionCone[30]); + for (int i = 0; i < 15; ++i) + { + RayCastCallback points[2]; + sf::Color colors[2]; + for (int j = 0; j < 2; ++j) + { + colors[j] = hSim->prms.worldColor; + sf::Vector2f eyeLine = trans.transformPoint(hSim->visionCone[i * 2 + j]); + hSim->tank.world.RayCast(&points[j], b2Vec2(eyePoint.x, eyePoint.y), b2Vec2(eyeLine.x, eyeLine.y)); + if (points[j].m_fixture) + { + if (points[j].m_fixture->GetBody()->GetType() == b2_staticBody) + { + colors[j] = mix(hSim->prms.worldColor, hSim->prms.zapperColor, points[j].m_fraction); + } + else + { + Entity *entity = (Entity *)points[j].m_fixture->GetBody()->GetUserData(); + if (entity->radius == Params::ZAPPER_RAD) + { + colors[j] = mix(hSim->prms.worldColor, hSim->prms.zapperColor, points[j].m_fraction); + } + else if (entity->radius == Params::EGG_RAD) + { + colors[j] = mix(hSim->prms.worldColor, hSim->guppN, points[j].m_fraction); + } + else if (entity->radius == Params::PELLET_RAD) + { + colors[j] = mix(hSim->prms.worldColor, hSim->prms.pelletColor, points[j].m_fraction); + } + else if (entity->radius == Params::CORPSE_RAD) + { + sf::Color corpseColor = mix(hSim->prms.corpseColor, hSim->prms.worldColor, ((float)((Corpse *)entity)->lifetime / (float)hSim->prms.corpseDecayTime)); + colors[j] = mix(hSim->prms.worldColor, corpseColor, points[j].m_fraction); + } + else if (entity->radius == Params::GUPPIE_RAD) + { + if (!((Guppie *)entity)->isActive) + { + colors[j] = mix(hSim->prms.worldColor, hSim->guppN, points[j].m_fraction); + } + else + { + colors[j] = mix(hSim->prms.worldColor, ((Guppie *)entity)->skinColor, points[j].m_fraction); + } + } + } + } + } + + sf::Color sum = mix(colors[0], colors[1], 0.5f); + eyeR[i] = (float)sum.r / 256.f; + eyeG[i] = (float)sum.g / 256.f; + eyeB[i] = (float)sum.b / 256.f; + } + // Skin sensors + for (int i = 0; i < 30; ++i) + { + RayCastCallback points[6]; + float valueTouch = 0.f; +// float valueSmell = 0.f; + for (int j = 0; j < 6; ++j) + { + sf::Vector2f skinPoint = trans.transformPoint(hSim->skinRadius[i * 6 + j]); + sf::Vector2f senseLine = trans.transformPoint(hSim->senseRadius[i * 6 + j]); + hSim->tank.world.RayCast(&points[j], b2Vec2(skinPoint.x, skinPoint.y), b2Vec2(senseLine.x, senseLine.y)); + if (points[j].m_fixture) + { + valueTouch += 1.f - points[j].m_fraction; + // FOR SWARM SIMULATION STYLE +// if (points[j].m_fixture->GetBody()->GetType() == b2_dynamicBody) +// { +// Entity *entity = (Entity *)points[j].m_fixture->GetBody()->GetUserData(); +// if (entity->radius == Params::GUPPIE_RAD) +// { +// if (((Guppie *)entity)->isActive) +// { +// if (((Guppie *)entity)->glandState == glandState) +// { +// valueSmell += 1.f - points[j].m_fraction; +// } +// } +// } +// } + } + } + + touch[i] = valueTouch / 6.f; +// smell[i] = valueSmell / 6.f; + } + // Current color sensor + cColorR = (float)skinColor.r / 255.f; + cColorG = (float)skinColor.g / 255.f; + cColorB = (float)skinColor.b / 255.f; + // Current gland color sensor +// cGland1 = 0.f; +// cGland2 = 0.f; +// cGland3 = 0.f; +// if (glandState == ALPHA) +// { +// cGland1 = 1.f; +// } +// else if (glandState == BETTA) +// { +// cGland2 = 1.f; +// } +// else +// { +// cGland3 = 1.f; +// } + // Speed sensors + b2Vec2 axisSpeed = body->GetLinearVelocity(); + sf::Transform rotat; + rotat.rotate(body->GetAngle() * Params::RAD_DGRS); + sf::Vector2f fwdDirect = rotat.transformPoint(sf::Vector2f(0.f, -1.f)); + sf::Vector2f sdeDirect = rotat.transformPoint(sf::Vector2f(1.f, 0.f)); + fwdSpeed = sigmoid(axisSpeed.x * fwdDirect.x + axisSpeed.y * fwdDirect.y); + sideSpeed = sigmoid(axisSpeed.x * sdeDirect.x + axisSpeed.y * sdeDirect.y); + // Rotation sensor + rotation = sigmoid(body->GetAngularVelocity()); + // Low energy sensor + lowEnergy = sigmoid(energy / 10.f) * 2.f - 1.f; + + // Merge inputs and get response from neural net + std::vector<float> inputs; + inputs.insert(inputs.end(), eyeR.begin(), eyeR.end()); + inputs.insert(inputs.end(), eyeG.begin(), eyeG.end()); + inputs.insert(inputs.end(), eyeB.begin(), eyeB.end()); + inputs.insert(inputs.end(), touch.begin(), touch.end()); +// inputs.insert(inputs.end(), smell.begin(), smell.end()); + inputs.push_back(cColorR); + inputs.push_back(cColorG); + inputs.push_back(cColorB); +// inputs.push_back(cGland1); +// inputs.push_back(cGland2); +// inputs.push_back(cGland3); + inputs.push_back(fwdSpeed); + inputs.push_back(sideSpeed); + inputs.push_back(rotation); + inputs.push_back(lowEnergy); + + std::vector<float> outputs = neuralNet->io(inputs); + + // Apply outputs + thruster1 = outputs[0]; + thruster2 = outputs[1]; + addClrR = outputs[2]; + addClrG = outputs[3]; + addClrB = outputs[4]; +// addGln1 = outputs[5]; +// addGln2 = outputs[6]; +// addGln3 = outputs[7]; + + // Handle outputs + // Guppies gain fitness by going straight forward + if (thruster1 + thruster2 > 1.f) + { + float forwardness = thruster1 + thruster2 - 1.f; + fitness += forwardness * hSim->prms.forGoingStraight; + } + + // Apply thrust + sf::Vector2f leftThrustPoint = trans.transformPoint(-hSim->prms.thrustRadius, 0.f); + sf::Vector2f rightThrustPoint = trans.transformPoint(hSim->prms.thrustRadius, 0.f); + + thruster1 = thruster1 * 2.f - 1.f; + sf::Vector2f thrLeft = rotat.transformPoint(-hSim->prms.thrustRadius, thruster1 * hSim->prms.thrustForce); + body->ApplyForce(b2Vec2(thrLeft.x, thrLeft.y), b2Vec2(leftThrustPoint.x, leftThrustPoint.y)); + + thruster2 = thruster2 * 2.f - 1.f; + sf::Vector2f thrRight = rotat.transformPoint(hSim->prms.thrustRadius, thruster2 * hSim->prms.thrustForce); + body->ApplyForce(b2Vec2(thrRight.x, thrRight.y), b2Vec2(rightThrustPoint.x, rightThrustPoint.y)); + // Substract energy +// energy -= fabsf(thruster1 / 60.f); +// energy -= fabsf(thruster2 / 60.f); + + // Apply color change + float temp = 0.f; + if ((temp = skinColor.r + addClrR * 8.f) < 255) + { + skinColor.r += temp; + } + else + { + skinColor.r = 255; + } + if ((temp = skinColor.g + addClrG * 8.f) < 255) + { + skinColor.g += temp; + } + else + { + skinColor.g = 255; + } + if ((temp = skinColor.b + addClrB * 8.f) < 255) + { + skinColor.b += temp; + } + else + { + skinColor.b = 255; + } + // Substract energy +// energy -= addClrR / 60.f; +// energy -= addClrG / 60.f; +// energy -= addClrB / 60.f; + // Apply gland change +// nGland1 += addGln1; +// nGland2 += addGln2; +// nGland3 += addGln3; +// bool changeGln = false; +// if (nGland1 > 30.f) +// { +// glandState = ALPHA; +// changeGln = true; +// } +// else if (nGland2 > 30.f) +// { +// glandState = BETTA; +// changeGln = true; +// } +// else if (nGland3 > 30.f) +// { +// glandState = GAMMA; +// changeGln = true; +// } +// if (changeGln) +// { +// nGland1 = 0.f; +// nGland2 = 0.f; +// nGland3 = 0.f; +// } +// // Substract energy +// energy -= addGln1 / 60.f; +// energy -= addGln2 / 60.f; +// energy -= addGln3 / 60.f; + + if (hSim->prms.simStyle == SELECTION_BY_FITNESS) + { + if (fitness > ((SimFitness *)hSim)->fitnessRecord) + { + ((SimFitness *)hSim)->fitnessRecord = fitness; + hSim->text.longestLife.setString("Fitness record: " + nts(((SimFitness *)hSim)->fitnessRecord)); + } + } +} + + +void Guppie::destroy() +{ + if (!body) + { + return; + } + + --hSim->guppieCount; + hSim->text.guppieCnt.setString("Guppie count: " + nts(hSim->guppieCount)); + + // Place a corpse + if (hSim->prms.leaveCorpse) + { + bool vacant = false; + for (auto &i : hSim->corpses) + { + if (!i.isCreated()) + { + i.create(); + i.setup(body->GetPosition(), body->GetLinearVelocity(), body->GetAngle(), body->GetAngularVelocity()); + if (hSim->camera.trgtBody == body) + { + hSim->camera.trgtBody = i.body; + } + vacant = true; + break; + } + } + if (!vacant) + { + hSim->corpses.push_back(Corpse()); + hSim->corpses.back().startup(hSim); + hSim->corpses.back().create(); + hSim->corpses.back().setup(body->GetPosition(), body->GetLinearVelocity(), body->GetAngle(), body->GetAngularVelocity()); + if (hSim->camera.trgtBody == body) + { + hSim->camera.trgtBody = hSim->corpses.back().body; + } + } + } + else if (hSim->camera.trgtBody == body) + { + hSim->camera.trgtBody = nullptr; + } + + hSim->tank.world.DestroyBody(body); + body = nullptr; +} + + +void Guppie::draw() +{ + if (!body) + { + return; + } + + sf::Vector2f vSize = hSim->window.getView().getSize(); + sf::Vector2f vCent = hSim->window.getView().getCenter(); + if ( body->GetPosition().x + radius < vCent.x - vSize.x / 2.f || + body->GetPosition().y + radius < vCent.y - vSize.y / 2.f || + body->GetPosition().x - radius > vCent.x + vSize.x / 2.f || + body->GetPosition().y - radius > vCent.y + vSize.y / 2.f ) + { + return; + } + + if (hSim->camera.zoom > Params::GUPPIE_RAD) + { + float blend = sigmoid((hSim->camera.zoom - Params::GUPPIE_RAD) * 8.f); + if (isActive) + { + hSim->guppiePoint.setFillColor(mix(hSim->prms.worldColor, skinColor, blend)); + } + else + { + hSim->guppiePoint.setFillColor(mix(hSim->prms.worldColor, hSim->guppN, blend)); + } + + hSim->guppiePoint.setPosition(body->GetPosition().x, body->GetPosition().y); + hSim->window.draw(hSim->guppiePoint); + return; + } + + // Position and rotate + float posX = body->GetPosition().x; + float posY = body->GetPosition().y; + float rot = body->GetAngle() * Params::RAD_DGRS; + sf::ConvexShape *shape = nullptr; + + hSim->gOuterSkin.setPosition(posX, posY); + for (shape = &hSim->beak1; shape <= &hSim->tailMask; ++shape) + { + shape->setPosition(posX, posY); + shape->setRotation(rot); + } + hSim->innerBodyMask.setPosition(posX, posY); + hSim->innerBodyMask.setRotation(rot); + for (shape = hSim->thrusters; shape <= &hSim->eyeCavity; ++shape) + { + shape->setPosition(posX, posY); + shape->setRotation(rot); + } + + // Apply colors + // When inactive + if (!isActive) + { + // Skin + hSim->beak1.setFillColor(hSim->guppN); + hSim->beak2.setFillColor(hSim->guppN); + hSim->gOuterSkin.setFillColor(hSim->guppN); + for (int i = 0; i < 15; ++i) + { + hSim->eyeCells[i].setFillColor(hSim->prms.worldColor); + } + for (int i = 0; i < 30; ++i) + { + hSim->touchCells[i].setFillColor(hSim->guppN); + hSim->smellCells[i].setFillColor(hSim->guppN); + } + // Thrusters + for (int i = 0; i < 4; ++i) + { + hSim->thrusters[i].setFillColor(hSim->guppN); + } + // Heart + hSim->heart.setFillColor(hSim->guppN); + hSim->heartTop.setFillColor(hSim->guppN); + hSim->heartTri.setFillColor(hSim->guppN); + hSim->heartMask.setFillColor(hSim->guppN); + // Gland + hSim->gland.setFillColor(hSim->guppN); + } + // When active + else + { + // Outer skin + hSim->beak1.setFillColor(skinColor); + hSim->beak2.setFillColor(skinColor); + hSim->gOuterSkin.setFillColor(skinColor); + // Eye cells + for (int i = 0; i < 15; ++i) + { + sf::Color cellColor; + cellColor.r = eyeR[i] * 255.f; + cellColor.g = eyeG[i] * 255.f; + cellColor.b = eyeB[i] * 255.f; + hSim->eyeCells[i].setFillColor(cellColor); + } + // Skin cells + for (int i = 0; i < 30; ++i) + { + hSim->touchCells[i].setFillColor(mix(hSim->prms.guppieColorI, hSim->guppN, touch[i])); +// hSim->smellCells[i].setFillColor(mix(hSim->prms.guppieColorI, hSim->guppN, smell[i])); + } + // Thrusters + if (thruster1 > 0.f) + { + hSim->thrusters[3].setFillColor(mix(hSim->prms.guppieColorI, hSim->guppN, thruster1)); + hSim->thrusters[1].setFillColor(hSim->guppN); + } + else + { + hSim->thrusters[1].setFillColor(mix(hSim->prms.guppieColorI, hSim->guppN, -thruster1)); + hSim->thrusters[3].setFillColor(hSim->guppN); + } + + if (thruster2 > 0.f) + { + hSim->thrusters[2].setFillColor(mix(hSim->prms.guppieColorI, hSim->guppN, thruster2)); + hSim->thrusters[0].setFillColor(hSim->guppN); + } + else + { + hSim->thrusters[0].setFillColor(mix(hSim->prms.guppieColorI, hSim->guppN, -thruster2)); + hSim->thrusters[2].setFillColor(hSim->guppN); + } + // Heart beat + sf::Color beat = mix(hSim->guppN, hSim->prms.guppieColorI, sinf(((float)lifetime * (1.f + (1.f - lowEnergy))) / 15.f) / 2.f + 0.5f); + hSim->heart.setFillColor(beat); + hSim->heartTop.setFillColor(beat); + hSim->heartTri.setFillColor(beat); + // Stomach + if (lastContactStep) + { + if (lastContact == ZAPPER) + { + hSim->heartMask.setFillColor(mix(hSim->prms.zapperColor, hSim->guppN, (float)lastContactStep / (float)hSim->prms.contactSteps)); + } + else if (lastContact == PELLET) + { + hSim->heartMask.setFillColor(mix(hSim->prms.pelletColor, hSim->guppN, (float)lastContactStep / (float)hSim->prms.contactSteps)); + } + else if (lastContact == CORPSE) + { + hSim->heartMask.setFillColor(mix(hSim->prms.corpseColor, hSim->guppN, (float)lastContactStep / (float)hSim->prms.contactSteps)); + } + else + { + hSim->heartMask.setFillColor(mix(hSim->prms.guppieColorI, hSim->guppN, (float)lastContactStep / (float)hSim->prms.contactSteps)); + } + } + else + { + hSim->heartMask.setFillColor(hSim->guppN); + } + // Gland + if (glandState == ALPHA) + { + hSim->gland.setFillColor(hSim->prms.glandColor1); + } + else if (glandState == BETTA) + { + hSim->gland.setFillColor(hSim->prms.glandColor2); + } + else + { + hSim->gland.setFillColor(hSim->prms.glandColor3); + } + } + + // Draw + hSim->window.draw(hSim->gOuterSkin); + for (shape = &hSim->beak1; shape <= &hSim->tailMask; ++shape) + { + hSim->window.draw(*shape); + } + hSim->window.draw(hSim->innerBodyMask); + for (shape = hSim->thrusters; shape <= &hSim->eyeCavity; ++shape) + { + hSim->window.draw(*shape); + } + + if (hSim->camera.zoom > 0.02f) + { + if (isActive) + { + hSim->guppieShell.setOutlineColor(skinColor); + } + else + { + hSim->guppieShell.setOutlineColor(hSim->guppN); + } + + hSim->guppieShell.setPosition(body->GetPosition().x, body->GetPosition().y); + hSim->window.draw(hSim->guppieShell); + } + + if (!isActive) + { + hSim->guppieEgg.setPosition(body->GetPosition().x, body->GetPosition().y); + hSim->window.draw(hSim->guppieEgg); + } +} + + +void Guppie::clean() +{ + isActive = false; + lifetime = 0; + fitness = 0.; + energy = 0.f; + skinColor = hSim->guppN; +// nGland1 = 0.f; +// nGland2 = 0.f; +// nGland3 = 0.f; + lastContactStep = 0; +} diff --git a/Simulation/Guppie.hpp b/Simulation/Guppie.hpp new file mode 100644 index 0000000..14770ff --- /dev/null +++ b/Simulation/Guppie.hpp @@ -0,0 +1,58 @@ +#ifndef __GUPPIE_HPP__ +#define __GUPPIE_HPP__ + +#include <NeuralNet.hpp> +#include "Entity.hpp" + +class Guppie : public Entity +{ +public: + void startup(SimBase *sim); + void update(); + void draw(); + +private: + void destroy(); + void clean(); + + // State variables + bool isActive = false; + unsigned lifetime = 0; + double fitness = 0; + float energy = 0.f; + sf::Color skinColor; + enum gs { ALPHA = 0, BETTA = 1, GAMMA = 2 } glandState; +// float nGland1 = 0.f; +// float nGland2 = 0.f; +// float nGland3 = 0.f; + enum lc { ZAPPER, PELLET, CORPSE, GUPPIE } lastContact; + unsigned lastContactStep = 0; + + // Sensors + std::vector<float> eyeR = std::vector<float>(15, 0.f); + std::vector<float> eyeG = std::vector<float>(15, 0.f); + std::vector<float> eyeB = std::vector<float>(15, 0.f); + std::vector<float> touch = std::vector<float>(30, 0.f); +// std::vector<float> smell = std::vector<float>(30, 0.f); + float cColorR = 0.f, cColorG = 0.f, cColorB = 0.f; +// float cGland1 = 0.f, cGland2 = 0.f, cGland3 = 0.f; + float fwdSpeed = 0.f; + float sideSpeed = 0.f; + float rotation = 0.f; + float lowEnergy = 0.f; + + // Neural net + std::shared_ptr<NeuralNet> neuralNet; + + // Outputs + float thruster1 = 0.f, thruster2 = 0.f; + float addClrR = 0.f, addClrG = 0.f, addClrB = 0.f; +// float addGln1 = 0.f, addGln2 = 0.f, addGln3 = 0.f; + + friend class ContactListener; + friend class SimFitness; + friend class Entity; + friend class Camera; +}; + +#endif // __GUPPIE_HPP__ diff --git a/Simulation/GuppiesInclude.hpp b/Simulation/GuppiesInclude.hpp new file mode 100644 index 0000000..4a7bf94 --- /dev/null +++ b/Simulation/GuppiesInclude.hpp @@ -0,0 +1,6 @@ +#ifndef __GUPPIESINCLUDE_HPP__ +#define __GUPPIESINCLUDE_HPP__ + +#include "SimFitness.hpp" + +#endif // __GUPPIESINCLUDE_HPP__ diff --git a/Simulation/Params.hpp b/Simulation/Params.hpp new file mode 100644 index 0000000..979cd4e --- /dev/null +++ b/Simulation/Params.hpp @@ -0,0 +1,172 @@ +#ifndef __PARAMS_HPP__ +#define __PARAMS_HPP__ + +#include <sstream> +#include <cmath> + +#include <SFML/Graphics.hpp> +#include <Box2D.h> + +#include <NeuralNetworks.hpp> + + +enum SimStyle +{ + SELECTION_BY_FITNESS +}; + +struct Params +{ + // Variable parameters must be defined before simulation starts + + // Simulation params + SimStyle simStyle = SELECTION_BY_FITNESS; + // If selection by fitness + NeuralNetClass netClass = SIMPLE_RN; + NodeClass nodeClass = MEMORY_CELL; + unsigned npHiddenLayer = 80; + unsigned popSize = 30; + unsigned popQtty = 10; + unsigned elites = 30; + + // World params + float worldRad = 40.f; + + // Entity params + float zapperForce = 50.f;//100 + float zapperTorque = 150.f;//300 + unsigned zapperQtty = 20; + unsigned pelletQtty = 400; + unsigned pelletCreationDelay = 150; + bool startScarce = false; + bool corpseDecay = true; + float corpseDecayTime = 3600.f; + + // Goopy params + unsigned activationDelay = 120; + unsigned contactSteps = 15; + float thrustForce = 5.f; + float thrustRadius = 0.05f; + float initEnergy = 60.f; + float maxEnergy = 180.f; + float agingRate = 600.f; + bool leaveCorpse = true; + + // Energy transfers + float etFromZapper = -20.f; + float etFromPellet = 15.f; + float etFromGuppie = 60.f; + float etFromCorpse = 60.f; + + // Fitness bonus + float forPellet = 9000.f; + float forGuppie = 36000.f; + float forCorpse = 36000.f; + float forGoingStraight = 12.f; + + // Colors + sf::Color clearColor = { 0, 0, 10 }; + sf::Color worldColor = { 0, 0, 0 }; + sf::Color textColor = { 191, 191, 191 }; + sf::Color zapperColor = { 0, 0, 255 }; + sf::Color pelletColor = { 0, 255, 0 }; + sf::Color corpseColor = { 0, 255, 0 }; + sf::Color guppieColorI = { 255, 0, 0 }; + sf::Color glandColor1 = { 255, 127, 0 }; + sf::Color glandColor2 = { 0, 255, 127 }; + sf::Color glandColor3 = { 127, 0, 255 }; + + + // Constant values cant be changed + constexpr static unsigned WIN_WIDTH = 800; + constexpr static unsigned WIN_HEIGHT = 600; + constexpr static unsigned ANTIALIAS = 8; + constexpr static unsigned CAM_STEPS = 8; + constexpr static float ZAPPER_RAD = 5.f; + constexpr static float GUPPIE_RAD = 0.5f; + constexpr static float EGG_RAD = 1.5f; + constexpr static float CORPSE_RAD = 0.25f; + constexpr static float PELLET_RAD = 0.1f; + constexpr static float TXT_SIZE = 12.f; + constexpr static float MAX_ZOOM = 0.002f; + constexpr static float RAD_DGRS = 57.2957795f; +}; + + +// Utility functions +template <class T> +inline std::string nts(T num) +{ + std::ostringstream ss; + ss << num; + return ss.str(); +} + + +inline sf::Vector2f vecMult(const sf::Vector2f &v1, const sf::Vector2f &v2) +{ + return sf::Vector2f(v1.x * v2.x, v1.y * v2.y); +} + + +inline void setCenterRad(sf::CircleShape &circle, float radius) +{ + circle.setRadius(radius); + circle.setOrigin(radius, radius); +} + + +inline sf::Color mix(const sf::Color &clr1, const sf::Color &clr2, float f1 = 0.2f) +{ + sf::Color result; + float f2 = 1.f - f1; + + result.r = clr1.r * f1 + clr2.r * f2; + result.g = clr1.g * f1 + clr2.g * f2; + result.b = clr1.b * f1 + clr2.b * f2; + + return result; +} + + +// Utility classes +class QueryCallback : public b2QueryCallback +{ +public: + bool ReportFixture(b2Fixture* fixture) + { + b2Body* body = fixture->GetBody(); + if (body->GetType() != b2_staticBody) + { + bool inside = fixture->TestPoint(m_point); + if (inside) + { + m_fixture = fixture; + return false; + } + } + + return true; + } + + b2Vec2 m_point; + b2Fixture *m_fixture = nullptr; +}; + + +class RayCastCallback : public b2RayCastCallback +{ +public: + float32 ReportFixture(b2Fixture *fixture, const b2Vec2 &point, const b2Vec2 &normal, float32 fraction) + { + m_fixture = fixture; + m_fraction = fraction; + + return fraction; + } + + float m_fraction = 0.f; + b2Fixture *m_fixture = nullptr; +}; + +#endif // __PARAMS_HPP__ diff --git a/Simulation/Pellet.cpp b/Simulation/Pellet.cpp new file mode 100644 index 0000000..1a911ff --- /dev/null +++ b/Simulation/Pellet.cpp @@ -0,0 +1,66 @@ +#include "Pellet.hpp" +#include "SimBase.hpp" + +void Pellet::startup(SimBase *sim) +{ + hSim = sim; + radius = Params::PELLET_RAD; +} + + +void Pellet::update() +{ + if (!body) + { + return; + } + + if (toBeDestroyed) + { + destroy(); + } +} + + +void Pellet::destroy() +{ + toBeDestroyed = false; + hSim->tank.world.DestroyBody(body); + body = nullptr; + + --hSim->pelletCount; + hSim->text.pelletCnt.setString("Pellet count: " + nts(hSim->pelletCount) + " / " + nts(hSim->prms.pelletQtty)); +} + + +void Pellet::draw() +{ + if (!body) + { + return; + } + + sf::Vector2f vSize = hSim->window.getView().getSize(); + sf::Vector2f vCent = hSim->window.getView().getCenter(); + if ( body->GetPosition().x + radius < vCent.x - vSize.x / 2.f || + body->GetPosition().y + radius < vCent.y - vSize.y / 2.f || + body->GetPosition().x - radius > vCent.x + vSize.x / 2.f || + body->GetPosition().y - radius > vCent.y + vSize.y / 2.f ) + { + return; + } + + if (hSim->camera.zoom > Params::PELLET_RAD / 2.f) + { + hSim->pelletPoint.setPosition(body->GetPosition().x, body->GetPosition().y); + hSim->window.draw(hSim->pelletPoint); + } + else + { + hSim->pelletShell.setPosition(body->GetPosition().x, body->GetPosition().y); + hSim->pelletNucleus.setPosition(body->GetPosition().x, body->GetPosition().y); + hSim->pelletNucleus.setRotation(body->GetAngle() * hSim->prms.RAD_DGRS); + hSim->window.draw(hSim->pelletShell); + hSim->window.draw(hSim->pelletNucleus); + } +} diff --git a/Simulation/Pellet.hpp b/Simulation/Pellet.hpp new file mode 100644 index 0000000..f3c31f8 --- /dev/null +++ b/Simulation/Pellet.hpp @@ -0,0 +1,20 @@ +#ifndef __PELLET_HPP__ +#define __PELLET_HPP__ + +#include "Entity.hpp" + +class Pellet : public Entity +{ +public: + void startup(SimBase *sim); + void update(); + void draw(); + + bool isCreated() const { return body ? true : false; } + bool toBeDestroyed = false; + +private: + void destroy(); +}; + +#endif // __PELLET_HPP__ diff --git a/Simulation/SimBase.hpp b/Simulation/SimBase.hpp new file mode 100644 index 0000000..83e7f4c --- /dev/null +++ b/Simulation/SimBase.hpp @@ -0,0 +1,70 @@ +#ifndef __SIMBASE_HPP__ +#define __SIMBASE_HPP__ + +//#include <fstream> +#include <list> + +#include "Params.hpp" +#include "GraphicObjs.hpp" + +#include "Camera.hpp" +#include "Tank.hpp" +#include "TextDisplay.hpp" +#include "Zapper.hpp" +#include "Pellet.hpp" +#include "Guppie.hpp" +#include "Corpse.hpp" + +class SimBase : public GraphicObjs +{ +public: + bool createNew(const Params &usrPrms); + //bool load(const std::ifstream &file); + void execute(); + +protected: + void prepareGraphics(); + void update(); + void draw(); + + virtual bool startSpecs() = 0; + virtual void updateSpecs() = 0; + //void onClose(); + + Params prms; + sf::RenderWindow window; + Camera camera; + Tank tank; + TextDisplay text; + + sf::Clock timer; + unsigned frameCounter = 0; + + bool paused = false; + bool gfx = true; + bool displayText = true; + bool vSync = true; + bool fullscreen = false; + unsigned step = 0; + + unsigned pelletCount = 0; + unsigned guppieCount = 0; + unsigned corpseCount = 0; + + std::list<Zapper> zappers; + std::list<Pellet> pellets; + std::list<Guppie> guppies; + std::list<Corpse> corpses; + + friend class Camera; + friend class Tank; + friend class ContactListener; + friend class TextDisplay; + friend class Entity; + friend class Zapper; + friend class Pellet; + friend class Corpse; + friend class Guppie; +}; + +#endif // __SIMBASE_HPP__ diff --git a/Simulation/SimBase_CreateNew.cpp b/Simulation/SimBase_CreateNew.cpp new file mode 100644 index 0000000..de752aa --- /dev/null +++ b/Simulation/SimBase_CreateNew.cpp @@ -0,0 +1,49 @@ +#include "SimBase.hpp" + +bool SimBase::createNew(const Params &usrPrms) +{ + prms = usrPrms; + + seedRand(); + + sf::VideoMode vmd = sf::VideoMode(prms.WIN_WIDTH, prms.WIN_HEIGHT); + sf::ContextSettings ctx = sf::ContextSettings(0, 0, prms.ANTIALIAS, 2, 0); + window.create(vmd, "Neural Guppies - 0.1 beta", sf::Style::Default, ctx); + window.setVerticalSyncEnabled(vSync); + + sf::Image icon; + icon.loadFromFile("gfx/icon.png"); + window.setIcon(32, 32, icon.getPixelsPtr()); + + prepareGraphics(); + + camera.create(this); + tank.create(this); + + zappers.resize(prms.zapperQtty); + for (auto &i : zappers) + { + i.startup(this); + i.create(); + } + + pellets.resize(prms.pelletQtty); + for (auto &i : pellets) + { + i.startup(this); + } + if (!prms.startScarce) + { + for (auto &i : pellets) + { + i.create(); + } + } + + if (!startSpecs()) + { + return false; + } + + return true; +} diff --git a/Simulation/SimBase_Draw.cpp b/Simulation/SimBase_Draw.cpp new file mode 100644 index 0000000..0bfe915 --- /dev/null +++ b/Simulation/SimBase_Draw.cpp @@ -0,0 +1,40 @@ +#include "SimBase.hpp" + +void SimBase::draw() +{ + window.clear(prms.clearColor); + + camera.update(); + + if (gfx) + { + tank.draw(); + + for (auto &i : zappers) + { + i.draw(); + } + + for (auto &i : pellets) + { + i.draw(); + } + + for (auto &i : guppies) + { + i.draw(); + } + + for (auto &i : corpses) + { + i.draw(); + } + } + + if (displayText) + { + text.print(); + } + + window.display(); +} diff --git a/Simulation/SimBase_Execute.cpp b/Simulation/SimBase_Execute.cpp new file mode 100644 index 0000000..eb3166a --- /dev/null +++ b/Simulation/SimBase_Execute.cpp @@ -0,0 +1,152 @@ +#include "SimBase.hpp" + +void SimBase::execute() +{ + while (window.isOpen()) + { + update(); + + sf::Event event; + while (window.pollEvent(event)) + { + if (event.type == sf::Event::Closed) + { + window.close(); + } + + if (event.type == sf::Event::Resized) + { + camera.onResize(); + } + + if (event.type == sf::Event::MouseButtonPressed) + { + if (event.mouseButton.button == sf::Mouse::Button::Left) + { + camera.onClick(); + } + } + + if (event.type == sf::Event::MouseWheelMoved) + { + sf::Vector2f mousePos = window.mapPixelToCoords(sf::Mouse::getPosition(window)); + float f1 = 2.f / 10.f; + float f2 = 1.f - f1; + + if (event.mouseWheel.delta > 0) + { + sf::Vector2f target = camera.getCrds() * f2 + mousePos * f1; + camera.shift(target, 1); + } + else + { + mousePos = camera.getCrds() * 2.f - mousePos; + sf::Vector2f target = camera.getCrds() * f2 + mousePos * f1; + camera.shift(target, -1); + } + } + + if (event.type == sf::Event::KeyPressed) + { + if (event.key.code == sf::Keyboard::Escape) + { + window.close(); + } + + if (event.key.code == sf::Keyboard::Space) + { + paused = !paused; + text.simState.setString("Sim. state: " + std::string(paused ? "PAUSED" : "RUNNING")); + } + + if (event.key.code == sf::Keyboard::G) + { + gfx = !gfx; + text.gfx.setString("Graphics: " + std::string(gfx ? "ON" : "OFF")); + } + + if (event.key.code == sf::Keyboard::T) + { + displayText = !displayText; + } + + if (event.key.code == sf::Keyboard::V) + { + vSync = !vSync; + window.setVerticalSyncEnabled(vSync); + text.vSync.setString("V. Sync: " + std::string(vSync ? "ON" : "OFF")); + } + + if (event.key.code == sf::Keyboard::Z) + { + camera.shift(sf::Vector2f(), -2); + } + + if (event.key.code == sf::Keyboard::F11) + { + sf::ContextSettings ctx = window.getSettings(); + std::string title = "Neural Guppies - 0.1 beta"; + + if (fullscreen) + { + fullscreen = false; + window.create(sf::VideoMode(prms.WIN_WIDTH, prms.WIN_HEIGHT), title, sf::Style::Default, ctx); + sf::Image icon; + icon.loadFromFile("gfx/icon.png"); + window.setIcon(32, 32, icon.getPixelsPtr()); + } + else + { + fullscreen = true; + window.create(sf::VideoMode::getDesktopMode(), title, sf::Style::Fullscreen, ctx); + } + + window.setVerticalSyncEnabled(vSync); + camera.onResize(); + } + + if (event.key.code == sf::Keyboard::I) + { + camera.shift(camera.getCrds(), 1, false); + } + + if (event.key.code == sf::Keyboard::O) + { + camera.shift(camera.getCrds(), -1, false); + } + + if ( event.key.code == sf::Keyboard::W || + event.key.code == sf::Keyboard::A || + event.key.code == sf::Keyboard::S || + event.key.code == sf::Keyboard::D ) + { + float shift = 30.f * camera.getZoom(); + sf::Vector2f shiftVec; + + if (event.key.code == sf::Keyboard::W) + { + shiftVec.y = -shift; + } + else if (event.key.code == sf::Keyboard::A) + { + shiftVec.x = -shift; + } + else if (event.key.code == sf::Keyboard::S) + { + shiftVec.y = shift; + } + else if (event.key.code == sf::Keyboard::D) + { + shiftVec.x = shift; + } + + camera.shift(camera.getCrds() + shiftVec); + } + } + } + + draw(); + } + + //onClose(); +} diff --git a/Simulation/SimBase_PrepareGraphics.cpp b/Simulation/SimBase_PrepareGraphics.cpp new file mode 100644 index 0000000..ba7b56d --- /dev/null +++ b/Simulation/SimBase_PrepareGraphics.cpp @@ -0,0 +1,269 @@ +#include "SimBase.hpp" + +void SimBase::prepareGraphics() +{ + // Prepare zappers + setCenterRad(zapperShell, prms.ZAPPER_RAD); + zapperShell.setPointCount(180); + zapperShell.setFillColor(mix(prms.zapperColor, prms.worldColor)); + zapperShell.setOutlineColor(prms.zapperColor); + setCenterRad(zapperNucleus, prms.ZAPPER_RAD); + zapperNucleus.setFillColor(prms.zapperColor); + + // Prepare pellets + setCenterRad(pelletShell, prms.PELLET_RAD); + pelletShell.setPointCount(60); + pelletShell.setFillColor(mix(prms.pelletColor, prms.worldColor)); + pelletShell.setOutlineColor(prms.pelletColor); + setCenterRad(pelletNucleus, prms.PELLET_RAD); + pelletNucleus.setFillColor(prms.pelletColor); + + // Prepare corpses + setCenterRad(corpseShell, prms.CORPSE_RAD); + corpseShell.setPointCount(60); + corpseShell.setFillColor(mix(prms.corpseColor, prms.worldColor)); + corpseShell.setOutlineColor(prms.corpseColor); + setCenterRad(corpseNucleus, prms.CORPSE_RAD); + corpseNucleus.setFillColor(prms.corpseColor); + + // Prepare guppies + guppieShell.setFillColor(sf::Color::Transparent); + guppN = mix(prms.guppieColorI, prms.worldColor, 0.1f); + // Prepare egg + guppieEgg.setFillColor(sf::Color::Transparent); + guppieEgg.setOutlineColor(guppN); + guppieEgg.setRadius(prms.EGG_RAD); + // Prepare vision cone shape + sf::CircleShape fovTempA = sf::CircleShape(10.f, 360); + sf::ConvexShape fovTemp; + fovTemp.setPointCount(360); + for (int i = 0; i < 360; ++i) + { + fovTemp.setPoint(i, fovTempA.getPoint(i) - sf::Vector2f(10.f, 10.f + prms.GUPPIE_RAD)); + } + for (int i = 0; i < 15; ++i) + { + visionCone[i] = fovTemp.getPoint(i * 2 + 331); + } + for (int i = 0; i < 15; ++i) + { + visionCone[i + 15] = fovTemp.getPoint(i * 2 + 1); + } + visionCone[30] = sf::Vector2f(0.f, -prms.GUPPIE_RAD); + // Prepare beaks + beak1.setPointCount(3); + beak1.setPoint(0, sf::Vector2f(0.f, -0.5f)); + beak1.setPoint(1, sf::Vector2f(0.0116f, -0.5201f)); + beak1.setPoint(2, sf::Vector2f(0.0236f, -0.4994f)); + beak2.setPointCount(3); + beak2.setPoint(0, sf::Vector2f(0.f, -0.5f)); + beak2.setPoint(1, sf::Vector2f(-0.0116f, -0.5201f)); + beak2.setPoint(2, sf::Vector2f(-0.0236f, -0.4994f)); + // Prepare skin radius shape + sf::CircleShape sknTempA = sf::CircleShape(prms.GUPPIE_RAD, 360); + sf::ConvexShape sknTemp; + sknTemp.setPointCount(360); + for (int i = 0; i < 360; ++i) + { + sknTemp.setPoint(i, sknTempA.getPoint(i) - sf::Vector2f(prms.GUPPIE_RAD, prms.GUPPIE_RAD)); + } + for (int i = 0; i < 180; ++i) + { + skinRadius[i] = sknTemp.getPoint(i * 2 + 1); + } + // Prepare sense radius shape + sf::CircleShape snsTempA = sf::CircleShape(10.f, 360); + sf::ConvexShape snsTemp; + snsTemp.setPointCount(360); + for (int i = 0; i < 360; ++i) + { + snsTemp.setPoint(i, snsTempA.getPoint(i) - sf::Vector2f(10.f, 10.f)); + } + for (int i = 0; i < 180; ++i) + { + senseRadius[i] = snsTemp.getPoint(i * 2 + 1); + } + // Draw skin + gOuterSkin.setPointCount(180); + setCenterRad(gOuterSkin, prms.GUPPIE_RAD); + // Draw touch and smell cells + sf::CircleShape tcTemp = sf::CircleShape(prms.GUPPIE_RAD - 0.04f, 180); + tcTemp.setOrigin(prms.GUPPIE_RAD - 0.04f, prms.GUPPIE_RAD - 0.04f); + sf::CircleShape scTemp = sf::CircleShape(prms.GUPPIE_RAD - 0.06f, 180); + scTemp.setOrigin(prms.GUPPIE_RAD - 0.06f, prms.GUPPIE_RAD - 0.06f); + for (int i = 0; i < 30; ++i) + { + touchCells[i].setPointCount(8); + smellCells[i].setPointCount(8); + for (int j = 0; j < 7; ++j) + { + touchCells[i].setPoint(j, tcTemp.getPoint((i * 6 + j) % 180)); + smellCells[i].setPoint(j, scTemp.getPoint((i * 6 + j) % 180)); + } + touchCells[i].setPoint(7, tcTemp.getOrigin()); + touchCells[i].setOrigin(tcTemp.getOrigin()); + smellCells[i].setPoint(7, scTemp.getOrigin()); + smellCells[i].setOrigin(scTemp.getOrigin()); + smellCells[i].setFillColor(guppN); + } + // Draw inner body masks + tailMask.setFillColor(guppN); + tailMask.setPointCount(3); + tailMask.setPoint(0, sf::Vector2f(0.f, 0.5f)); + tailMask.setPoint(1, sf::Vector2f(-0.0478f, 0.4173f)); + tailMask.setPoint(2, sf::Vector2f( 0.0478f, 0.4173f)); + innerBodyMask.setFillColor(guppN); + innerBodyMask.setPointCount(180); + setCenterRad(innerBodyMask, prms.GUPPIE_RAD - 0.08f); + // Draw thrusters + sf::CircleShape thTemp = sf::CircleShape(prms.GUPPIE_RAD - 0.1f, 180); + for (int i = 0; i < 4; ++i) + { + thrusters[i].setPointCount(41); + thrusters[i].setOrigin(prms.GUPPIE_RAD - 0.1f, prms.GUPPIE_RAD - 0.1f); + } + thrusters[0].setPoint(0, sf::Vector2f(0.4863f, 0.0094f)); + for (int i = 0; i < 38; ++i) + { + thrusters[0].setPoint(i + 1, thTemp.getPoint(i + 7)); + } + thrusters[0].setPoint(39, sf::Vector2f(0.7999f, 0.39f)); + thrusters[0].setPoint(40, sf::Vector2f(prms.GUPPIE_RAD - 0.1f, 0.39f)); + for (int i = 0; i < 41; ++i) + { + sf::Vector2f p1 = vecMult(thrusters[0].getPoint(i), sf::Vector2f(-1.f, 1.f)) + sf::Vector2f((prms.GUPPIE_RAD - 0.1f) * 2.f, 0.f); + sf::Vector2f p2 = vecMult(thrusters[0].getPoint(i), sf::Vector2f(1.f, -1.f)) + sf::Vector2f(0.f, (prms.GUPPIE_RAD - 0.1f) * 2.f); + sf::Vector2f p3 = vecMult(thrusters[0].getPoint(i), sf::Vector2f(-1.f, -1.f)) + sf::Vector2f((prms.GUPPIE_RAD - 0.1f) * 2.f, (prms.GUPPIE_RAD - 0.1f) * 2.f); + thrusters[1].setPoint(i, p1); + thrusters[2].setPoint(i, p2); + thrusters[3].setPoint(i, p3); + } + // Draw thruster mask + thrusterMask.setFillColor(guppN); + thrusterMask.setPointCount(48); + thrusterMask.setPoint(0, sf::Vector2f(0.0863f, -0.3906f)); + thrusterMask.setPoint(1, sf::Vector2f(0.214f, -0.1694f)); + thrusterMask.setPoint(2, sf::Vector2f(0.2148f, -0.1679f)); + thrusterMask.setPoint(3, sf::Vector2f(0.2237f, -0.1509f)); + thrusterMask.setPoint(4, sf::Vector2f(0.2313f, -0.1335f)); + thrusterMask.setPoint(5, sf::Vector2f(0.2377f, -0.1159f)); + thrusterMask.setPoint(6, sf::Vector2f(0.2429f, -0.0981f)); + thrusterMask.setPoint(7, sf::Vector2f(0.2471f, -0.0803f)); + thrusterMask.setPoint(8, sf::Vector2f(0.2504f, -0.0624f)); + thrusterMask.setPoint(9, sf::Vector2f(0.2527f, -0.0446f)); + thrusterMask.setPoint(10, sf::Vector2f(0.2542f, -0.0267f)); + thrusterMask.setPoint(11, sf::Vector2f(0.2549f, -0.01f)); + for (int i = 0; i < 12; ++i) + { + thrusterMask.setPoint(i + 12, vecMult(thrusterMask.getPoint(11 - i), sf::Vector2f(1.f, -1.f))); + } + for (int i = 0; i < 24; ++i) + { + thrusterMask.setPoint(i + 24, vecMult(thrusterMask.getPoint(23 - i), sf::Vector2f(-1.f, 1.f))); + } + // Draw heart + heart.setPointCount(43); + heart.setPoint(0, sf::Vector2f(0.1883f, -0.1321f)); + heart.setPoint(1, sf::Vector2f(0.1901f, -0.1282f)); + heart.setPoint(2, sf::Vector2f(0.1962f, -0.1133f)); + heart.setPoint(3, sf::Vector2f(0.2013f, -0.0982f)); + heart.setPoint(4, sf::Vector2f(0.2055f, -0.0830f)); + heart.setPoint(5, sf::Vector2f(0.2088f, -0.0679f)); + heart.setPoint(6, sf::Vector2f(0.2114f, -0.0527f)); + heart.setPoint(7, sf::Vector2f(0.2132f, -0.0376f)); + heart.setPoint(8, sf::Vector2f(0.2144f, -0.0225f)); + heart.setPoint(9, sf::Vector2f(0.2149f, -0.0075f)); + for (int i = 0; i < 9; ++i) + { + heart.setPoint(i + 10, vecMult(heart.getPoint(9 - i), sf::Vector2f(1.f, -1.f))); + } + heart.setPoint(19, sf::Vector2f(0.1829f, 0.1429f)); + heart.setPoint(20, sf::Vector2f(0.1793f, 0.1494f)); + heart.setPoint(21, sf::Vector2f(0.f, 0.46f)); + for (int i = 0; i < 21; ++i) + { + heart.setPoint(i + 22, vecMult(heart.getPoint(20 - i), sf::Vector2f(-1.f, 1.f))); + } + // Draw heart mask + heartMask.setFillColor(guppN); + heartMask.setPointCount(37); + heartMask.setPoint(0, sf::Vector2f(0.1655f, -0.078f)); + heartMask.setPoint(1, sf::Vector2f(0.168f, -0.0679f)); + heartMask.setPoint(2, sf::Vector2f(0.1705f, -0.0554f)); + heartMask.setPoint(3, sf::Vector2f(0.1723f, -0.043f)); + heartMask.setPoint(4, sf::Vector2f(0.1737f, -0.0306f)); + heartMask.setPoint(5, sf::Vector2f(0.1745f, -0.0183f)); + heartMask.setPoint(6, sf::Vector2f(0.1749f, -0.0061f)); + for (int i = 0; i < 6; ++i) + { + heartMask.setPoint(i + 7, vecMult(heartMask.getPoint(6 - i), sf::Vector2f(1.f, -1.f))); + } + heartMask.setPoint(13, sf::Vector2f(0.1648f, 0.0804f)); + heartMask.setPoint(14, sf::Vector2f(0.1610f, 0.093f)); + heartMask.setPoint(15, sf::Vector2f(0.1563f, 0.1055f)); + heartMask.setPoint(16, sf::Vector2f(0.1508f, 0.1178f)); + heartMask.setPoint(17, sf::Vector2f(0.1447f, 0.1294f)); + heartMask.setPoint(18, sf::Vector2f(0.f, 0.38f)); + for (int i = 0; i < 18; ++i) + { + heartMask.setPoint(i + 19, vecMult(heartMask.getPoint(17 - i), sf::Vector2f(-1.f, 1.f))); + } + // Draw heart top + heartTop.setPointCount(8); + heartTop.setPoint(0, sf::Vector2f(-0.1655f, -0.0780f)); + heartTop.setPoint(1, sf::Vector2f(-0.1401f, -0.0689f)); + heartTop.setPoint(2, sf::Vector2f(-0.1097f, -0.0601f)); + heartTop.setPoint(3, sf::Vector2f(-0.1025f, -0.0584f)); + for (int i = 0; i < 4; ++i) + { + heartTop.setPoint(i + 4, vecMult(heartTop.getPoint(3 - i), sf::Vector2f(-1.f, 1.f))); + } + // Draw heart triangle + heartTri.setPointCount(3); + heartTri.setPoint(0, sf::Vector2f(-0.1025f, -0.0584f)); + heartTri.setPoint(1, sf::Vector2f(0.f, 0.0935f)); + heartTri.setPoint(2, sf::Vector2f(0.1025f, -0.0584f)); + // Draw gland mask + glandMask.setFillColor(guppN); + glandMask.setPointCount(3); + glandMask.setPoint(0, sf::Vector2f(-0.0783f, -0.0942f)); + glandMask.setPoint(1, sf::Vector2f(0.f, 0.022f)); + glandMask.setPoint(2, sf::Vector2f(0.0783f, -0.0942f)); + // Draw gland + gland.setPointCount(3); + gland.setPoint(0, sf::Vector2f(-0.0513f, -0.0899f)); + gland.setPoint(1, sf::Vector2f(0.f, -0.0138f)); + gland.setPoint(2, sf::Vector2f(0.0513f, -0.0899f)); + // Draw eye mask + sf::CircleShape emTemp = sf::CircleShape(0.4133f, 180); + eyeMask.setFillColor(guppN); + eyeMask.setOrigin(0.4133f, 0.9133f); + eyeMask.setPointCount(32); + for (int i = 0; i < 31; ++i) + { + eyeMask.setPoint(i, emTemp.getPoint(i + 75)); + } + eyeMask.setPoint(31, sf::Vector2f(0.4133f, 0.4133f)); + // Draw eye cells + sf::CircleShape ecTemp = sf::CircleShape(0.3933f, 180); + for (int i = 0; i < 15; ++i) + { + eyeCells[i].setPointCount(4); + eyeCells[i].setOrigin(0.3933f, 0.8933f); + for (int j = 0; j < 3; ++j) + { + eyeCells[i].setPoint(j, ecTemp.getPoint(i * 2 + j + 75)); + } + eyeCells[i].setPoint(3, sf::Vector2f(0.3933f, 0.3933f)); + } + // Draw eye cavity + sf::CircleShape ekTemp = sf::CircleShape(0.3333f, 180); + eyeCavity.setPointCount(32); + eyeCavity.setFillColor(prms.worldColor); + eyeCavity.setOrigin(0.3333f, 0.8333f); + for (int i = 0; i < 31; ++i) + { + eyeCavity.setPoint(i, ekTemp.getPoint(i + 75)); + } + eyeCavity.setPoint(31, sf::Vector2f(0.3333f, 0.3333f)); +} diff --git a/Simulation/SimBase_Update.cpp b/Simulation/SimBase_Update.cpp new file mode 100644 index 0000000..fc50c58 --- /dev/null +++ b/Simulation/SimBase_Update.cpp @@ -0,0 +1,55 @@ +#include "SimBase.hpp" + +void SimBase::update() +{ + if (paused) + { + camera.update(); + return; + } + + ++step; + text.steps.setString("Steps: " + nts(step)); + + if (timer.getElapsedTime().asSeconds() > 1.f) + { + timer.restart(); + text.fps.setString("FPS: " + nts(frameCounter)); + frameCounter = 0; + } + else + { + ++frameCounter; + } + + for (auto &i : zappers) + { + i.update(); + } + + // Create new pellet if needed + if (pelletCount < prms.pelletQtty && !(step % prms.pelletCreationDelay)) + { + for (auto &i : pellets) + { + if (!i.isCreated()) + { + i.create(); + break; + } + } + } + for (auto &i : pellets) + { + i.update(); + } + + for (auto &i : corpses) + { + i.update(); + } + + updateSpecs(); + + tank.updateAllPhysics(); +} diff --git a/Simulation/SimFitness.hpp b/Simulation/SimFitness.hpp new file mode 100644 index 0000000..7eb7974 --- /dev/null +++ b/Simulation/SimFitness.hpp @@ -0,0 +1,26 @@ +#ifndef __SIMFITNESS_HPP__ +#define __SIMFITNESS_HPP__ + +#include <Population.hpp> +#include <SingleMLP.hpp> +#include <DualMLP.hpp> +#include <SimpleRN.hpp> +#include <FullyRN.hpp> + +#include "SimBase.hpp" + +class SimFitness : public SimBase +{ + bool startSpecs(); + void updateSpecs(); + + std::unique_ptr<Population> population; + + unsigned currentPopulation = 0; + unsigned currentGeneration = 0; + unsigned fitnessRecord = 0; + + friend class Guppie; +}; + +#endif // __SIMFITNESS_HPP__ diff --git a/Simulation/SimFitness_StartSpecs.cpp b/Simulation/SimFitness_StartSpecs.cpp new file mode 100644 index 0000000..8fe1a41 --- /dev/null +++ b/Simulation/SimFitness_StartSpecs.cpp @@ -0,0 +1,60 @@ +#include "SimFitness.hpp" + +bool SimFitness::startSpecs() +{ + if (!text.startup(this, SELECTION_BY_FITNESS)) + { + return false; + } + + std::unique_ptr<NeuralNet> dummy; + if (prms.netClass == SINGLE_MLP) + { + dummy = std::unique_ptr<NeuralNet>(new SingleMLP(82, prms.npHiddenLayer, 5, prms.nodeClass, true)); + } + else if (prms.netClass == DUAL_MLP) + { + dummy = std::unique_ptr<NeuralNet>(new DualMLP(82, prms.npHiddenLayer, 5, prms.nodeClass, true)); + } + else if (prms.netClass == SIMPLE_RN) + { + dummy = std::unique_ptr<NeuralNet>(new SimpleRN(82, prms.npHiddenLayer, 5, prms.nodeClass, true)); + } + else if (prms.netClass == FULLY_RN) + { + dummy = std::unique_ptr<NeuralNet>(new FullyRN(82, prms.npHiddenLayer, 5, prms.nodeClass, true)); + } + unsigned chromosomeSize = dummy->getChromosomeSize(); + population = std::unique_ptr<Population>(new Population(prms.popQtty * prms.popSize, prms.elites, chromosomeSize)); + + // Set first population + guppies.resize(prms.popSize); + unsigned index = 0; + for (auto &i : guppies) + { + i.startup(this); + if (prms.netClass == SINGLE_MLP) + { + i.neuralNet = std::shared_ptr<NeuralNet>(new SingleMLP(82, prms.npHiddenLayer, 5, prms.nodeClass, true)); + } + else if (prms.netClass == DUAL_MLP) + { + i.neuralNet = std::shared_ptr<NeuralNet>(new DualMLP(82, prms.npHiddenLayer, 5, prms.nodeClass, true)); + } + else if (prms.netClass == SIMPLE_RN) + { + i.neuralNet = std::shared_ptr<SimpleRN>(new SimpleRN(82, prms.npHiddenLayer, 5, prms.nodeClass, true)); + } + else if (prms.netClass == FULLY_RN) + { + i.neuralNet = std::shared_ptr<FullyRN>(new FullyRN(82, prms.npHiddenLayer, 5, prms.nodeClass, true)); + } + i.create(); + // Set neural net initial random weights + i.neuralNet->setChromosome(population->getChromosome(index)); + + ++index; + } + + return true; +} diff --git a/Simulation/SimFitness_UpdateSpecs.cpp b/Simulation/SimFitness_UpdateSpecs.cpp new file mode 100644 index 0000000..3919914 --- /dev/null +++ b/Simulation/SimFitness_UpdateSpecs.cpp @@ -0,0 +1,40 @@ +#include "SimFitness.hpp" + +void SimFitness::updateSpecs() +{ + for (auto &i : guppies) + { + i.update(); + } + + if (!guppieCount) + { + unsigned soul = 0; + for (auto &i : guppies) + { + population->setFitness(currentPopulation * prms.popSize + soul, (unsigned)i.fitness); + ++soul; + } + + ++currentPopulation; + if (currentPopulation == prms.popQtty) + { + population->roulleteWheel(); + + currentPopulation = 0; + ++currentGeneration; + } + + unsigned index = 0; + for (auto &i : guppies) + { + i.clean(); + i.create(); + i.neuralNet->setChromosome(population->getChromosome(currentPopulation * prms.popSize + index)); + ++index; + } + + text.currentPop.setString("Current population: " + nts(currentPopulation + 1) + " / " + nts(prms.popQtty)); + text.currentGen.setString("Current generation: " + nts(currentGeneration + 1)); + } +} diff --git a/Simulation/Tank.cpp b/Simulation/Tank.cpp new file mode 100644 index 0000000..7d0c6d1 --- /dev/null +++ b/Simulation/Tank.cpp @@ -0,0 +1,52 @@ +#include "Tank.hpp" +#include "SimBase.hpp" + +void Tank::create(SimBase *sim) +{ + hSim = sim; + + // Prepare tank graphics + setCenterRad(worldEdges, hSim->prms.worldRad); + worldEdges.setPointCount(360); + worldEdges.setFillColor(hSim->prms.worldColor); + worldEdges.setOutlineColor(hSim->prms.zapperColor); + worldEdges.setOutlineThickness(hSim->prms.worldRad / 100.f); + + // Prepare tank physics + world.SetAllowSleeping(true); + contactListener.hSim = hSim; + world.SetContactListener(&contactListener); + + // Create edge of the world + b2BodyDef worldDef; + worldDef.type = b2_staticBody; + + tankEdge = world.CreateBody(&worldDef); + + b2ChainShape shape; + b2Vec2 vrtx[360]; + for (int dgr = 0; dgr < 360; ++dgr) + { + vrtx[dgr].Set(cosf((float)dgr / Params::RAD_DGRS), sinf((float)dgr / Params::RAD_DGRS)), + vrtx[dgr] *= hSim->prms.worldRad; + } + shape.CreateLoop(vrtx, 360); + + b2FixtureDef worldFix; + worldFix.shape = &shape; + worldFix.friction = 1.f; + + tankEdge->CreateFixture(&worldFix); +} + + +void Tank::updateAllPhysics() +{ + world.Step(1.f / 60.f, 8, 3); +} + + +void Tank::draw() +{ + hSim->window.draw(worldEdges); +} diff --git a/Simulation/Tank.hpp b/Simulation/Tank.hpp new file mode 100644 index 0000000..f11d42b --- /dev/null +++ b/Simulation/Tank.hpp @@ -0,0 +1,34 @@ +#ifndef __TANK_HPP__ +#define __TANK_HPP__ + +#include <SFML/Graphics.hpp> +#include <Box2d.h> + +#include "ContactListener.hpp" + +class SimBase; + +class Tank +{ +public: + void create(SimBase *sim); + void updateAllPhysics(); + void draw(); + +private: + SimBase *hSim = nullptr; + + b2World world = b2World(b2Vec2(0.f, 0.f)); + sf::CircleShape worldEdges; + b2Body *tankEdge = nullptr; + + ContactListener contactListener; + + friend class Camera; + friend class Entity; + friend class Pellet; + friend class Guppie; + friend class Corpse; +}; + +#endif // __TANK_HPP__ diff --git a/Simulation/TextDisplay.cpp b/Simulation/TextDisplay.cpp new file mode 100644 index 0000000..6cdd1ad --- /dev/null +++ b/Simulation/TextDisplay.cpp @@ -0,0 +1,136 @@ +#include "TextDisplay.hpp" +#include "SimBase.hpp" + +#include <sstream> + +bool TextDisplay::startup(SimBase *sim, SimStyle style) +{ + hSim = sim; + sStyle = style; + + if (!font.loadFromFile("gfx\\font.ttf")) + { + return false; + } + + const float TXT_MARGIN = Params::TXT_SIZE / 2.f; + + // RIGHT PANEL + // Prepare left panel text + simState = sf::Text("Sim. state: RUNNING", font, Params::TXT_SIZE); + steps = sf::Text("Steps: 0", font, Params::TXT_SIZE); + // Separator + fps = sf::Text("FPS: 0", font, Params::TXT_SIZE); + gfx = sf::Text("Graphics: ON", font, Params::TXT_SIZE); + vSync = sf::Text("V. Sync: ON", font, Params::TXT_SIZE); + // Separator + worldRad = sf::Text("World radius: " + nts(hSim->prms.worldRad), font, Params::TXT_SIZE); + // Separator + zapperCnt = sf::Text("Zapper count: " + nts(hSim->prms.zapperQtty), font, Params::TXT_SIZE); + pelletCnt = sf::Text("Pellet count: " + nts(hSim->pelletCount) + " / " + nts(hSim->prms.pelletQtty), font, Params::TXT_SIZE); + guppieCnt = sf::Text("Guppie count: 0", font, Params::TXT_SIZE); + corpseCnt = sf::Text("Corpse count: 0", font, Params::TXT_SIZE); + if (sStyle == SELECTION_BY_FITNESS) + { + simStyle = sf::Text("Sim. style: FITNESS SELECTION", font, Params::TXT_SIZE); + currentPop = sf::Text("Current population: 1 / " + nts(hSim->prms.popQtty), font, Params::TXT_SIZE); + currentGen = sf::Text("Current generation: 1", font, Params::TXT_SIZE); + // Separator + longestLife = sf::Text("Fitness record: 0", font, Params::TXT_SIZE); + } + // Position and color text items + sf::Text *line = nullptr; + float posX = TXT_MARGIN * 2.f; + float posY = TXT_MARGIN * 2.f - Params::TXT_SIZE / 4.f; + for (line = &simState; line <= &corpseCnt; ++line) + { + line->setPosition(posX, posY); + line->setColor(hSim->prms.textColor); + posY += Params::TXT_SIZE; + if (line == &steps || line == &vSync || line == &worldRad || line == &corpseCnt) + { + posY += Params::TXT_SIZE; + } + } + if (sStyle == SELECTION_BY_FITNESS) + { + for (line = &simStyle; line <= &longestLife; ++line) + { + line->setPosition(posX, posY); + line->setColor(hSim->prms.textColor); + posY += Params::TXT_SIZE; + if (line == ¤tGen) + { + posY += Params::TXT_SIZE; + } + } + } + // Prepare left panel + float lpTxtWidth = 0.f; + float lpTxtHeight = 0.f; + for (line = &simState; line <= &corpseCnt; ++line) + { + line->getGlobalBounds().width > lpTxtWidth ? lpTxtWidth = line->getGlobalBounds().width : lpTxtWidth = lpTxtWidth; + } + if (sStyle == SELECTION_BY_FITNESS) + { + for (line = &simStyle; line <= &longestLife; ++line) + { + line->getGlobalBounds().width > lpTxtWidth ? lpTxtWidth = line->getGlobalBounds().width : lpTxtWidth = lpTxtWidth; + } + lpTxtHeight = (longestLife.getGlobalBounds().height + longestLife.getPosition().y) - simState.getPosition().y; + } + leftPanel.setSize(sf::Vector2f(lpTxtWidth + TXT_MARGIN * 2.f, lpTxtHeight + TXT_MARGIN * 2.f)); + leftPanel.setPosition(TXT_MARGIN, TXT_MARGIN); + leftPanel.setOutlineThickness(1.f); + leftPanel.setFillColor(sf::Color(0, 0, 0, 191)); + leftPanel.setOutlineColor(hSim->prms.textColor); + // Append left panel separators + for (int i = 0; i < 5; ++i) + { + sepLP[i].setSize(sf::Vector2f(lpTxtWidth, 1.f)); + sepLP[i].setFillColor(hSim->prms.textColor); + } + sepLP[0].setPosition(posX, fps.getPosition().y - TXT_MARGIN * 2.f / 3.f); + sepLP[1].setPosition(posX, worldRad.getPosition().y - TXT_MARGIN * 2.f / 3.f); + sepLP[2].setPosition(posX, zapperCnt.getPosition().y - TXT_MARGIN * 2.f / 3.f); + if (sStyle == SELECTION_BY_FITNESS) + { + sepLP[3].setPosition(posX, simStyle.getPosition().y - TXT_MARGIN * 2.f / 3.f); + sepLP[4].setPosition(posX, longestLife.getPosition().y - TXT_MARGIN * 2.f / 3.f); + } + + return true; +} + + +void TextDisplay::print() +{ + sf::View textView = sf::View(sf::Vector2f(hSim->window.getSize().x / 2.f, hSim->window.getSize().y / 2.f), sf::Vector2f(hSim->window.getSize().x, hSim->window.getSize().y)); + sf::View crntView = hSim->window.getView(); + + hSim->window.setView(textView); + + // Print left panel + hSim->window.draw(leftPanel); + sf::Text *line = nullptr; + for (line = &simState; line <= &simStyle; ++line) + { + hSim->window.draw(*line); + } + if (sStyle == SELECTION_BY_FITNESS) + { + for (line = ¤tPop; line <= &longestLife; ++line) + { + hSim->window.draw(*line); + } + } + for (int i = 0; i < 5; ++i) + { + hSim->window.draw(sepLP[i]); + } + + // Print right panel + + hSim->window.setView(crntView); +} diff --git a/Simulation/TextDisplay.hpp b/Simulation/TextDisplay.hpp new file mode 100644 index 0000000..b46cefb --- /dev/null +++ b/Simulation/TextDisplay.hpp @@ -0,0 +1,55 @@ +#ifndef __TEXTDISPLAY_HPP__ +#define __TEXTDISPLAY_HPP__ + +#include <SFML/Graphics.hpp> + +#include "Params.hpp" + +class SimBase; + +class TextDisplay +{ +public: + bool startup(SimBase *sim, SimStyle style); + void print(); + +private: + SimBase *hSim = nullptr; + SimStyle sStyle; + + sf::Font font; + sf::RectangleShape leftPanel; + + // ON LEFT PANEL + sf::Text simState; + sf::Text steps; + // Separator + sf::Text fps; + sf::Text gfx; + sf::Text vSync; + // Separator + sf::Text worldRad; + // Separator + sf::Text zapperCnt; + sf::Text pelletCnt; + sf::Text guppieCnt; + sf::Text corpseCnt; + // Separator + sf::Text simStyle; + // If sim. style == sel. by fitness + sf::Text currentPop; + sf::Text currentGen; + // Separator + sf::Text longestLife; + // Left panel separators + sf::RectangleShape sepLP[5]; + + friend class SimBase; + friend class SimFitness; + friend class Entity; + friend class Pellet; + friend class Guppie; + friend class Corpse; +}; + +#endif // __TEXTDISPLAY_HPP__ diff --git a/Simulation/Zapper.cpp b/Simulation/Zapper.cpp new file mode 100644 index 0000000..f31b996 --- /dev/null +++ b/Simulation/Zapper.cpp @@ -0,0 +1,46 @@ +#include "Zapper.hpp" +#include "SimBase.hpp" + +void Zapper::startup(SimBase *sim) +{ + hSim = sim; + radius = Params::ZAPPER_RAD; +} + + +void Zapper::update() +{ + float forceX = realRand(-hSim->prms.zapperForce, hSim->prms.zapperForce); + float forceY = realRand(-hSim->prms.zapperForce, hSim->prms.zapperForce); + body->ApplyForceToCenter(b2Vec2(forceX, forceY)); + float torque = realRand(-hSim->prms.zapperTorque, hSim->prms.zapperTorque); + body->ApplyTorque(torque); +} + + +void Zapper::draw() +{ + sf::Vector2f vSize = hSim->window.getView().getSize(); + sf::Vector2f vCent = hSim->window.getView().getCenter(); + if ( body->GetPosition().x + radius < vCent.x - vSize.x / 2.f || + body->GetPosition().y + radius < vCent.y - vSize.y / 2.f || + body->GetPosition().x - radius > vCent.x + vSize.x / 2.f || + body->GetPosition().y - radius > vCent.y + vSize.y / 2.f ) + { + return; + } + + if (hSim->camera.zoom > Params::ZAPPER_RAD / 2.f) + { + hSim->zapperPoint.setPosition(body->GetPosition().x, body->GetPosition().y); + hSim->window.draw(hSim->zapperPoint); + } + else + { + hSim->zapperShell.setPosition(body->GetPosition().x, body->GetPosition().y); + hSim->zapperNucleus.setPosition(body->GetPosition().x, body->GetPosition().y); + hSim->zapperNucleus.setRotation(body->GetAngle() * hSim->prms.RAD_DGRS); + hSim->window.draw(hSim->zapperShell); + hSim->window.draw(hSim->zapperNucleus); + } +} diff --git a/Simulation/Zapper.hpp b/Simulation/Zapper.hpp new file mode 100644 index 0000000..e71ff82 --- /dev/null +++ b/Simulation/Zapper.hpp @@ -0,0 +1,14 @@ +#ifndef __ZAPPER_HPP__ +#define __ZAPPER_HPP__ + +#include "Entity.hpp" + +class Zapper : public Entity +{ +public: + void startup(SimBase *sim); + void update(); + void draw(); +}; + +#endif // __ZAPPER_HPP__ |