-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathmain-sfml.cpp
More file actions
244 lines (216 loc) · 8.64 KB
/
main-sfml.cpp
File metadata and controls
244 lines (216 loc) · 8.64 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
// Conway's Game of Life using SFML
// Copyright (c) 2025 Faraz Fallahi <fffaraz@gmail.com>
#include "Common.hpp"
#include <SFML/Graphics.hpp>
#include <atomic>
#include <iostream>
#include <thread>
std::atomic_bool mouseRightPressed = false;
std::atomic_bool mouseLeftPressed = false;
// Update the next grid state
static void updateGrid(const sf::RenderWindow& window)
{
// Get the writable next grid
auto [nextGrid, writeLock] = grid.writeBuffer();
// Handle right mouse button click
if (mouseRightPressed.load()) {
nextGrid.clear(); // Clear the grid
grid.swap(std::move(writeLock));
return;
}
// Get the current grid and update nextGrid - scope ensures readLock is released
{
const auto [currGrid, readLock] = grid.readBuffer();
nextGrid.updateGrid(currGrid);
}
// Add random noise to the grid
nextGrid.addNoise();
// Handle mouse movement while the left button is pressed
if (mouseLeftPressed.load()) {
const sf::Vector2i mousePos = sf::Mouse::getPosition(window);
const int x = mousePos.x / CELL_SIZE;
const int y = mousePos.y / CELL_SIZE;
if (x >= 0 && x < GRID_SIZE && y >= 0 && y < GRID_SIZE) {
nextGrid.toggleBlock({ x, y }); // Toggle a 3x3 block
}
}
// Swap the buffers
grid.swap(std::move(writeLock));
}
// Color map for cells based on the number of live neighbors
static const std::array<sf::Color, 9> colorMap {
sf::Color::Red, // 0 live neighbors
sf::Color::Green, // 1 live neighbor
sf::Color::Blue, // 2 live neighbors
sf::Color::Cyan, // 3 live neighbors
sf::Color::Magenta, // 4 live neighbors
sf::Color::Yellow, // 5 live neighbors
sf::Color::White, // 6 live neighbors
sf::Color::White, // 7 live neighbors
sf::Color::White, // 8 live neighbors
};
// Function to update the vertex array
int updateVertices(sf::RenderWindow& window, sf::VertexArray& vertices)
{
int numAlive = 0; // Count of alive cells
auto [currGrid, lock] = grid.readBuffer();
for (int i = 0; i < GRID_SIZE; ++i) {
for (int j = 0; j < GRID_SIZE; ++j) {
const Point p { i, j };
const bool cellAlive = currGrid.get(p);
numAlive += cellAlive ? 1 : 0;
#if 1 // Enable to color cells based on live neighbors
const sf::Color color = cellAlive ? colorMap[currGrid.countLiveNeighbors(p)] : sf::Color::Black;
#else
const sf::Color color = cellAlive ? sf::Color::White : sf::Color::Black;
#endif
if (CELL_SIZE == 1) {
const int index = (i * GRID_SIZE) + j;
vertices[index].color = color;
continue;
}
const int index = ((i * GRID_SIZE) + j) * 6;
vertices[index + 0].color = color;
vertices[index + 1].color = color;
vertices[index + 2].color = color;
vertices[index + 3].color = color;
vertices[index + 4].color = color;
vertices[index + 5].color = color;
}
}
return numAlive; // Return the number of alive cells
}
int main()
{
printInfo();
std::cout << "SFML version: " << SFML_VERSION_MAJOR << "." << SFML_VERSION_MINOR << "." << SFML_VERSION_PATCH << "\n";
// Create the main window
sf::RenderWindow window(sf::VideoMode({ GRID_SIZE * CELL_SIZE, GRID_SIZE * CELL_SIZE }), "Conway's Game of Life");
if (targetFPS > 0)
{
window.setFramerateLimit(targetFPS);
std::cout << "Framerate Limit: " << targetFPS << "\n";
}
std::cout.flush();
// Vertex array for the grid
sf::VertexArray vertices = CELL_SIZE > 1 ? sf::VertexArray(sf::PrimitiveType::Triangles, GRID_SIZE * GRID_SIZE * 6) : sf::VertexArray(sf::PrimitiveType::Points, GRID_SIZE * GRID_SIZE);
if (CELL_SIZE > 1) {
for (int i = 0; i < GRID_SIZE; ++i) {
for (int j = 0; j < GRID_SIZE; ++j) {
const int index = ((i * GRID_SIZE) + j) * 6;
const float x = (float)i * CELL_SIZE;
const float y = (float)j * CELL_SIZE;
vertices[index + 0].position = sf::Vector2f(x, y);
vertices[index + 1].position = sf::Vector2f(x + CELL_SIZE, y);
vertices[index + 2].position = sf::Vector2f(x + CELL_SIZE, y + CELL_SIZE);
vertices[index + 3].position = sf::Vector2f(x, y);
vertices[index + 4].position = sf::Vector2f(x, y + CELL_SIZE);
vertices[index + 5].position = sf::Vector2f(x + CELL_SIZE, y + CELL_SIZE);
}
}
} else {
for (int i = 0; i < GRID_SIZE; ++i) {
for (int j = 0; j < GRID_SIZE; ++j) {
const int index = (i * GRID_SIZE) + j;
const float x = (float)i * CELL_SIZE;
const float y = (float)j * CELL_SIZE;
vertices[index].position = sf::Vector2f(x, y);
}
}
}
// Load font for displaying text
sf::Font font;
const std::vector<std::string> fontPaths {
#ifdef _WIN32
"C:\\Windows\\Fonts\\Arial.ttf",
#else
"/usr/share/fonts/gnu-free/FreeSans.ttf",
"/usr/share/fonts/truetype/freefont/FreeSans.ttf",
"/usr/share/fonts/truetype/msttcorefonts/arial.ttf",
#endif
};
for (const auto& path : fontPaths) {
if (font.openFromFile(path)) {
break;
}
}
sf::Text txtNumAlive(font, "", 24);
txtNumAlive.setFillColor(sf::Color::White);
txtNumAlive.setPosition({ 10, 5 });
txtNumAlive.setOutlineThickness(2);
txtNumAlive.setOutlineColor(sf::Color::Black);
sf::Text txtFPS(font, "", 24);
txtFPS.setFillColor(sf::Color::White);
txtFPS.setPosition({ (float)window.getSize().x - 200, 5 });
txtFPS.setOutlineThickness(2);
txtFPS.setOutlineColor(sf::Color::Black);
// Start the grid update thread
std::atomic<float> epochsPerSecond = 0.0f;
std::jthread updateThread([&window, &epochsPerSecond](std::stop_token stop_token) {
sf::Clock epochClock;
int epochCount = 0;
while (!stop_token.stop_requested()) {
updateGrid(window);
epochCount++;
if (epochClock.getElapsedTime().asSeconds() >= 1.0f) {
epochsPerSecond = epochCount / epochClock.getElapsedTime().asSeconds();
epochCount = 0;
epochClock.restart();
}
}
});
// Clock for FPS calculation
sf::Clock fpsClock;
int frameCount = 0;
// Start the game loop
while (window.isOpen()) {
// Process events
while (const std::optional event = window.pollEvent()) {
if (event->is<sf::Event::Closed>()) {
window.close();
} else if (const auto* keyPressed = event->getIf<sf::Event::KeyPressed>()) {
if (keyPressed->scancode == sf::Keyboard::Scancode::Escape) {
window.close();
}
} else if (const auto* mousePressed = event->getIf<sf::Event::MouseButtonPressed>()) {
if (mousePressed->button == sf::Mouse::Button::Left) {
mouseLeftPressed.store(true);
} else if (mousePressed->button == sf::Mouse::Button::Right) {
mouseRightPressed.store(true);
}
} else if (const auto* mouseReleased = event->getIf<sf::Event::MouseButtonReleased>()) {
if (mouseReleased->button == sf::Mouse::Button::Left) {
mouseLeftPressed.store(false);
} else if (mouseReleased->button == sf::Mouse::Button::Right) {
mouseRightPressed.store(false);
}
}
}
// Update the grid
const int numAlive = updateVertices(window, vertices);
txtNumAlive.setString("Alive: " + std::to_string(numAlive));
// Update FPS counter
frameCount++;
if (fpsClock.getElapsedTime().asSeconds() >= 1.0f) {
const float fps = frameCount / fpsClock.getElapsedTime().asSeconds();
const float eps = epochsPerSecond.load();
const float cups = eps * (GRID_SIZE * GRID_SIZE / 1'000'000'000.0);
char buffer[50];
snprintf(buffer, sizeof(buffer), "FPS: %.2f\nEPS: %.2f\nCUpS: %.3fe9", fps, eps, cups);
txtFPS.setString(buffer);
frameCount = 0;
fpsClock.restart();
}
// Clear the window
window.clear();
// Draw the grid and texts
window.draw(vertices);
window.draw(txtNumAlive);
window.draw(txtFPS);
// Update the window
window.display();
}
// Stop the grid update thread
updateThread.request_stop();
return 0;
}