// TODO: extend to nearby bodies as well
for (auto c : cam.Reference().Creatures()) {
assets.shaders.creature_skin.SetM(cam.Model(cam.Reference()) * glm::mat4(c->LocalTransform()));
- c->Draw(assets, viewport);
+ c->Draw(viewport);
}
viewport.ClearDepth();
using namespace blobs;
+namespace {
+
+struct SwitchPanel {
+ SwitchPanel(world::Planet &p, app::Application &app, app::MasterState &state)
+ : planet(p), app(app), state(state) { }
+
+ void operator ()(creature::Creature &c) {
+ if (planet.Creatures().empty()) {
+ std::cout << "no more creatures, game over" << std::endl;
+ while (app.HasState()) {
+ app.PopState();
+ }
+ } else {
+ for (auto a : planet.Creatures()) {
+ if (a != &c) {
+ state.GetCreaturePanel().Show(*a);
+ a->OnDeath([&](creature::Creature &b) { (*this)(b); });
+ break;
+ }
+ }
+ }
+ }
+
+ world::Planet &planet;
+ app::Application &app;
+ app::MasterState &state;
+};
+}
+
int main(int argc, char *argv[]) {
app::Init init(true, 8);
app::Assets assets;
second_planet.AxialTilt(glm::dvec2(PI * 0.95, 0.0));
second_planet.AngularMomentum(1.0e8);
- world::Simulation sim(sun, assets.data.resources, assets.data.tile_types);
+ world::Simulation sim(sun, assets);
sim.AddSun(sun);
sim.AddPlanet(planet);
sim.AddPlanet(second_planet);
std::cout << "moon cycles per year: " << (planet.OrbitalPeriod() / moon.OrbitalPeriod()) << std::endl;
auto blob = new creature::Creature(sim);
- Spawn(*blob, planet, assets);
- blob->BuildVAO();
blob->Name("Blob");
+ Spawn(*blob, planet);
+ blob->BuildVAO();
app::MasterState state(assets, sim);
state.GetCamera()
state.GetCreaturePanel().Show(*blob);
app::Application app(init.window, init.viewport);
+ SwitchPanel swp(planet, app, state);
+ blob->OnDeath([&](creature::Creature &c) { swp(c); });
app.PushState(&state);
app.Run();
class Creature {
+public:
+ using Callback = std::function<void(Creature &)>;
+
public:
explicit Creature(world::Simulation &);
~Creature();
Genome &GetGenome() noexcept { return genome; }
const Genome &GetGenome() const noexcept { return genome; }
- void Mass(double m) noexcept { mass = m; }
+ Genome::Properties<double> &GetProperties() noexcept { return properties; }
+ const Genome::Properties<double> &GetProperties() const noexcept { return properties; }
+
+ void Mass(double m) noexcept { mass = m; size = std::cbrt(mass / density); }
double Mass() const noexcept { return mass; }
+ void Grow(double amount) noexcept;
- void Size(double s) noexcept { size = s; }
- double Size() const noexcept { return size; }
+ void Density(double d) noexcept { density = d; size = std::cbrt(mass / density); }
+ double Density() const noexcept { return density; }
+
+ double Size() const noexcept;
+ double Age() const noexcept;
+ // change of giving birth per tick
+ double Fertility() const noexcept;
void Health(double h) noexcept { health = h; }
double Health() const noexcept { return health; }
void Hurt(double d) noexcept;
+ void Die() noexcept;
+ void OnDeath(Callback cb) noexcept { on_death = cb; }
+ void Remove() noexcept { removable = true; }
+ bool Removable() const noexcept { return removable; }
void AddNeed(std::unique_ptr<Need> &&n) { needs.emplace_back(std::move(n)); }
const std::vector<std::unique_ptr<Need>> &Needs() const noexcept { return needs; }
void Velocity(const glm::dvec3 &v) noexcept { vel = v; }
const glm::dvec3 &Velocity() const noexcept { return vel; }
- bool Moving() const noexcept { return glm::length2(vel) < 0.000001; }
+ bool Moving() const noexcept { return glm::length2(vel) < 0.00000001; }
glm::dmat4 LocalTransform() noexcept;
void BuildVAO();
- void Draw(app::Assets &, graphics::Viewport &);
+ void Draw(graphics::Viewport &);
private:
world::Simulation ∼
std::string name;
Genome genome;
+ Genome::Properties<double> properties;
double mass;
+ double density;
double size;
+
+ double birth;
double health;
+ Callback on_death;
+ bool removable;
std::vector<std::unique_ptr<Need>> needs;
std::vector<std::unique_ptr<Goal>> goals;
};
/// put creature on planet and configure it to (hopefully) survive
-void Spawn(Creature &, world::Planet &, app::Assets &);
+void Spawn(Creature &, world::Planet &);
+
+/// split the creature into two
+void Split(Creature &);
}
}
struct Genome {
+ template<class T>
+ struct Properties {
+ T birth_mass;
+ T fertile_mass;
+ T max_mass;
+
+ T fertile_age;
+ T infertile_age;
+ T death_age;
+
+ T fertility;
+ };
+ Properties<math::Distribution> properties;
+
+
struct Composition {
// which resource
int resource;
// how much contained in the body
+ // relative value to determine average density
math::Distribution mass;
// how much to circulate
math::Distribution intake;
// how important for alive-being
math::Distribution penalty;
+ // how much off the mass may stay in the body
+ math::Distribution growth;
};
std::vector<Composition> composition;
- void Configure(app::Assets &, Creature &) const;
+ void Configure(Creature &) const;
};
namespace blobs {
+namespace app {
+ struct Assets;
+}
namespace creature {
class Creature;
const Situation &GetSituation() const noexcept;
Steering &GetSteering() noexcept;
const Steering &GetSteering() const noexcept;
+ app::Assets &Assets() noexcept;
+ const app::Assets &Assets() const noexcept;
double Urgency() const noexcept { return urgency; }
void Urgency(double u) noexcept { urgency = u; }
--- /dev/null
+#ifndef BLOBS_CREATURE_IDLEGOAL_HPP_
+#define BLOBS_CREATURE_IDLEGOAL_HPP_
+
+#include "Goal.hpp"
+
+
+namespace blobs {
+namespace creature {
+
+class IdleGoal
+: public Goal {
+
+public:
+ explicit IdleGoal(Creature &);
+ ~IdleGoal() override;
+
+public:
+ std::string Describe() const override;
+ void Enable() override;
+ void Tick(double dt) override;
+ void Action() override;
+
+};
+
+}
+}
+
+#endif
double inconvenient = 0.0;
// the value at which this need starts to hurt
double critical = 0.0;
+ // factor of the intake that may stay in the body
+ double growth = 0.0;
virtual ~Need() noexcept;
Situation();
~Situation();
+ Situation(const Situation &) = delete;
+ Situation &operator =(const Situation &) = delete;
+
+ Situation(Situation &&) = delete;
+ Situation &operator =(Situation &&) = delete;
+
public:
bool OnPlanet() const noexcept;
world::Planet &GetPlanet() const noexcept { return *planet; }
#include "Steering.hpp"
#include "Goal.hpp"
+#include "IdleGoal.hpp"
#include "InhaleNeed.hpp"
#include "IngestNeed.hpp"
#include "Need.hpp"
, name()
, genome()
, mass(1.0)
+, density(1.0)
, size(1.0)
+, birth(sim.Time())
, health(1.0)
+, on_death()
+, removable(false)
, needs()
, goals()
, situation()
Creature::~Creature() {
}
+void Creature::Grow(double amount) noexcept {
+ Mass(std::min(properties.max_mass, mass + amount));
+}
+
void Creature::Hurt(double dt) noexcept {
health = std::max(0.0, health - dt);
+ if (health == 0.0) {
+ Die();
+ }
+}
+
+void Creature::Die() noexcept {
+ needs.clear();
+ goals.clear();
+ steering.Halt();
+ if (on_death) {
+ on_death(*this);
+ }
+ Remove();
+}
+
+double Creature::Size() const noexcept {
+ return size;
+}
+
+double Creature::Age() const noexcept {
+ return sim.Time() - birth;
+}
+
+double Creature::Fertility() const noexcept {
+ double age = Age();
+ if (mass < properties.fertile_mass
+ || age < properties.fertile_age
+ || age > properties.infertile_age) {
+ return 0.0;
+ }
+ return properties.fertility / 3600.0;
}
void Creature::AddGoal(std::unique_ptr<Goal> &&g) {
- std::cout << "new goal: " << g->Describe() << std::endl;
+ std::cout << "[" << int(sim.Time()) << "s] " << name << " new goal: " << g->Describe() << std::endl;
g->Enable();
goals.emplace_back(std::move(g));
}
std::sort(goals.begin(), goals.end(), GoalCompare);
Goal *new_top = &*goals[0];
if (new_top != old_top) {
- std::cout << "changing goal from " << old_top->Describe()
+ std::cout << "[" << int(sim.Time()) << "s] " << name
+ << " changing goal from " << old_top->Describe()
<< " to " << new_top->Describe() << std::endl;
}
}
goals[0]->Action();
for (auto goal = goals.begin(); goal != goals.end();) {
if ((*goal)->Complete()) {
- std::cout << "complete goal: " << (*goal)->Describe() << std::endl;
+ std::cout << "[" << int(sim.Time()) << "s] " << name
+ << " complete goal: " << (*goal)->Describe() << std::endl;
goals.erase(goal);
} else {
++goal;
vao.Unbind();
}
-void Creature::Draw(app::Assets &assets, graphics::Viewport &viewport) {
+void Creature::Draw(graphics::Viewport &viewport) {
vao.Bind();
vao.DrawTriangles(6 * 6);
}
-void Spawn(Creature &c, world::Planet &p, app::Assets &assets) {
+void Spawn(Creature &c, world::Planet &p) {
p.AddCreature(&c);
c.GetSituation().SetPlanetSurface(p, 0, p.TileCenter(0, p.SideLength() / 2, p.SideLength() / 2));
std::map<int, double> yields;
for (int y = start; y < end; ++y) {
for (int x = start; x < end; ++x) {
- const world::TileType &t = assets.data.tile_types[p.TileAt(0, x, y).type];
+ const world::TileType &t = p.TypeAt(0, x, y);
for (auto yield : t.resources) {
yields[yield.resource] += yield.ubiquity;
}
int liquid = -1;
int solid = -1;
for (auto e : yields) {
- if (assets.data.resources[e.first].state == world::Resource::LIQUID) {
+ if (c.GetSimulation().Resources()[e.first].state == world::Resource::LIQUID) {
if (liquid < 0 || e.second > yields[liquid]) {
liquid = e.first;
}
- } else if (assets.data.resources[e.first].state == world::Resource::SOLID) {
+ } else if (c.GetSimulation().Resources()[e.first].state == world::Resource::SOLID) {
if (solid < 0 || e.second > yields[solid]) {
solid = e.first;
}
}
Genome genome;
+ genome.properties.birth_mass = { 0.5, 0.1 };
+ genome.properties.fertile_mass = { 1.0, 0.1 };
+ genome.properties.max_mass = { 1.2, 0.1 };
+ genome.properties.fertile_age = { 60.0, 5.0 };
+ genome.properties.infertile_age = { 700.0, 30.0 };
+ genome.properties.death_age = { 900.0, 90.0 };
+ genome.properties.fertility = { 0.5, 0.01 };
+
if (p.HasAtmosphere()) {
genome.composition.push_back({
p.Atmosphere(), // resource
{ 0.01, 0.00001 }, // mass
{ 0.5, 0.001 }, // intake
- { 0.1, 0.0005 } // penalty
+ { 0.1, 0.0005 }, // penalty
+ { 0.0, 0.0 }, // growth
});
}
if (liquid > -1) {
liquid, // resource
{ 0.6, 0.01 }, // mass
{ 0.2, 0.001 }, // intake
- { 0.01, 0.002 } // penalty
+ { 0.01, 0.002 }, // penalty
+ { 0.1, 0.0 }, // growth
});
}
if (solid > -1) {
genome.composition.push_back({
- solid, // resource
- { 0.4, 0.01 }, // mass
- { 0.1, 0.001 }, // intake
- { 0.001, 0.0001 } // penalty
+ solid, // resource
+ { 0.4, 0.01 }, // mass
+ //{ 0.1, 0.001 }, // intake
+ { 0.4, 0.001 }, // intake
+ { 0.001, 0.0001 }, // penalty
+ { 10.0, 0.002 }, // growth
});
}
- genome.Configure(assets, c);
- c.GetSteering().MaxAcceleration(1.4);
- c.GetSteering().MaxSpeed(4.4);
+ genome.Configure(c);
}
-void Genome::Configure(app::Assets &assets, Creature &c) const {
+void Genome::Configure(Creature &c) const {
c.GetGenome() = *this;
+
+ math::GaloisLFSR &random = c.GetSimulation().Assets().random;
+
+ c.GetProperties().birth_mass = properties.birth_mass.FakeNormal(random.SNorm());
+ c.GetProperties().fertile_mass = properties.fertile_mass.FakeNormal(random.SNorm());
+ c.GetProperties().max_mass = properties.max_mass.FakeNormal(random.SNorm());
+ c.GetProperties().fertile_age = properties.fertile_age.FakeNormal(random.SNorm());
+ c.GetProperties().infertile_age = properties.infertile_age.FakeNormal(random.SNorm());
+ c.GetProperties().death_age = properties.death_age.FakeNormal(random.SNorm());
+ c.GetProperties().fertility = properties.fertility.FakeNormal(random.SNorm());
+
double mass = 0.0;
double volume = 0.0;
for (const auto &comp : composition) {
- double comp_mass = comp.mass.FakeNormal(assets.random.SNorm());
- double intake = comp.intake.FakeNormal(assets.random.SNorm());
- double penalty = comp.intake.FakeNormal(assets.random.SNorm());
+ double comp_mass = comp.mass.FakeNormal(random.SNorm());
+ double intake = comp.intake.FakeNormal(random.SNorm());
+ double penalty = comp.penalty.FakeNormal(random.SNorm());
mass += comp_mass;
- volume += comp_mass / assets.data.resources[comp.resource].density;
+ volume += comp_mass / c.GetSimulation().Resources()[comp.resource].density;
std::unique_ptr<Need> need;
- if (assets.data.resources[comp.resource].state == world::Resource::SOLID) {
+ if (c.GetSimulation().Resources()[comp.resource].state == world::Resource::SOLID) {
need.reset(new IngestNeed(comp.resource, intake, penalty));
need->gain = intake * 0.05;
- } else if (assets.data.resources[comp.resource].state == world::Resource::LIQUID) {
+ } else if (c.GetSimulation().Resources()[comp.resource].state == world::Resource::LIQUID) {
need.reset(new IngestNeed(comp.resource, intake, penalty));
need->gain = intake * 0.1;
} else {
need.reset(new InhaleNeed(comp.resource, intake, penalty));
need->gain = intake * 0.5;
}
- need->name = assets.data.resources[comp.resource].label;
+ need->name = c.GetSimulation().Resources()[comp.resource].label;
+ need->growth = comp.growth.FakeNormal(random.SNorm());
need->inconvenient = 0.5;
need->critical = 0.95;
c.AddNeed(std::move(need));
}
- c.Mass(mass);
- c.Size(std::cbrt(volume));
+
+ c.Mass(c.GetProperties().birth_mass);
+ c.Density(mass / volume);
+ c.GetSteering().MaxAcceleration(1.4);
+ c.GetSteering().MaxSpeed(4.4);
+ c.AddGoal(std::unique_ptr<Goal>(new IdleGoal(c)));
+}
+
+
+void Split(Creature &c) {
+ Creature *a = new Creature(c.GetSimulation());
+ const Situation &s = c.GetSituation();
+ // TODO: generate names
+ a->Name("Blobby");
+ // TODO: mutate
+ c.GetGenome().Configure(*a);
+ s.GetPlanet().AddCreature(a);
+ // TODO: duplicate situation somehow
+ a->GetSituation().SetPlanetSurface(
+ s.GetPlanet(), s.Surface(),
+ s.Position() + glm::dvec3(0.0, a->Size() * 0.51, 0.0));
+ a->BuildVAO();
+
+ Creature *b = new Creature(c.GetSimulation());
+ b->Name("Sir Blobalot");
+ c.GetGenome().Configure(*b);
+ s.GetPlanet().AddCreature(b);
+ b->GetSituation().SetPlanetSurface(
+ s.GetPlanet(), s.Surface(),
+ s.Position() + glm::dvec3(0.0, b->Size() * -0.51, 0.0));
+ b->BuildVAO();
+
+ c.Die();
}
+
Situation::Situation()
: planet(nullptr)
, position(0.0)
#include "Goal.hpp"
+#include "IdleGoal.hpp"
#include "LocateResourceGoal.hpp"
#include "Creature.hpp"
+#include "../app/Assets.hpp"
#include "../world/Planet.hpp"
#include "../world/Resource.hpp"
#include "../world/Simulation.hpp"
return c.GetSteering();
}
+app::Assets &Goal::Assets() noexcept {
+ return c.GetSimulation().Assets();
+}
+
+const app::Assets &Goal::Assets() const noexcept {
+ return c.GetSimulation().Assets();
+}
+
void Goal::SetComplete() noexcept {
if (!complete) {
complete = true;
}
+IdleGoal::IdleGoal(Creature &c)
+: Goal(c) {
+ Urgency(-1.0);
+ Interruptible(true);
+}
+
+IdleGoal::~IdleGoal() {
+}
+
+std::string IdleGoal::Describe() const {
+ return "idle";
+}
+
+void IdleGoal::Enable() {
+}
+
+void IdleGoal::Tick(double dt) {
+}
+
+void IdleGoal::Action() {
+ double fert = GetCreature().Fertility();
+ double rand = Assets().random.UNorm();
+ if (fert > rand) {
+ std::cout << "[" << int(GetCreature().GetSimulation().Time())
+ << "s] splitting " << GetCreature().Name()
+ << " because " << fert << " > " << rand << std::endl;
+ Split(GetCreature());
+ }
+}
+
+
LocateResourceGoal::LocateResourceGoal(Creature &c, int res)
: Goal(c)
, res(res)
target_srf = srf;
target_tile = best_pos;
GetSteering().GoTo(target_pos);
- std::cout << "found resource at " << target_pos << std::endl;
} else {
// oh crap
}
if (yield.resource == resource) {
found = true;
// TODO: check if not busy with something else
- Decrease(std::min(yield.ubiquity, speed) * dt);
+ double amount = std::min(yield.ubiquity, speed) * dt;
+ c.Grow(amount * growth * dt);
+ Decrease(amount);
if (value == 0.0) {
ingesting = false;
if (locate_goal) {
"void main() {\n"
"vec3 tex_color = texture(tex_sampler, frag_tex_uv).rgb;\n"
- "vec3 total_light = tex_color * vec3(0.01, 0.01, 0.01);\n"
+ "vec3 total_light = tex_color * vec3(0.1, 0.1, 0.1);\n"
"for (int i = 0; i < num_lights; ++i) {\n"
"vec3 to_light = light[i].position - vtx_viewspace;\n"
"float distance = length(to_light) + length(vtx_viewspace);\n"
creature::Creature *c;
Label *name;
+ Label *age;
+ Label *mass;
+ Label *goal;
Panel *needs;
Panel panel;
#include "../creature/Need.hpp"
#include "../graphics/Viewport.hpp"
+#include <iomanip>
+#include <sstream>
#include <glm/gtx/transform.hpp>
: assets(assets)
, c(nullptr)
, name(new Label(assets.fonts.large))
+, age(new Label(assets.fonts.medium))
+, mass(new Label(assets.fonts.medium))
+, goal(new Label(assets.fonts.medium))
, needs(new Panel)
, panel()
, health_meter(new Meter)
health_panel
->Add(health_label)
->Add(health_meter)
+ ->Spacing(10.0f)
+ ->Direction(Panel::HORIZONTAL);
+
+ Label *age_label = new Label(assets.fonts.medium);
+ age_label->Text("Age");
+ Panel *age_panel = new Panel;
+ age_panel
+ ->Add(age_label)
+ ->Add(age)
+ ->Spacing(10.0f)
+ ->Direction(Panel::HORIZONTAL);
+
+ Label *mass_label = new Label(assets.fonts.medium);
+ mass_label->Text("Mass");
+ Panel *mass_panel = new Panel;
+ mass_panel
+ ->Add(mass_label)
+ ->Add(mass)
+ ->Spacing(10.0f)
->Direction(Panel::HORIZONTAL);
+
+ Label *goal_label = new Label(assets.fonts.medium);
+ goal_label->Text("Goal");
+ Panel *goal_panel = new Panel;
+ goal_panel
+ ->Add(goal_label)
+ ->Add(goal)
+ ->Spacing(10.0f)
+ ->Direction(Panel::HORIZONTAL);
+
panel
.Add(name)
+ ->Add(age_panel)
+ ->Add(mass_panel)
+ ->Add(goal_panel)
->Add(health_panel)
->Add(needs)
->Padding(glm::vec2(10.0f))
void CreaturePanel::Draw(app::Assets &assets, graphics::Viewport &viewport) noexcept {
if (!c) return;
+ age->Text(std::to_string(int(c->Age())) + "s");
+ {
+ std::stringstream ss;
+ ss << std::fixed << std::setprecision(3) << c->Mass() << "kg";
+ mass->Text(ss.str());
+ }
+ if (c->Goals().empty()) {
+ goal->Text("none");
+ } else {
+ goal->Text(c->Goals()[0]->Describe());
+ }
health_meter->Value(c->Health());
if (need_meters.size() != c->Needs().size()) {
}
glm::vec2 Panel::Size() {
- return (2.0f * padding) + glm::vec2(0.0f, (widgets.size() - 1) * spacing) + size;
+ glm::vec2 space(0.0f);
+ space[dir] = (widgets.size() - 1) * spacing;
+ return (2.0f * padding) + space + size;
}
void Panel::Relayout() {
#define BLOBS_WORLD_SIMULATION_HPP_
#include "Set.hpp"
+#include "../app/Assets.hpp"
#include <set>
class Simulation {
public:
- explicit Simulation(Body &root, const Set<Resource> &, const Set<TileType> &);
+ explicit Simulation(Body &root, app::Assets &);
~Simulation();
Simulation(const Simulation &) = delete;
Body &Root() noexcept { return root; }
const Body &Root() const noexcept { return root; }
- const Set<Resource> &Resources() const noexcept { return resources; }
- const Set<TileType> &TileTypes() const noexcept { return tile_types; }
+ app::Assets &Assets() noexcept { return assets; }
+ const app::Assets &Assets() const noexcept { return assets; }
+ const Set<Resource> &Resources() const noexcept { return assets.data.resources; }
+ const Set<TileType> &TileTypes() const noexcept { return assets.data.tile_types; }
const std::set<Body *> &Bodies() const noexcept { return bodies; }
const std::set<Planet *> &Planets() const noexcept { return planets; }
private:
Body &root;
- const Set<Resource> &resources;
- const Set<TileType> &tile_types;
+ app::Assets &assets;
std::set<Body *> bodies;
std::set<Planet *> planets;
std::set<Sun *> suns;
namespace blobs {
namespace world {
-Simulation::Simulation(Body &r, const Set<Resource> &res, const Set<TileType> &tile)
+Simulation::Simulation(Body &r, app::Assets &assets)
: root(r)
-, resources(res)
-, tile_types(tile)
+, assets(assets)
, bodies()
, planets()
, suns()
for (creature::Creature *c : Creatures()) {
c->Tick(dt);
}
+ for (auto c = Creatures().begin(); c != Creatures().end();) {
+ if ((*c)->Removable()) {
+ delete *c;
+ c = Creatures().erase(c);
+ } else {
+ ++c;
+ }
+ }
}
void Body::Cache() noexcept {