3
\$\begingroup\$

This is a follow-up to my previous question.
I followed the given advice and implemented the MVC design pattern. As I've never used MVC before I am quite unsure about the quality or if it is even considered a "valid" MVC implementation.
The design is (I believe) quite straight-forward. Main acts as the user interacting with the controller. Controller updates the model which in turn updates the view. Despite my doubts it actually runs better than before. I haven't profiled anything but the earlier version would burn a lot of cycles doing nothing but displaying the state while now there is barely any usage even when the simulation is running.
I'm mainly wondering if more things from main should be moved into other parts or if this is fine as is? Any other observations are welcome as well of course.

main.cpp

#include "controller.h"

#include <SFML/Graphics.hpp>

#include <string>
#include <vector>

int main() {
    constexpr int width = 1440;
    constexpr int height = 900;

    sf::RenderWindow window(sf::VideoMode(width, height), "0",
                            sf::Style::Titlebar | sf::Style::Close);
    window.setFramerateLimit(60);

    GameOfLife::Controller controller{width, height, window};

    constexpr float update_delay = .5;
    sf::Clock clock;
    clock.restart();

    while (window.isOpen()) {
        sf::Event event;
        while (window.pollEvent(event)) {
            switch (event.type) {
                case sf::Event::Closed:
                    window.close();
                    break;

                case sf::Event::KeyPressed:
                    controller.on_keypress(event);
                    break;

                default:
                    break;
            }
        }

        window.clear();

        if (clock.getElapsedTime().asSeconds() >= update_delay) {
            controller.update_model();
            clock.restart();
        }

        controller.display_view();

        window.display();
    }
}

controller.h

#pragma once

#include "model.h"
#include "view.h"

#include <SFML/System.hpp>

namespace GameOfLife {

class Controller {
public:
    Controller(int const width, int const height, sf::RenderWindow& win);

    void update_model();
    void display_view();
    void on_keypress(sf::Event const& event);

private:
    View view;
    Model model;
    bool is_active = false;
};

}

controller.cpp

#include "controller.h"
#include "model.h"
#include "view.h"

#include <SFML/System.hpp>

namespace GameOfLife {

Controller::Controller(int const width, int const height, sf::RenderWindow& win)
    : view{width, height, win}
    , model{width, height, view}
{}

void Controller::update_model() {
    if (is_active) {
        model.update();
    }
}

void Controller::display_view() {
    view.display();
}

void Controller::on_keypress(sf::Event const& event) {
    if (event.key.code == sf::Keyboard::P) {
        is_active = !is_active;
    }

    if (!is_active && event.key.code == sf::Keyboard::U) {
        model.update();
    }

    if (event.key.code == sf::Keyboard::R) {
        model.reset();
    }
}

}

model.h

#pragma once

#include "view.h"

#include <cstdint>
#include <random>
#include <vector>

namespace GameOfLife {

class Model {
public:
    Model(int const width_, int const height_, View& view_);

    void update();
    void reset();

private:
    void seed_grid();
    int count_neighbors(int const m_origin, int const n_origin) const;
    void check_rules(int const pos, int const neighbors);
    void apply_rules(int pos, bool const state);

    int width;
    int height;
    std::vector<bool> grid_current;
    std::vector<bool> grid_next;
    View& view;
    std::mt19937 rng;
    std::uint64_t iterations = 0;
    static constexpr bool cell_alive = true;
    static constexpr bool cell_dead = false;
};

}

model.cpp

#include "model.h"
#include "view.h"

#include <cstddef>
#include <cstdint>
#include <ctime>
#include <random>
#include <vector>

