Skip to content

Instantly share code, notes, and snippets.

@mazbox
Created June 4, 2024 08:37
Show Gist options
  • Save mazbox/bdad9ab0e4d9d4d6374a760f5c778ed3 to your computer and use it in GitHub Desktop.
Save mazbox/bdad9ab0e4d9d4d6374a760f5c778ed3 to your computer and use it in GitHub Desktop.
/*
* save to file, then type
* `mzgl-livecode Conway.h`
* its mouse interactive, you can click and drag to create new life.
*/
#pragma once
#include "App.h"
#include "Drawer.h"
#include <array>
class Cell {
public:
bool isOn() const { return on; }
void set(bool _on) { on = _on; }
double phase = 0;
float amp = 0;
private:
bool on = false;
};
class Game {
public:
std::vector<std::vector<Cell>> grid;
std::vector<std::vector<Cell>> nextGrid;
int w;
int h;
Game(int _w, int _h)
: w(_w)
, h(_h) {
grid.resize(w);
for (auto &g: grid)
g.resize(h);
nextGrid = grid;
}
VboRef vbo = nullptr;
void seed(int numStarters) {
for (int i = 0; i < numStarters; i++) {
grid[randi(w - 1)][randi(h - 1)].set(true);
}
}
void createVbo(Rectf canvas) {
Drawer d;
auto cellWidth = canvas.width / static_cast<float>(w);
auto cellHeight = canvas.height / static_cast<float>(h);
for (int i = 0; i < grid.size(); i++) {
for (int j = 0; j < grid[i].size(); j++) {
bool on = grid[i][j].isOn();
Rectf r(canvas.x + i * cellWidth, canvas.y + j * cellHeight, cellWidth, cellHeight);
if (on) {
d.setColor(1.f, 0.4f, 0.4f);
} else {
d.setColor(0.25f, 0.1f, 0.1f);
}
d.drawCircle(r.centre(), cellWidth / 2.5);
}
}
vbo = d.createVbo();
}
void step() {
for (int x = 0; x < w; x++) {
for (int y = 0; y < h; y++) {
int numNeighbors = getNumNeighbors(x, y);
auto cell = grid[x][y];
if (cell.isOn() && (numNeighbors < 2 || numNeighbors > 3)) {
cell.set(false);
} else if (!cell.isOn() && numNeighbors == 3) {
cell.set(true);
}
nextGrid[x][y] = cell;
}
}
std::swap(grid, nextGrid);
vbo = nullptr;
}
int getNumNeighbors(int x, int y) {
int total = 0;
if (x > 0) {
if (grid[x - 1][y].isOn()) total++;
if (y > 0 && grid[x - 1][y - 1].isOn()) total++;
if (y < h - 1 && grid[x - 1][y + 1].isOn()) total++;
}
if (y > 0 && grid[x][y - 1].isOn()) total++;
if (y < h - 1 && grid[x][y + 1].isOn()) total++;
if (x < w - 1) {
if (grid[x + 1][y].isOn()) total++;
if (y > 0 && grid[x + 1][y - 1].isOn()) total++;
if (y < h - 1 && grid[x + 1][y + 1].isOn()) total++;
}
return total;
}
bool isOn(int x, int y) const {
if (x < 0 || y < 0 || x >= w || y >= h) return false;
return grid[x][y].isOn();
}
void set(int x, int y, bool value) {
if (x < 0 || y < 0 || x >= w || y >= h) return;
grid[x][y].set(value);
}
void draw(Graphics &g, Rectf r) {
if (vbo == nullptr) {
createVbo(r);
}
g.setColor(1);
vbo->draw(g);
}
static constexpr double mult = M_PI * 2.f / 48000.f;
// float coordsToFreq(int x, int y) { return 128 + 128 * pow(2.f, x / 6.f) * (y + 1); }
std::vector<int> maj {0, 2, 4, 5, 7, 9, 11};
float coordsToFreq(int x, int y) {
int s = x + y;
int note = maj[s % maj.size()] + 12 * s / maj.size();
return 256 * pow(2.f, note / 12.f);
// return 128 + 128 * pow(2.f, x / 6.f);
}
void render(Cell &cell, int x, int y, float *outs, int frames) {
float freq = coordsToFreq(x, y);
float panL = mapf(x, 0, w, 1, 0);
float panR = 1.f - panL;
for (int i = 0; i < frames; i++) {
if (cell.isOn()) {
cell.amp += 0.1;
} else {
cell.amp *= 0.99;
}
cell.amp = std::clamp(cell.amp, 0.f, 1.f);
float out = sin(cell.phase)
//randf()
* 0.1 * 0.2 * cell.amp; // * (grid[x][y].isOn() ? 1 : 0);
cell.phase += mult * freq;
outs[i * 2] += out * panL;
outs[i * 2 + 1] += out * panR;
}
}
double s = 0;
void audioOut(float *outs, int frames, int chans) {
memset(outs, 0, sizeof(float) * frames * chans);
for (int x = 0; x < w; x++) {
for (int y = 0; y < h; y++) {
render(grid[x][y], x, y, outs, frames);
}
}
// render(grid[1][1], 1, 1, outs, frames);
// for (int i = 0; i < frames; i++) {
// outs[i * 2] = randuf() * 0.05;
// outs[i * 2 + 1] = sin(s); //randuf() * 0.05;
// s += M_PI * 2.f * 256 / 48000.f;
// }
}
};
class Conway : public App {
public:
Game game;
Conway(Graphics &g)
: App(g)
, game(16, 16) {
game.seed(200);
}
void update() override {
if (g.getFrameNum() % 5 == 0) {
game.step();
}
}
// void resized() {}
void draw() override {
g.clear(0);
game.draw(g, Rectf(0, 0, g.width, g.height));
}
vec2 lastTouch;
void setAtCoord(float x, float y) {
int xi = mapf(x, 0, g.width, 0, game.w, true);
int yi = mapf(y, 0, g.height, 0, game.h, true);
game.set(xi, yi, true);
}
void touchDown(float x, float y, int id) override {
setAtCoord(x, y);
game.createVbo(Rectf(0, 0, g.width, g.height));
lastTouch = {x, y};
}
void touchMoved(float x, float y, int id) override {
vec2 currTouch(x, y);
float len = glm::length(currTouch - lastTouch);
vec2 dir = glm::normalize(currTouch - lastTouch);
for (int i = 0; i < len; i++) {
auto pos = lastTouch + dir * static_cast<float>(i);
setAtCoord(pos.x, pos.y);
}
}
void touchUp(float x, float y, int id) override {}
void audioOut(float *outs, int frames, int chans) override { game.audioOut(outs, frames, chans); }
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment