I have a basic version of CGoL running with pdCurses. My goal was to have each newly spawned cell take on the dominant color of their neighbors (if a spawned cell is mostly surrounded by red, make it red). I managed to get a half-baked solution working, but it has a few problems, mainly:
- It requires another member vector to hold the frequency of colors
- It requires aforementioned vector to be marked as mutable so the constness of other functions isn't affected
- It required creating a struct to return duel results (the neighbor count, and the dominant color)
- The color frequency storage scheme is slightly confusing
If someone can think of a cleaner method of achieving this, I would appreciate it. I'll also take any other kind of critique you may have.
My main function to count the neighbors is:
NeighborData Population::getNeighborData(int x, int y, int depth) const {
int count = 0;
for (int cY = y - depth; cY <= y + depth; cY++) {
if (cY < 0 || cY >= height) continue;
for (int cX = x - depth; cX <= x + depth; cX++) {
if (cX < 0 || cX >= width || (cX == x && cY == y)) continue;
unsigned char color = getPointColor(cX, cY);
if (color != '\0') {
count += 1;
colorFreqs[color] += 1;
}
}
}
unsigned char c = consumeColorFrequencies();
return NeighborData(count,c);
}
- vector
colorFreqs
has a pre-allocated slot for each color (only 16 on my machine). Every time we check a color, we look up the color using the color as an index, and increment its count. consumeColorFrequenices()
is the main function that I'm asking about. It "consumes" the frequency vector; returning the dominant color (or the first found color if more the one have an equal frequency)NeighborData
is a small struct with 2 members: the count, and the dominant color. I needed a way to return both bits of data at once to mydecideLifeOf()
method.
consumeColorFrequencies()
:
unsigned char Population::consumeColorFrequencies() const {
int hIndex = 0, highest = 0;
for (unsigned int i = 0; i < colorFreqs.size(); i++) {
unsigned char freq = colorFreqs[i];
if (freq > highest) {
hIndex = i, highest = freq;
}
}
//Set all color frequencies to 0
std::fill(colorFreqs.begin(), colorFreqs.end(), 0);
return hIndex;
}
And, the target use:
void Population::decideLifeOf(int x, int y) {
NeighborData nD = getNeighborData(x, y, 1);
unsigned int ns = nD.count;
unsigned char color = nD.color;
if (ns < 2 || ns > 3) killPoint(x, y);
else if (ns == 3) addPoint(x, y, color);
}
Population.h:
#ifndef POPULATION_H
#define POPULATION_H
#include <set>
#include <vector>
#include "curses.h"
struct NeighborData {
unsigned int count = 0;
unsigned char color = COLOR_WHITE;
NeighborData(unsigned int ct, unsigned char cr);
};
class Population {
//To hold the "finished" generation, and the generation
// currently being constructed
std::vector<unsigned char> cells;
std::vector<unsigned char> newCells;
//To temporarily hold frequencies of colors
//Index is the color, value is the number of occurances
mutable std::vector<unsigned int> colorFreqs;
int width = 0, height = 0;
public:
Population(int newWidth, int newHeight);
bool pointIsOccupied(int x, int y) const;
void addPoint(int x, int y, unsigned char color);
void killPoint(int x, int y);
unsigned char getPointColor(int x, int y) const;
NeighborData getNeighborData(int x, int y, int depth = 1) const;
void decideLifeOf(int, int);
int getIndexOf(int, int) const;
void replacePopulation();
unsigned char consumeColorFrequencies() const;
};
unsigned char randomColor(unsigned char starting = 1);
#endif
Population.cpp:
#include "Population.h"
#include <cstdlib>
#include <algorithm>
#include "curses.h"
NeighborData::NeighborData(unsigned int ct, unsigned char cr) {
count = ct, color = cr;
}
Population::Population(int newWidth, int newHeight) {
width = newWidth;
height = newHeight;
cells.resize(width * height);
newCells.resize(width * height);
colorFreqs.resize(COLORS);
}
bool Population::pointIsOccupied(int x, int y) const {
return cells[getIndexOf(x, y)] != '\0';
}
unsigned char Population::getPointColor(int x, int y) const {
return cells[getIndexOf(x, y)];
}
void Population::addPoint(int x, int y, unsigned char color) {
newCells[getIndexOf(x, y)] = color;
}
void Population::killPoint(int x, int y) {
newCells[getIndexOf(x, y)] = '\0';
}
NeighborData Population::getNeighborData(int x, int y, int depth) const {
int count = 0;
for (int cY = y - depth; cY <= y + depth; cY++) {
if (cY < 0 || cY >= height) continue;
for (int cX = x - depth; cX <= x + depth; cX++) {
if (cX < 0 || cX >= width || (cX == x && cY == y)) continue;
unsigned char color = getPointColor(cX, cY);
if (color != '\0') {
count += 1;
colorFreqs[color] += 1;
}
}
}
unsigned char c = consumeColorFrequencies();
return NeighborData(count,c);
}
void Population::decideLifeOf(int x, int y) {
NeighborData nD = getNeighborData(x, y, 1);
unsigned int ns = nD.count;
unsigned char color = nD.color;
if (ns < 2 || ns > 3) killPoint(x, y);
else if (ns == 3) addPoint(x, y, color);
}
int Population::getIndexOf(int x, int y) const {
return y * width + x;
}
void Population::replacePopulation() {
cells = newCells;
}
unsigned char randomColor(unsigned char starting) {
return (rand() % (COLORS - starting)) + starting;
}
unsigned char Population::consumeColorFrequencies() const {
int hIndex = 0, highest = 0;
for (unsigned int i = 0; i < colorFreqs.size(); i++) {
unsigned char freq = colorFreqs[i];
if (freq > highest) {
hIndex = i, highest = freq;
}
}
//Set all color frequencies to 0
std::fill(colorFreqs.begin(), colorFreqs.end(), 0);
return hIndex;
}
World.h:
#ifndef WORLD_H
#define WORLD_H
#include <set>
#include <sstream>
#include <limits>
#include <vector>
#include "Population.h"
class World {
Population pop;
int worldWidth = 0, worldHeight = 0;
public:
World(int, int);
void compileOutput(std::string disp = "#") const;
void simGeneration();
void randomizeCells(double chanceOfLife = 0.3, int newSeed = -1);
};
#endif
World.cpp:
#include "World.h"
#include <iomanip>
#include <set>
#include <cstdlib>
#include <string>
#include "curses.h"
World::World(int xMax, int yMax) :
pop(xMax,yMax) {
worldWidth = xMax;
worldHeight = yMax;
}
void World::compileOutput(std::string disp) const {
for (int cY = 0; cY < worldHeight; cY++) {
for (int cX = 0; cX < worldWidth; cX++) {
char c = pop.getPointColor(cX, cY);
init_pair(c, c, COLOR_BLACK); //(Pair number, fore color, back color)
attron(COLOR_PAIR(c));
mvprintw( cY, cX, (pop.pointIsOccupied(cX, cY) ? disp.c_str() : " ") );
attroff(COLOR_PAIR(c));
}
}
}
void World::simGeneration() {
for (int y = 0; y < worldHeight; y++) {
for (int x = 0; x < worldWidth; x++) {
pop.decideLifeOf(x,y);
}
}
pop.replacePopulation();
}
void World::randomizeCells(double chanceOfLife, int newSeed) {
if (newSeed > 0) srand(newSeed);
for (int y = 0; y < worldHeight; y++) {
for (int x = 0; x < worldWidth; x++) {
if ((rand() % int(1.0 / chanceOfLife)) == 0) {
unsigned char color = randomColor();
pop.addPoint(x, y, color);
}
}
}
pop.replacePopulation();
}
Timer.h:
#ifndef TIMER_H
#define TIMER_H
#include <chrono>
class Timer {
std::chrono::system_clock::time_point start;
public:
Timer();
void restart();
std::chrono::system_clock::time_point now();
double getMS();
double getSecs();
};
#endif
Timer.cpp:
#include "Timer.h"
#include <ctime>
Timer::Timer() {
start = now();
}
void Timer::restart() {
start = now();
}
std::chrono::system_clock::time_point Timer::now() {
return std::chrono::system_clock::now();
}
double Timer::getMS() {
return (now() - start).count() / 10000.0;
}
double Timer::getSecs() {
return getMS() / 1000.0;
}
Main.cpp:
#include "Timer.h"
#include "World.h"
#include <iostream>
#include <cstdlib>
#include <vector>
#include <chrono>
#include <thread>
#include "curses.h"
int main(int argc, char* argv[]) {
using namespace std;
initscr(); /* Start curses mode */
start_color();
noecho(); // Don't echo any keypresses
curs_set(FALSE); // Don't display a cursor
const long maxX = 60, maxY = 40;
World w(maxX, maxY);
w.randomizeCells(0.4, 10);
double lastDur = 1;
Timer t;
for (int rounds = 0; rounds < 5000; rounds++) {
clear();
w.compileOutput("#");
mvprintw(maxY + 1, 0, "%d", rounds);
w.simGeneration();
lastDur = t.getMS(); t.restart();
mvprintw(maxY + 2, 0, "%0.1f fps", 1000.0 / lastDur);
refresh();
this_thread::sleep_for(chrono::milliseconds( 50 ) );
}
endwin();
}
colorFreqs
is a temporary, then why don't you convert it into a local variable ingetNeighborData
and pass it as a parameter toconsumeColorFrequencies
? \$\endgroup\$std::array
. The overhead for construction and destruction is zero. \$\endgroup\$mutable
problem? Thanks, I'll try it. \$\endgroup\$simGeneration
method and possibly even increase your single threaded performance \$\endgroup\$