I finally finished my Conway's game of life in c++ project and i'd love to find out how to improve the code.
main.cpp
#include "Engine/Engine.h"
int main()
{
Conway::Engine Engine(1280, 1024);
Engine.Run();
return 0;
}
Engine.h
#pragma once
#include <SDL2/SDL.h>
#include <memory>
#include "Board.h"
// Namespace Conway to
// always enclose classes in a namespace.
namespace Conway
{
// The engine class handles
// everything about the program:
// It handles the window, the renderer,
// Drawing, Events, Game logic,
// and holds representations of the game's
// parts (Cell, Grid)
class Engine
{
public:
Engine(int ScreenWidth, int ScreenHeight);
~Engine();
void Run();
private:
void HandleEvents();
void Draw();
void DrawLines();
const int m_ScreenWidth;
const int m_ScreenHeight;
bool m_Update = false;
bool m_Running = true;
std::unique_ptr<Board> m_Board;
SDL_Window* m_Window;
SDL_Renderer* m_Renderer;
};
}
Engine.cpp
#include "Engine.h"
Conway::Engine::Engine(int ScreenWidth, int ScreenHeight)
: m_ScreenWidth{ScreenWidth}, m_ScreenHeight{ScreenHeight}
{
SDL_assert(SDL_Init(SDL_INIT_VIDEO) >= 0);
m_Window = SDL_CreateWindow(
"Conway's game of life",
SDL_WINDOWPOS_UNDEFINED,
SDL_WINDOWPOS_UNDEFINED,
m_ScreenWidth,
m_ScreenHeight,
SDL_WINDOW_SHOWN
);
SDL_assert(m_Window != NULL);
if (!m_Board)
{
Coord<int, int> ScreenSize = {m_ScreenWidth, m_ScreenHeight};
m_Board = std::make_unique<Board>(ScreenSize);
}
SDL_assert(m_Board != nullptr);
m_Renderer = SDL_CreateRenderer(m_Window, -1, SDL_RENDERER_ACCELERATED|SDL_RENDERER_PRESENTVSYNC);
SDL_assert(m_Renderer != NULL);
// Show lines
SDL_RenderClear(m_Renderer);
DrawLines();
}
Conway::Engine::~Engine()
{
SDL_DestroyWindow(m_Window);
SDL_DestroyRenderer(m_Renderer);
m_Window = NULL;
m_Renderer = NULL;
SDL_Quit();
}
void Conway::Engine::HandleEvents()
{
SDL_Event Event;
while(SDL_PollEvent(&Event))
{
switch(Event.type)
{
case SDL_QUIT:
m_Running = false;
break;
// Toggles the updating with a keypress
case SDL_KEYDOWN:
if (Event.key.keysym.sym == SDLK_SPACE)
{
m_Update = m_Update ? false : true;
DrawLines();
}
else if (Event.key.keysym.sym == SDLK_c)
{
m_Board->Clear();
}
break;
case SDL_MOUSEBUTTONDOWN:
if (!m_Update)
{
if (Event.button.button == SDL_BUTTON_LEFT)
{
m_Board->ToggleClickedCell({Event.button.x, Event.button.y});
}
}
break;
}
}
}
// Draws the grid
void Conway::Engine::Draw()
{
SDL_RenderClear(m_Renderer);
for (int i = 0; i < Board::GRID_HEIGHT; ++i)
{
for (int j = 0; j < Board::GRID_WIDTH; ++j)
{
if (m_Board->ReadCell(j + Board::GRID_WIDTH * i) == Board::Cell::Alive)
{
SDL_SetRenderDrawColor(m_Renderer, 255, 255, 255, 255);
}
else
{
SDL_SetRenderDrawColor(m_Renderer, 0, 0, 0, 255);
}
SDL_Rect rect;
rect.x = m_Board->GetCellSize().first * j;
rect.y = m_Board->GetCellSize().second * i;
rect.w = m_Board->GetCellSize().first;
rect.h = m_Board->GetCellSize().second;
SDL_RenderFillRect(m_Renderer, &rect);
}
}
if (!m_Update)
{
DrawLines();
}
SDL_RenderPresent(m_Renderer);
}
// This function draws
// the lines delimiting each cell.
// The first loop draws the horizontal
// lines, the second one the vertical lines.
void Conway::Engine::DrawLines()
{
SDL_SetRenderDrawColor(m_Renderer, 255, 255, 255, 255);
for (int i = 0; i < Board::GRID_HEIGHT; ++i)
{
if (i != 0)
{
SDL_RenderDrawLine(
m_Renderer,
0,
m_Board->GetCellSize().second * i,
m_Board->GetCellSize().first * Board::GRID_WIDTH,
m_Board->GetCellSize().second * i
);
}
}
for (int i = 0; i < Board::GRID_WIDTH; ++i)
{
if (i != 0)
{
SDL_RenderDrawLine(
m_Renderer,
m_Board->GetCellSize().first * i,
0,
m_Board->GetCellSize().first * i,
m_Board->GetCellSize().second * Board::GRID_HEIGHT
);
}
}
SDL_RenderPresent(m_Renderer);
SDL_SetRenderDrawColor(m_Renderer, 0, 0, 0, 255);
}
// Main game loop
void Conway::Engine::Run()
{
while (m_Running)
{
HandleEvents();
if (m_Update)
{
m_Board->Update();
}
Draw();
SDL_Delay(100);
}
}
Board.h
#pragma once
#include <vector>
#include "Coord.h"
namespace Conway
{
class Board
{
public:
Board(Coord<int, int> ScreenSize);
static constexpr int GRID_WIDTH = 80;
static constexpr int GRID_HEIGHT = 60;
Coord<int, int> GetCellSize() { return m_CellSize; }
void ToggleClickedCell(Coord<int, int> MouseCoords);
void Update();
void Clear();
enum class Cell
{
Dead,
Alive
};
private:
int CountAliveNeighbors(Coord<int, int> GridCell);
std::vector<Cell> m_Grid;
const Coord<int, int> m_CellSize;
public:
Cell ReadCell(int Index) { return m_Grid[Index]; }
};
}
Board.cpp
#include "Board.h"
#include <cmath>
Conway::Board::Board(Coord<int, int> ScreenSize)
: m_CellSize{ScreenSize.first / GRID_WIDTH, ScreenSize.second / GRID_HEIGHT}
{
int GridSize = GRID_WIDTH * GRID_HEIGHT;
std::vector<Cell> temp(GridSize, Cell::Dead);
m_Grid = temp;
}
void Conway::Board::Clear()
{
std::fill(m_Grid.begin(), m_Grid.end(), Cell::Dead);
}
int Conway::Board::CountAliveNeighbors(Coord<int, int> GridCell)
{
int count = 0;
for (int i = -1; i < 2; ++i)
{
for (int j = -1; j <2; ++j)
{
int absoluteX = GridCell.first + i;
int absoluteY = GridCell.second + j;
if (absoluteX == -1 || absoluteX == GRID_WIDTH ||
absoluteY == -1 || absoluteY == GRID_HEIGHT ||
(i == 0 && j == 0))
{
continue;
}
if (m_Grid[absoluteX + GRID_WIDTH * absoluteY] == Cell::Alive)
{
++count;
}
}
}
return count;
}
// Inverses the cell that was clicked on
void Conway::Board::ToggleClickedCell(Coord<int, int> Coords)
{
int ClickedCell = (floor(Coords.first / m_CellSize.first)) + GRID_WIDTH * (floor(Coords.second / m_CellSize.second));
m_Grid[ClickedCell] = m_Grid[ClickedCell] == Cell::Dead ? Cell::Alive : Cell::Dead;
}
void Conway::Board::Update()
{
std::vector<Cell> temp(m_Grid);
for (int i = 0; i < Board::GRID_HEIGHT; ++i)
{
for (int j = 0; j < GRID_WIDTH; ++j)
{
if (m_Grid[j + GRID_WIDTH * i] == Cell::Alive)
{
if (CountAliveNeighbors({j, i}) < 2 || CountAliveNeighbors({j, i}) > 3)
{
temp[j + GRID_WIDTH * i] = Cell::Dead;
}
}
else
{
if (CountAliveNeighbors({j, i}) == 3)
{
temp[j + GRID_WIDTH * i] = Cell::Alive;
}
}
}
}
m_Grid = temp;
}
Coord.h
#pragma once
template <typename T1, typename T2>
struct Coord
{
T1 first;
T2 second;
};