clay-template/src/simulation.cpp

192 lines
5.1 KiB
C++

#include "simulation.h"
#include "input.h"
#include <cstdint>
#include <ranges>
#include <random>
#include <algorithm>
#include <clay/clay.h>
#include <SDL3/SDL_log.h>
#include <SDL3/SDL_render.h>
namespace simulation {
std::set<Cell> living{};
static std::vector<Cell> overpopulated{};
static std::vector<Cell> underpopulated{};
static std::vector<Cell> born{};
#ifdef __glibc_likely
#define likely(cond_) __glibc_likely(cond_)
#else
#define likely(cond_) (cond_)
#endif
CellIterator::CellIterator(Cell begin, Cell end)
: state{ begin } , begin{ begin }, end{ end } {}
CellIterator::CellIterator(Cell begin, Cell end, Cell state)
: state{ state } , begin{ begin }, end{ end } {}
CellIterator &CellIterator::operator++() {
++(this->state.x);
if (this->state.x == this->end.x) {
this->state.x = this->begin.x;
this->state.y = SDL_min(this->state.y + 1, this->end.y);
}
return *this;
}
CellIterator &CellIterator::operator--() {
--(this->state.x);
if (this->state.x == this->begin.x) {
this->state.x = this->end.x - 1;
this->state.y = SDL_max(this->state.y - 1, this->begin.y);
}
return *this;
}
bool CellIterator::operator==(CellIterator const &src) const {
return src.begin == this->begin && src.end == this->end && (src.state == this->state || src.at_end() == this->at_end());
}
bool CellIterator::operator!=(CellIterator const &src) const {
return src.begin != this->begin || src.end != this->end || (src.state != this->state && src.at_end() != this->at_end());
}
Cell const &CellIterator::operator*() const {
return state;
}
bool CellIterator::at_end() const {
return this->state.y == this->end.y;
}
static std::vector<Cell> NeighborSet(Cell cell) {
std::vector<Cell> out{};
for (Cell const &c : CellRange{ { cell.x - 1, cell.y - 1 }, { cell.x + 2, cell.y + 2 } }) {
if (likely(c != cell)) {
out.push_back(c);
}
}
return out;
}
static size_t CountNeighbors(Cell const &cell) {
size_t count{ 0 };
for (Cell const &c : CellRange{ {cell.x - 1, cell.y - 1}, { cell.x + 2, cell.y + 2} }) {
if (c != cell && living.contains(c)) {
++count;
}
}
return count;
}
static void PopulateChanges() {
overpopulated.clear();
underpopulated.clear();
born.clear();
// TODO: consider multithreading, this is highly parallelisable
std::ranges::copy_if(living, std::back_inserter(overpopulated),
[&](Cell const &c) -> bool {
size_t const neighbors{ CountNeighbors(c) };
return neighbors > 3;
});
std::ranges::copy_if(living, std::back_inserter(underpopulated),
[&](Cell const &c) -> bool {
size_t const neighbors{ CountNeighbors(c) };
return neighbors < 2;
});
for (Cell const &cell : living) {
std::vector<Cell> neighbors{ NeighborSet(cell) };
std::ranges::copy_if(neighbors, std::back_inserter(born),
[&](Cell const &c) -> bool {
size_t const neighbors{ CountNeighbors(c) };
return !living.contains(c) && neighbors == 3;
});
}
}
void InitializeRandom(size_t livingChance, int64_t fillArea) {
living.clear();
Cell itr{ 0, 0 };
while (itr.y < fillArea) {
if (std::rand() % livingChance == 0 ) {
living.insert(itr);
}
++itr.x;
if (itr.x == fillArea) {
itr.x = 0;
++itr.y;
}
}
PopulateChanges();
}
void Step() {
for (Cell const &cell : underpopulated) {
living.erase(cell);
}
for (Cell const &cell : overpopulated) {
living.erase(cell);
}
for (Cell const &cell : born) {
living.insert(cell);
}
PopulateChanges();
}
static bool simulationHovered{ false };
static SDL_FPoint viewOffset{ 0, 0 };
void Draw(SDL_Renderer *renderer, double cellSizePercent) {
if (simulationHovered) {
viewOffset.x += input::scrollMotion.x;
viewOffset.y += input::scrollMotion.y;
}
int w;
SDL_GetCurrentRenderOutputSize(renderer, &w, nullptr);
float const cellWidth = static_cast<float>(w) * cellSizePercent;
SDL_FRect cellRect{
0, 0, cellWidth, cellWidth
};
SDL_SetRenderDrawColor(renderer, 255, 255, 255, 255);
for (Cell const &cell : living) {
cellRect.x = (viewOffset.x + cell.x) * cellRect.w;
cellRect.y = (viewOffset.y + cell.y) * cellRect.h;
SDL_RenderFillRect(renderer, &cellRect);
}
SDL_SetRenderDrawColor(renderer, 255, 0, 0, 255);
for (Cell const &cell : overpopulated) {
cellRect.x = (viewOffset.x + cell.x) * cellRect.w;
cellRect.y = (viewOffset.y + cell.y) * cellRect.h;
SDL_RenderRect(renderer, &cellRect);
}
SDL_SetRenderDrawColor(renderer, 255, 0, 255, 255);
for (Cell const &cell : underpopulated) {
cellRect.x = (viewOffset.x + cell.x) * cellRect.w;
cellRect.y = (viewOffset.y + cell.y) * cellRect.h;
SDL_RenderRect(renderer, &cellRect);
}
SDL_SetRenderDrawColor(renderer, 0, 255, 0, 255);
for (Cell const &cell : born) {
cellRect.x = (viewOffset.x + cell.x) * cellRect.w;
cellRect.y = (viewOffset.y + cell.y) * cellRect.h;
SDL_RenderRect(renderer, &cellRect);
}
}
void SetSimulationHovered(bool value) {
simulationHovered = value;
}
bool operator==(Cell const &lhs, Cell const &rhs) {
return lhs.x == rhs.x && lhs.y == rhs.y;
}
bool operator!=(Cell const &lhs, Cell const &rhs) {
return lhs.x != rhs.x || lhs.y != rhs.y;
}
bool operator<(Cell const &lhs, Cell const &rhs) {
if (lhs.y == rhs.y) return lhs.x < rhs.x;
else return lhs.y < rhs.y;
}
}