namespace GameOfLife {

Model::Model(int const width_, int const height_, View& view_)
    : width{width_}
    , height{height_}
    , grid_current{}
    , grid_next{}
    , view{view_}
    , rng{}
{
    std::random_device rd;
    rng.seed(rd());

    reset();
}

void Model::update() {
    grid_current.swap(grid_next);

    ++iterations;

    for (int m = 0; m < height; ++m) {
        for (int n = 0; n < width; ++n) {
            check_rules(width * m + n, count_neighbors(m, n));
        }
    }
}

void Model::reset() {
    grid_current.clear();
    grid_current.resize(static_cast<std::size_t>(width * height));
    grid_next.clear();
    grid_next.resize(static_cast<std::size_t>(width * height));
    seed_grid();
    iterations = 0;
}

void Model::seed_grid() {
    std::uniform_int_distribution<int> pos_dist(0, width * height);
    std::uniform_int_distribution<int> cells(width * height / 2,
                                            width * height);

    int initial_cell_nr = cells(rng);
    while (initial_cell_nr > 0) {
        std::size_t pos = pos_dist(rng);
        if (grid_next[pos] == cell_dead) {
            grid_next[pos] = cell_alive;
            --initial_cell_nr;
            view.update(pos, cell_alive, 0U);
        }
    }
}

int Model::count_neighbors(int const m_origin, int const n_origin) const {
    int neighbors = 0;
    for (int m_current = m_origin - 1; m_current <= m_origin + 1; ++m_current) {
        for (int n_current = n_origin - 1; n_current <= n_origin + 1; ++n_current) {
            if (m_current < 0 || m_current >= height ||
                n_current < 0 || n_current >= width) {
                continue;
            }

            if (m_current == m_origin && n_current == n_origin) {
                continue;
            }

            if (grid_current[width * m_current + n_current] == cell_alive) {
                if (++neighbors > 3) {
                    return neighbors;
                }
            }
        }
    }
    return neighbors;
}

void Model::check_rules(int const pos, int const neighbors) {
    apply_rules(pos, grid_current[pos]);

    // Any live cell with fewer than two live neighbors dies,
    // as if caused by under population.
    if (grid_current[pos] == cell_alive && neighbors < 2) {
        apply_rules(pos, cell_dead);
    }

    // Any live cell with two or three live neighbors lives on
    if (grid_current[pos] == cell_alive && (neighbors == 2 || neighbors == 3)) {
        apply_rules(pos, cell_alive);
    }

    // Any live cell with more than three live neighbors dies,
    // as if by overpopulation.
    if (grid_current[pos] == cell_alive && neighbors > 3) {
        apply_rules(pos, cell_dead);
    }

    // Any dead cell with exactly three live neighbors becomes a live cell,
    // as if by reproduction.
    if (grid_current[pos] == cell_dead && neighbors == 3) {
        apply_rules(pos, cell_alive);
    }
}

void Model::apply_rules(int pos, bool const state) {
    grid_next[pos] = state;

    view.update(pos, state, iterations);
}

}

view.h

#pragma once

#include <SFML/Graphics.hpp>

#include <vector>

namespace GameOfLife {

class View {
public:
    View(int const width, int const height, sf::RenderWindow& win_);

    void update(int pos, bool const state, std::uint64_t const iterations_);
    void display();

private:
    std::vector<std::uint_fast8_t> pixmap;
    sf::Texture texture;
    sf::Sprite sprite;
    sf::RenderWindow& win;
    std::uint64_t iterations = 0;
    bool model_was_updated = false;
    static constexpr int offset_r = 0;
    static constexpr int offset_g = 1;
    static constexpr int offset_b = 2;
    static constexpr int offset_a = 3;
    static constexpr int rgba_quad = 4;
};

}

view.cpp

#pragma once

#include "view.h"

#include <string>

namespace GameOfLife  {

View::View(int const width_, int const height_, sf::RenderWindow& win_)
    : pixmap{std::vector<uint_fast8_t>(width_ * height_ * rgba_quad)}
    , texture{}
    , sprite{}
    , win{win_}
{
    for (int i = 0; i < width_ * height_ * rgba_quad; i += rgba_quad) {
        pixmap[i + offset_r] = 255;
        pixmap[i + offset_g] = 255;
        pixmap[i + offset_b] = 255;
        pixmap[i + offset_a] = 0;
    }

    texture.create(width_, height_);
    sprite.setTexture(texture);
    sprite.setPosition(0, 0);
}

void View::update(int pos, bool const state, std::uint64_t const iterations_) {
    pos *= rgba_quad;
    pos += offset_a;
    pixmap[pos] = state ? 255 : 0;
    iterations = iterations_;
    model_was_updated = true;
}

void View::display() {
    if (model_was_updated) {
        texture.update(&pixmap[0]);
        model_was_updated = false;
    }

    win.setTitle(std::to_string(iterations));
    win.draw(sprite);
}

}
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.