Created
June 4, 2024 08:37
-
-
Save mazbox/bdad9ab0e4d9d4d6374a760f5c778ed3 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
* 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