aboutsummaryrefslogtreecommitdiff
path: root/Simulation
diff options
context:
space:
mode:
authorPaul Oliver <contact@pauloliver.dev>2024-02-29 19:27:35 +0100
committerPaul Oliver <contact@pauloliver.dev>2024-02-29 19:27:49 +0100
commit17909d029c6a8872b2fddf4e171d7925bbbe9c5c (patch)
treecbb08af84cd68d24acc362d593a2048b0fa79689 /Simulation
Initial commitHEADmaster
Diffstat (limited to 'Simulation')
-rw-r--r--Simulation/Camera.cpp241
-rw-r--r--Simulation/Camera.hpp42
-rw-r--r--Simulation/ContactListener.cpp168
-rw-r--r--Simulation/ContactListener.hpp16
-rw-r--r--Simulation/Corpse.cpp99
-rw-r--r--Simulation/Corpse.hpp28
-rw-r--r--Simulation/Entity.cpp59
-rw-r--r--Simulation/Entity.hpp34
-rw-r--r--Simulation/GraphicObjs.hpp56
-rw-r--r--Simulation/Guppie.cpp584
-rw-r--r--Simulation/Guppie.hpp58
-rw-r--r--Simulation/GuppiesInclude.hpp6
-rw-r--r--Simulation/Params.hpp172
-rw-r--r--Simulation/Pellet.cpp66
-rw-r--r--Simulation/Pellet.hpp20
-rw-r--r--Simulation/SimBase.hpp70
-rw-r--r--Simulation/SimBase_CreateNew.cpp49
-rw-r--r--Simulation/SimBase_Draw.cpp40
-rw-r--r--Simulation/SimBase_Execute.cpp152
-rw-r--r--Simulation/SimBase_PrepareGraphics.cpp269
-rw-r--r--Simulation/SimBase_Update.cpp55
-rw-r--r--Simulation/SimFitness.hpp26
-rw-r--r--Simulation/SimFitness_StartSpecs.cpp60
-rw-r--r--Simulation/SimFitness_UpdateSpecs.cpp40
-rw-r--r--Simulation/Tank.cpp52
-rw-r--r--Simulation/Tank.hpp34
-rw-r--r--Simulation/TextDisplay.cpp136
-rw-r--r--Simulation/TextDisplay.hpp55
-rw-r--r--Simulation/Zapper.cpp46
-rw-r--r--Simulation/Zapper.hpp14
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 == &currentGen)
+ {
+ 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 = &currentPop; 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__