Skip to content

Instantly share code, notes, and snippets.

@mazbox
Created June 4, 2024 08:37
Show Gist options
  • Select an option

  • Save mazbox/bdad9ab0e4d9d4d6374a760f5c778ed3 to your computer and use it in GitHub Desktop.

Select an option

Save mazbox/bdad9ab0e4d9d4d6374a760f5c778ed3 to your computer and use it in GitHub Desktop.

Revisions

  1. mazbox created this gist Jun 4, 2024.
    207 changes: 207 additions & 0 deletions Conway.h
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,207 @@
    /*
    * 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); }
    };