From 75398ab9230c15215e7a378a26d2d55de67b47f0 Mon Sep 17 00:00:00 2001 From: Daniel Karbach Date: Wed, 6 Dec 2017 23:57:39 +0100 Subject: [PATCH] random walks --- src/blobs.cpp | 4 +- src/creature/Creature.hpp | 2 +- src/creature/Goal.hpp | 21 ++- src/creature/IdleGoal.hpp | 4 +- src/creature/Situation.hpp | 1 + src/creature/StrollGoal.hpp | 36 +++++ src/creature/creature.cpp | 21 ++- src/creature/goal.cpp | 253 +++++++++++++++++++++++------------- src/ui/CreaturePanel.hpp | 3 + src/ui/ui.cpp | 34 +++++ src/world/world.cpp | 16 +-- 11 files changed, 290 insertions(+), 105 deletions(-) create mode 100644 src/creature/StrollGoal.hpp diff --git a/src/blobs.cpp b/src/blobs.cpp index 1d854fc..f5cc988 100644 --- a/src/blobs.cpp +++ b/src/blobs.cpp @@ -32,7 +32,7 @@ struct SwitchPanel { for (auto a : planet.Creatures()) { if (a != &c) { state.GetCreaturePanel().Show(*a); - a->OnDeath([&](creature::Creature &b) { (*this)(b); }); + a->WhenDead([&](creature::Creature &b) { (*this)(b); }); break; } } @@ -102,7 +102,7 @@ int main(int argc, char *argv[]) { app::Application app(init.window, init.viewport); SwitchPanel swp(planet, app, state); - blob->OnDeath([&](creature::Creature &c) { swp(c); }); + blob->WhenDead([&](creature::Creature &c) { swp(c); }); app.PushState(&state); app.Run(); diff --git a/src/creature/Creature.hpp b/src/creature/Creature.hpp index 28e4131..0012d78 100644 --- a/src/creature/Creature.hpp +++ b/src/creature/Creature.hpp @@ -144,7 +144,7 @@ public: void Hurt(double d) noexcept; void Die() noexcept; bool Dead() const noexcept; - void OnDeath(Callback cb) noexcept { on_death = cb; } + void WhenDead(Callback cb) noexcept { on_death = cb; } void Remove() noexcept; bool Removable() const noexcept { return removable; } void Removed() noexcept; diff --git a/src/creature/Goal.hpp b/src/creature/Goal.hpp index 9ffeb0f..4d3a17f 100644 --- a/src/creature/Goal.hpp +++ b/src/creature/Goal.hpp @@ -9,6 +9,9 @@ namespace blobs { namespace app { struct Assets; } +namespace math { + class GaloisLFSR; +} namespace creature { class Creature; @@ -33,6 +36,7 @@ public: const Steering &GetSteering() const noexcept; app::Assets &Assets() noexcept; const app::Assets &Assets() const noexcept; + math::GaloisLFSR &Random() noexcept; double Urgency() const noexcept { return urgency; } void Urgency(double u) noexcept { urgency = u; } @@ -41,9 +45,15 @@ public: void Interruptible(bool i) noexcept { interruptible = i; } bool Complete() const noexcept { return complete; } - void SetComplete() noexcept; + void SetComplete(); + void SetForeground(); + void SetBackground(); /// only supports one callback for now, new one will replace an old - void OnComplete(Callback) noexcept; + void WhenComplete(Callback) noexcept; + void WhenForeground(Callback) noexcept; + /// on background will not be called when the goal is first inserted + /// but only after is has been in the foreground once + void WhenBackground(Callback) noexcept; public: virtual std::string Describe() const = 0; @@ -51,9 +61,16 @@ public: virtual void Tick(double dt) { } virtual void Action() { } +private: + virtual void OnComplete() { } + virtual void OnForeground() { } + virtual void OnBackground() { } + private: Creature &c; Callback on_complete; + Callback on_foreground; + Callback on_background; double urgency; bool interruptible; bool complete; diff --git a/src/creature/IdleGoal.hpp b/src/creature/IdleGoal.hpp index eb402ba..e7c98d4 100644 --- a/src/creature/IdleGoal.hpp +++ b/src/creature/IdleGoal.hpp @@ -16,10 +16,10 @@ public: public: std::string Describe() const override; - void Enable() override; - void Tick(double dt) override; void Action() override; + void PickActivity(); + }; } diff --git a/src/creature/Situation.hpp b/src/creature/Situation.hpp index 25d218d..561f106 100644 --- a/src/creature/Situation.hpp +++ b/src/creature/Situation.hpp @@ -65,6 +65,7 @@ public: void Move(const glm::dvec3 &dp) noexcept; void Accelerate(const glm::dvec3 &dv) noexcept; void EnforceConstraints(State &) noexcept; + void CheckWrap() noexcept; void Heading(const glm::dvec3 &h) noexcept { state.dir = h; } const glm::dvec3 &Heading() const noexcept { return state.dir; } diff --git a/src/creature/StrollGoal.hpp b/src/creature/StrollGoal.hpp new file mode 100644 index 0000000..969e4da --- /dev/null +++ b/src/creature/StrollGoal.hpp @@ -0,0 +1,36 @@ +#ifndef BLOBS_CREATURE_STROLLGOAL_HPP_ +#define BLOBS_CREATURE_STROLLGOAL_HPP_ + +#include "Goal.hpp" + +#include "../math/glm.hpp" + + +namespace blobs { +namespace creature { + +class StrollGoal +: public Goal { + +public: + explicit StrollGoal(Creature &); + ~StrollGoal() override; + +public: + std::string Describe() const override; + void Enable() override; + void Action() override; + void OnBackground() override; + + void PickTarget() noexcept; + +private: + glm::dvec3 last; + glm::dvec3 next; + +}; + +} +} + +#endif diff --git a/src/creature/creature.cpp b/src/creature/creature.cpp index a9c4dc7..4689678 100644 --- a/src/creature/creature.cpp +++ b/src/creature/creature.cpp @@ -193,7 +193,7 @@ void Creature::Ingest(int res, double amount) noexcept { } void Creature::DoWork(double amount) noexcept { - stats.Exhaustion().Add(amount / Stamina()); + stats.Exhaustion().Add(amount / (Stamina() + 1.0)); // burn resources proportional to composition // factor = 1/total * 1/efficiency * amount * -1 double factor = -amount / (composition.TotalMass() * EnergyEfficiency()); @@ -204,6 +204,8 @@ void Creature::DoWork(double amount) noexcept { double value = cmp.value * factor * sim.Resources()[cmp.resource].inverse_energy; AddMass(cmp.resource, value); } + // doing work improves strength a little + properties.Strength() += amount * 0.0001; } void Creature::Hurt(double amount) noexcept { @@ -360,6 +362,9 @@ double Creature::AdaptChance() const noexcept { void Creature::AddGoal(std::unique_ptr &&g) { g->Enable(); + if (goals.empty()) { + g->SetForeground(); + } goals.emplace_back(std::move(g)); } @@ -470,10 +475,16 @@ void Creature::TickBrain(double dt) { for (auto &goal : goals) { goal->Tick(dt); } + Goal *top = &*goals.front(); // if active goal can be interrupted, check priorities if (goals.size() > 1 && goals[0]->Interruptible()) { std::sort(goals.begin(), goals.end(), GoalCompare); } + if (&*goals.front() != top) { + top->SetBackground(); + goals.front()->SetForeground(); + top = &*goals.front(); + } goals[0]->Action(); for (auto goal = goals.begin(); goal != goals.end();) { if ((*goal)->Complete()) { @@ -482,6 +493,9 @@ void Creature::TickBrain(double dt) { ++goal; } } + if (&*goals.front() != top) { + goals.front()->SetForeground(); + } } math::AABB Creature::CollisionBox() const noexcept { @@ -499,8 +513,9 @@ glm::dmat4 Creature::CollisionTransform() const noexcept { } orient[0] = normalize(cross(orient[1], orient[2])); orient[2] = normalize(cross(orient[0], orient[1])); - return glm::translate(glm::dvec3(pos.x, pos.y, pos.z + half_size)) - * glm::dmat4(orient); + return glm::translate(glm::dvec3(pos.x, pos.y, pos.z)) + * glm::dmat4(orient) + * glm::translate(glm::dvec3(0.0, half_size, 0.0)); } glm::dmat4 Creature::LocalTransform() noexcept { diff --git a/src/creature/goal.cpp b/src/creature/goal.cpp index 4124876..021e32d 100644 --- a/src/creature/goal.cpp +++ b/src/creature/goal.cpp @@ -3,6 +3,7 @@ #include "IdleGoal.hpp" #include "IngestGoal.hpp" #include "LocateResourceGoal.hpp" +#include "StrollGoal.hpp" #include "Creature.hpp" #include "../app/Assets.hpp" @@ -74,7 +75,7 @@ void BlobBackgroundTask::CheckStats() { drink_subtask->Accept(cmp.resource, 1.0); } } - drink_subtask->OnComplete([&](Goal &) { drink_subtask = nullptr; }); + drink_subtask->WhenComplete([&](Goal &) { drink_subtask = nullptr; }); GetCreature().AddGoal(std::unique_ptr(drink_subtask)); } @@ -85,7 +86,7 @@ void BlobBackgroundTask::CheckStats() { eat_subtask->Accept(cmp.resource, 1.0); } } - eat_subtask->OnComplete([&](Goal &) { eat_subtask = nullptr; }); + eat_subtask->WhenComplete([&](Goal &) { eat_subtask = nullptr; }); GetCreature().AddGoal(std::unique_ptr(eat_subtask)); } @@ -99,7 +100,7 @@ void BlobBackgroundTask::CheckStats() { void BlobBackgroundTask::CheckSplit() { if (GetCreature().Mass() > GetCreature().OffspringMass() * 2.0 - && GetCreature().OffspringChance() > Assets().random.UNorm()) { + && GetCreature().OffspringChance() > Random().UNorm()) { GetCreature().GetSimulation().Log() << GetCreature().Name() << " split" << std::endl; Split(GetCreature()); return; @@ -108,10 +109,10 @@ void BlobBackgroundTask::CheckSplit() { void BlobBackgroundTask::CheckMutate() { // check for random property mutation - if (GetCreature().MutateChance() > Assets().random.UNorm()) { - double amount = 1.0 + (Assets().random.SNorm() * 0.05); - math::Distribution &d = GetCreature().GetGenome().properties.props[Assets().random.UInt(9)]; - if (Assets().random.UNorm() < 0.5) { + if (GetCreature().MutateChance() > Random().UNorm()) { + double amount = 1.0 + (Random().SNorm() * 0.05); + math::Distribution &d = GetCreature().GetGenome().properties.props[Random().UInt(9)]; + if (Random().UNorm() < 0.5) { d.Mean(d.Mean() * amount); } else { d.StandardDeviation(d.StandardDeviation() * amount); @@ -119,6 +120,113 @@ void BlobBackgroundTask::CheckMutate() { } } + +Goal::Goal(Creature &c) +: c(c) +, on_complete() +, on_foreground() +, on_background() +, urgency(0.0) +, interruptible(true) +, complete(false) { +} + +Goal::~Goal() noexcept { +} + +Situation &Goal::GetSituation() noexcept { + return c.GetSituation(); +} + +const Situation &Goal::GetSituation() const noexcept { + return c.GetSituation(); +} + +Steering &Goal::GetSteering() noexcept { + return c.GetSteering(); +} + +const Steering &Goal::GetSteering() const noexcept { + return c.GetSteering(); +} + +app::Assets &Goal::Assets() noexcept { + return c.GetSimulation().Assets(); +} + +const app::Assets &Goal::Assets() const noexcept { + return c.GetSimulation().Assets(); +} + +math::GaloisLFSR &Goal::Random() noexcept { + return Assets().random; +} + +void Goal::SetComplete() { + if (!complete) { + complete = true; + OnComplete(); + if (on_complete) { + on_complete(*this); + } + } +} + +void Goal::SetForeground() { + OnForeground(); + if (on_foreground) { + on_foreground(*this); + } +} + +void Goal::SetBackground() { + OnBackground(); + if (on_background) { + on_background(*this); + } +} + +void Goal::WhenComplete(std::function cb) noexcept { + on_complete = cb; + if (complete) { + on_complete(*this); + } +} + +void Goal::WhenForeground(std::function cb) noexcept { + on_foreground = cb; +} + +void Goal::WhenBackground(std::function cb) noexcept { + on_background = cb; +} + + +IdleGoal::IdleGoal(Creature &c) +: Goal(c) { + Urgency(-1.0); + Interruptible(true); +} + +IdleGoal::~IdleGoal() { +} + +std::string IdleGoal::Describe() const { + return "idle"; +} + +void IdleGoal::Action() { + // use boredom as chance per minute + if (Random().UNorm() < GetCreature().GetStats().Boredom().value * (1.0 / 3600.0)) { + PickActivity(); + } +} + +void IdleGoal::PickActivity() { + GetCreature().AddGoal(std::unique_ptr(new StrollGoal(GetCreature()))); +} + + namespace { std::string summarize(const Composition &comp, const app::Assets &assets) { @@ -204,6 +312,7 @@ void IngestGoal::Action() { GetSteering().Halt(); } else { // finally + // TODO: somehow this still gets interrupted Interruptible(false); ingesting = true; } @@ -213,7 +322,7 @@ void IngestGoal::Action() { locate_subtask->Accept(c.resource, c.value); } locate_subtask->Urgency(Urgency() + 0.1); - locate_subtask->OnComplete([&](Goal &){ locate_subtask = nullptr; }); + locate_subtask->WhenComplete([&](Goal &){ locate_subtask = nullptr; }); GetCreature().AddGoal(std::unique_ptr(locate_subtask)); } } @@ -235,81 +344,6 @@ bool IngestGoal::OnSuitableTile() { } -Goal::Goal(Creature &c) -: c(c) -, on_complete() -, urgency(0.0) -, interruptible(true) -, complete(false) { -} - -Goal::~Goal() noexcept { -} - -Situation &Goal::GetSituation() noexcept { - return c.GetSituation(); -} - -const Situation &Goal::GetSituation() const noexcept { - return c.GetSituation(); -} - -Steering &Goal::GetSteering() noexcept { - return c.GetSteering(); -} - -const Steering &Goal::GetSteering() const noexcept { - 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; - if (on_complete) { - on_complete(*this); - } - } -} - -void Goal::OnComplete(std::function cb) noexcept { - on_complete = cb; - if (complete) { - on_complete(*this); - } -} - - -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() { -} - - LocateResourceGoal::LocateResourceGoal(Creature &c) : Goal(c) , accept() @@ -348,6 +382,7 @@ void LocateResourceGoal::Action() { } else { double dist = glm::length2(GetSituation().Position() - target_pos); if (dist < 0.0001) { + searching = false; LocateResource(); } else { GetSteering().GoTo(target_pos); @@ -437,7 +472,7 @@ void LocateResourceGoal::SearchVicinity() { } } - if (best_rating) { + if (best_rating > 0.0) { found = true; searching = false; target_pos = normalize(pos + (double(best_pos.x) * step_x) + (double(best_pos.y) * step_y)) * planet.Radius(); @@ -446,10 +481,10 @@ void LocateResourceGoal::SearchVicinity() { found = false; searching = true; target_pos = GetSituation().Position(); - target_pos += Assets().random.SNorm() * step_x; - target_pos += Assets().random.SNorm() * step_y; + target_pos += Random().SNorm() * step_x; + target_pos += Random().SNorm() * step_y; // bias towards current heading - target_pos += GetSituation().Heading() * 0.5; + target_pos += GetSituation().Heading() * 1.5; target_pos = normalize(target_pos) * planet.Radius(); GetSteering().GoTo(target_pos); } @@ -460,5 +495,49 @@ bool LocateResourceGoal::NearTarget() const noexcept { return s.OnSurface() && length2(s.Position() - target_pos) < 0.5; } + +StrollGoal::StrollGoal(Creature &c) +: Goal(c) +, last(GetSituation().Position()) +, next(last) { +} + +StrollGoal::~StrollGoal() { +} + +std::string StrollGoal::Describe() const { + return "take a walk"; +} + +void StrollGoal::Enable() { + last = GetSituation().Position(); + GetSteering().Haste(0.0); + PickTarget(); +} + +void StrollGoal::Action() { + if (length2(next - GetSituation().Position()) < 0.0001) { + PickTarget(); + } +} + +void StrollGoal::OnBackground() { + SetComplete(); +} + +void StrollGoal::PickTarget() noexcept { + last = next; + next += GetSituation().Heading() * 1.5; + const glm::dvec3 normal(GetSituation().GetPlanet().NormalAt(GetSituation().Position())); + glm::dvec3 rand_x(GetSituation().Heading()); + if (std::abs(dot(normal, rand_x)) > 0.999) { + rand_x = glm::dvec3(normal.z, normal.x, normal.y); + } + glm::dvec3 rand_y = cross(normal, rand_x); + next += ((rand_x * Random().SNorm()) + (rand_y * Random().SNorm())) * 1.5; + next = normalize(next) * GetSituation().GetPlanet().Radius(); + GetSteering().GoTo(next); +} + } } diff --git a/src/ui/CreaturePanel.hpp b/src/ui/CreaturePanel.hpp index 92e81be..719af40 100644 --- a/src/ui/CreaturePanel.hpp +++ b/src/ui/CreaturePanel.hpp @@ -52,6 +52,9 @@ private: Label *age; Label *mass; Label *goal; + Label *pos; + Label *tile; + Label *head; Panel *composition; std::vector