The code I have here is preliminary. I am focusing on the clipping procedures. There seems to be a lot of if() statements, and I'm hoping someone will have a clever way of optimizing this at least a little bit.
If you're wondering what m_nStride is, it's the number of elements to add to any given pixel pointer to reach the pixel directly below it (y + 1, x + 0). But either way, that is unimportant pertaining to my question.
The general idea is to fill a rectangular area of the image (32-bpp) using coordinates left, top, width and height. In cases where the coordinates would cause that area to cross the boundaries of the image, the area would be clipped to fit inside the image instead of being interpreted as an error.
void Image::Clear(int nLeft, int nTop, int nWidth, int nHeight, DWORD dwColor)
{
if(nWidth <= 0) return;
if(nHeight <= 0) return;
if(nLeft >= m_nWidth) return;
if(nTop >= m_nHeight) return;
if(nLeft < 0)
{
nWidth += nLeft;
if(nWidth <= 0)
return;
nLeft = 0;
}
if(nTop < 0)
{
nHeight += nTop;
if(nHeight <= 0)
return;
nTop = 0;
}
if(nLeft + nWidth > m_nWidth)
{
nWidth -= ((nLeft + nWidth) - m_nWidth);
if(nWidth <= 0)
return;
}
if(nTop + nHeight > m_nHeight)
{
nHeight -= ((nTop + nHeight) - m_nHeight);
if(nHeight <= 0)
return;
}
DWORD *p = m_pBuffer + (m_nStride * nTop) + nLeft;
for(int y = 0; y < nHeight; y++)
{
for(int x = 0; x < nWidth; x++)
p[x] = dwColor;
p += m_nStride;
}
}
For performance, the if overhead is virtually zero compared to the for loops. Nonetheless, as an exercise here's a version with fewer checks. It clips the bounds first and then only needs to check width and height being positive.
void Image::Clear(int nLeft, int nTop, int nWidth, int nHeight, DWORD dwColor)
{
if(nLeft < 0)
{
nWidth += nLeft;
nLeft = 0;
}
if(nTop < 0)
{
nHeight += nTop;
nTop = 0;
}
if(nLeft + nWidth > m_nWidth)
{
nWidth = m_nWidth - nLeft;
}
if(nTop + nHeight > m_nHeight)
{
nHeight = m_nHeight - nTop;
}
if(nWidth <= 0) return;
if(nHeight <= 0) return;
DWORD *p = m_pBuffer + (m_nStride * nTop) + nLeft;
for(int y = 0; y < nHeight; y++)
{
for(int x = 0; x < nWidth; x++)
p[x] = dwColor;
p += m_nStride;
}
}
Related
So I tried making a basic game for testing movement with console. I decided to use screen buffers, but when I compile and run the c++ code the letter a appears with the player (character #)
Here's the code:
#include "pch.h"
#include <iostream>
#include <stdio.h>
#include <Windows.h>
int width = 50, height = 20;
int main()
{
wchar_t *screen = new wchar_t[width * height];
HANDLE hConsole = CreateConsoleScreenBuffer(GENERIC_READ | GENERIC_WRITE, 0, NULL, CONSOLE_TEXTMODE_BUFFER, NULL);
SetConsoleActiveScreenBuffer(hConsole);
DWORD bytesWritten = 0;
int x = width / 2, y = height / 2;
while (true) {
screen[width * height - 1] = '\0';
for (int i = 0; i < width * height; i++) screen[i] = L' ';
if (GetAsyncKeyState('W') & 1) y--;
else if (GetAsyncKeyState('S') & 1) y++;
if (GetAsyncKeyState('A') & 1) x--;
else if (GetAsyncKeyState('D') & 1) x++;
// keep the character inside
if (x < 0) x = 0;
else if (x > width-1) x = width-1;
if (y < 0) y = 0;
else if (y > height - 1) y = height - 1;
wsprintf(&screen[width * y + x], L"#");
WriteConsoleOutputCharacter(hConsole, screen, width * height, { 0,0 }, &bytesWritten);
}
return 0;
}
Running it looks like this:
I use visual studio 2017.
Why does the letter a appear?
Thanks in advance!
I'm trying to update a 128x128 D3DLOCKED_RECT with sub images using the following code, but it seems to squish them down along the top, the X offset is ignored and the y offset is 60 percent off.
I've also tried to make the texture the correct size and copy it into a 128x128 texture at the correct location using RECT, however this is very slow and didn't seem to work correctly when I attempted it. There must be way to do it using the raw pixel data?
Any help would be much appreciated :)
EDIT: I got it semi working using the below code, the locations are now correct and the sizes. But it's only using the blue channel and everything is grey scale (blue scale?)
srcdata = (byte *) pixels;
dstdata = (unsigned int *)lockrect.pBits;
for (y = yoffset; y < (yoffset + height); y++)
{
for (x = xoffset; x < (xoffset + width); x++)
{
dstdata[ ( y * lockrect.Pitch / dstbytes + x ) + 0] = (unsigned int)srcdata[0];
dstdata[ ( y * lockrect.Pitch / dstbytes + x ) + 1] = (unsigned int)srcdata[1];
dstdata[ ( y * lockrect.Pitch / dstbytes + x ) + 2] = (unsigned int)srcdata[0];
dstdata[ ( y * lockrect.Pitch / dstbytes + x ) + 3] = (unsigned int)srcdata[3];
srcdata += srcbytes;
}
}'
END Edit
Test call after creating the 128x128 texture:
int x, y;
byte temp[132*132*4];
// Test texture (pink and black checker)
for( y = 0; y < 16; y++ )
{
for( x = 0; x < 16; x++ )
{
if(( y < 8 ) ^ ( x < 8 ))
((uint *)&temp)[y*16+x] = 0xFFFF00FF;
else ((uint *)&temp)[y*16+x] = 0xFF000000;
}
}
UpdateSubImage (0, 0, 16, 16, temp )
The update Fuction:
void UpdateSubImage (int xoffset, int yoffset, int width, int height, const
GLvoid *pixels)
{
int x, y;
int srcbytes = 4; //Hard coded for now, as all tests are RGBA
int dstbytes = 4; // ^
byte *srcdata;
byte *dstdata;
D3DLOCKED_RECT lockrect;
pTexture->LockRect( 0, &lockrect, NULL, 0);
srcdata = (byte *) pixels;
dstdata = (byte *) lockrect.pBits;
dstdata += (yoffset * width + xoffset) * dstbytes;
for (y = yoffset; y < (yoffset + height); y++)
{
for (x = xoffset; x < (xoffset + width); x++)
{
if (srcbytes == 1)
{
if (dstbytes == 1)
dstdata[0] = srcdata[0];
else if (dstbytes == 4)
{
dstdata[0] = srcdata[0];
dstdata[1] = srcdata[0];
dstdata[2] = srcdata[0];
dstdata[3] = srcdata[0];
}
}
else if (srcbytes == 3)
{
if (dstbytes == 1)
dstdata[0] = ((int) srcdata[0] + (int) srcdata[1] + (int) srcdata[2]) / 3;
else if (dstbytes == 4)
{
dstdata[0] = srcdata[2];
dstdata[1] = srcdata[1];
dstdata[2] = srcdata[0];
dstdata[3] = 255;
}
}
else if (srcbytes == 4)
{
if (dstbytes == 1)
dstdata[0] = ((int) srcdata[0] + (int) srcdata[1] + (int) srcdata[2]) / 3;
else if (dstbytes == 4)
{
dstdata[0] = srcdata[2];
dstdata[1] = srcdata[1];
dstdata[2] = srcdata[0];
dstdata[3] = srcdata[3];
}
}
// advance
srcdata += srcbytes;
dstdata += dstbytes;
}
}
pTexture->UnlockRect(0);
}
What the output looks like:
What the output should look like:
You're assuming that the data accessable through lockrect.pBits is linear in memory. This is in general not the case. Instead you have a constant offset between your rows which is defined by the lockrect.Pitch value.
To get the address of a pixel in the destination use:
byte * destAddr = (lockrect.pBits + y * lockrect.Pitch + 4 * x);
// for 32 bit images. For other formats adjust the hard-coded 4.
Thanks for the help :), in the end the following code worked:
Can it be made faster?
for (y = yoffset; y < (yoffset + height); y++)
{
for (x = xoffset; x < (xoffset + width); x++)
{
ARGB pixel;
pixel.r = srcdata[0];
pixel.g = srcdata[1];
pixel.b = srcdata[2];
pixel.a = srcdata[3];
memcpy( &dstdata[lockrect.Pitch * y + dstbytes * x], &pixel, dstbytes );
srcdata += srcbytes;
}
}
I'm having some issues using freetype2 rendering text, adapting it from using SDL_ttf previously.
The code below results in the following image:
Clearly not the DejaVu Sans font that it's set to, but unfortunately I'm at a loss to what's going wrong.
I think the code is mostly self-explanatory - blit_rect.address is the destination buffer
/**
* Blit text to the screen.
* #param text Text to display.
* #param colour Colour of the text.
* #param xpos Absolute horizontal position at the display.
* #param ypos Absolute vertical position at the display.
* #param width Available width of the text (in pixels).
* #param align Horizontal alignment of the string.
*/
void VideoSystem::BlitText(const uint8 *text, uint32 colour, int xpos, int ypos, int width, Alignment align)
{
this->blit_rect.ValidateAddress();
int w, h;
this->GetTextSize(text, &w, &h);
ypos += h;
int real_w = std::min(w, width);
switch (align) {
case ALG_LEFT:
break;
case ALG_CENTER:
xpos += (width - real_w) / 2;
break;
case ALG_RIGHT:
xpos += width - real_w;
break;
default: NOT_REACHED();
}
bool use_kerning = FT_HAS_KERNING(this->face);
uint previous = 0;
FT_GlyphSlot slot = this->face->glyph;
for (const uint8 *pt = text; *pt != '\0';) {
uint32 u32;
int len = DecodeUtf8Char(pt, strlen((const char *)pt), &u32);
if (len == 0) break;
pt += len;
uint glyph = FT_Get_Char_Index(this->face, u32);
if (use_kerning && previous && glyph) {
FT_Vector delta;
FT_Get_Kerning(this->face, previous, glyph, FT_KERNING_DEFAULT, &delta);
xpos += delta.x >> 6;
}
if (FT_Load_Glyph(this->face, glyph, FT_LOAD_RENDER) != 0) {
}
uint32 *dest = this->blit_rect.address + (xpos + slot->bitmap_left) + (ypos - slot->bitmap_top) * this->blit_rect.pitch;
for (int i = 0; i < slot->bitmap.rows; i++) {
if (ypos - slot->bitmap_top + i < 0) continue;
for (int j = 0; j < slot->bitmap.pitch; j++) {
if (xpos + slot->bitmap_left + j < 0) continue;
if (slot->bitmap.buffer[i * slot->bitmap.pitch + j] > 0) {
dest[i * this->blit_rect.pitch + j] = slot->bitmap.buffer[i * slot->bitmap.pitch + j];
}
}
}
previous = glyph;
xpos += slot->advance.x >> 6;
ypos += slot->advance.y >> 6;
}
}
EDIT: Font is loaded with:
/* Freetype init */
if (FT_Init_FreeType(&this->library) != 0) {
}
if (FT_New_Face(this->library, font_name, 0, &this->face) != 0) {
}
/** #todo use actual screen dpi? */
if (FT_Set_Char_Size(this->face, 0, font_size * 64, 0, 0) != 0) {
}
EDIT2:
adding FT_Render_Glyph(slot, FT_RENDER_MODE_NORMAL); (or FT_RENDER_MODE_MONO) after the FT_Load_Glyph does nothing.
Any other comments about the quality appreciated, of course
So I have an image that I want to overlay with a checkerboard pattern.
This is what I have come up with so far:
for ( uint_8 nRow = 0; nRow < image.width(); ++nRow)
for (uint_8 nCol = 0; nCol < image.height(); ++nCol)
if(((nRow/20 + nCol/20) % 2) == 0)
memset(&image.data[nCol + nRow], 0, 1);
Produces a white image unfortunately. I dont think this is very performant because memset is called for every single pixel in the image instead of multiple.
Why does this code not produce a chckerboard pattern? How would you improve it?
For better performance, don't treat the image as a 2-dimensional entity. Instead, look at it as a 1D array of continuous data, where all lines of the image are arranged one after the other.
With this approach, you can write the pattern in one go with a single loop, where in every iteration you memset() multiple adjacent pixels and increase the index by twice the amount of pixels you set:
int data_size = image.width() * image.height();
for (auto it = image.data; it < image.data + data_size; it += 20) {
memset(it, 0, 20);
if (((it - data) + 40) % (20 * 400) == 0) {
it += 40;
} else if (((it - data) + 20) % (20 * 400) != 0) {
it += 20;
}
}
(Replace auto with the type of image.data if you're not using C++11; I suspect it's unsigned char*.)
This is quite friendly for the CPU cache prefetch. It's also friendly for the compiler, which can potentially vectorize and/or perform loop unrolling.
If you have an image's dimensions which are multiple of the checker square size :
(I coded in C but it is fairly easy to transpose to C++)
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#define uint unsigned int
#define WIDTH 40
#define HEIGHT 40
#define BLOCK_SIZE 5
void create_checker_row(uint* row, uint size_block, uint nb_col, uint offset )
{
uint ic;
for (ic = size_block*offset ; ic < nb_col; ic+= 2*size_block )
{
memset( (row + ic) , 0, size_block*sizeof(uint) );
}
}
int main()
{
uint ir,ic;
// image creation
uint* pixels = (uint*) malloc(WIDTH*HEIGHT*sizeof(uint));
for (ir = 0; ir < WIDTH; ir++)
{
for ( ic = 0; ic < HEIGHT; ic++)
{
// arbitrary numbers
pixels[ir*WIDTH + ic] = (ir*WIDTH + ic) % 57 ;
printf("%d,", pixels[ir*WIDTH + ic] );
}
printf("\n");
}
for (ir = 0; ir < WIDTH; ir++)
{
create_checker_row( pixels + ir*WIDTH , // pointer at the beggining of n-th row
BLOCK_SIZE , // horizontal length for square
WIDTH , // image width
(ir/BLOCK_SIZE) % 2 // offset to create the checker pattern
);
}
// validation
printf("\n");
printf("Validation \n");
printf("\n");
for (ir = 0; ir < WIDTH; ir++)
{
for ( ic = 0; ic < HEIGHT; ic++)
{
printf("%d,", pixels[ir*WIDTH + ic] );
}
printf("\n");
}
return 0;
}
Seems pretty checkered for me : http://ideone.com/gp9so6
I use this and stb_image_write.h
#include <stdlib.h>
#include <stb_image_write.h>
int main(int argc, char *argv[])
{
const int w = 256, h = 256, ch = 4, segments = 8, box_sz = w / segments;
unsigned char rgba_fg[4] = {255, 255, 0, 255}; //yellow
unsigned char rgba_bg[4] = {255, 0, 0, 255}; //red
unsigned char* data = calloc(w * h * ch, sizeof(unsigned char));
int swap = 0;
int fill = 0; /* set to 1 to fill fg first*/
unsigned char* col = NULL;
for(int i = 0; i < w * h; i++)
{
if(i % (w * box_sz) == 0 && i != 0)
swap = !swap;
if(i % box_sz == 0 && i != 0)
fill = !fill;
if(fill)
{
if(swap)
col = rgba_bg;
else
col = rgba_fg;
}else
{
if(swap)
col = rgba_fg;
else
col = rgba_bg;
}
for(int j = 0; j < ch; j++)
{
data[i*ch + j] = col[j];
}
}
stbi_write_png("checker.png", w, h, ch, data, 0);
free(data);
return 0;
}
Its a bit slow with large images but gets the job done if you cache them
I am creating a version of Conway's Game of Life. It is eventually going to be run on an Arduino and will control LEDs so the memory footprint is important. It seems that I have a memory leak, I believe that this leak occurs whilst frreing a two dimensional array. If anyone could help me with this then I would be very grateful.
Thanks,
Joe
VLD's output is:
c:\projects\gameoflifecpp\gameoflifecpp\gameoflifecpp.cpp (72): GameOfLifeCPP.exe!GenerateGrid + 0xA bytes
c:\projects\gameoflifecpp\gameoflifecpp\gameoflifecpp.cpp (185): GameOfLifeCPP.exe!ProcessGrid + 0x7 bytes
c:\projects\gameoflifecpp\gameoflifecpp\gameoflifecpp.cpp (46): GameOfLifeCPP.exe!wmain + 0x9 bytes
f:\dd\vctools\crt_bld\self_x86\crt\src\crtexe.c (552): GameOfLifeCPP.exe!__tmainCRTStartup + 0x19 bytes
f:\dd\vctools\crt_bld\self_x86\crt\src\crtexe.c (371): GameOfLifeCPP.exe!wmainCRTStartup
0x7C817077 (File and line number not available): kernel32.dll!RegisterWaitForInputIdle + 0x49 bytes
Code is:
// GameOfLifeCPP.cpp : Defines the entry point for the console application.
//
#include "stdafx.h"
#define _CRTDBG_MAP_ALLOC
#include <stdlib.h>
#include <crtdbg.h>
#include <vld.h>
#define WIDTH 75
#define HEIGHT 88
#define GENERATION_COUNT_LIMIT -1
long _generationCount = 0;
// These get set by controls on the table
long _delay = 1000;
bool _run = true;
bool _trail = true;
bool _randomize = false;
char* _colours = "roy";
int _tmain(int argc, _TCHAR* argv[])
{
system("pause");
short** grid = GenerateGrid(false);
short** trailGrid = GenerateGrid(true); // This is used to record all prev cells
while(_run)
{
if (_randomize)
{
grid = GenerateGrid(false);
trailGrid = GenerateGrid(true);
// Fade out LEDs
// Clear the historical grids that we compare
_randomize = false;
_generationCount = 0;
}
OutputGrid(grid, trailGrid);
if (_trail)
trailGrid = CalculateTrailGrid(grid, trailGrid);
short** nextGrid = ProcessGrid(grid);
// Release the old grid
for(int i = 0; i < sizeof(nextGrid); i++)
{
delete(grid[i]);
}
delete(grid);
grid = nextGrid;
// We don't want to just sleep we need to find out the start and end time
Sleep(_delay);
bool foundRecurance = false;
// Need to detect recurence, have a buffer of 5-10 prev grids and one
// hundredth ago, one thousanth etc that we compare to.
_generationCount++;
if (foundRecurance || _generationCount == GENERATION_COUNT_LIMIT)
_randomize = true;
_CrtDumpMemoryLeaks();
//system("pause");
}
return 0;
}
short** GenerateGrid(bool empty)
{
// The coordinates are y,x because it is simpler to output a row of chars
// when testing in the command line than it is to output a column of chars
short** grid = new short*[HEIGHT];
for(int y = 0; y < HEIGHT; y++)
{
short* row = new short[WIDTH];
for(int x = 0; x < WIDTH; x++)
{
// There is no point creating random numbers that we aren't going
// to use
if (empty)
row[x] = 0;
else
row[x] = rand() % 5 == 1 ? 1 : 0;
// Might want to adjust this or make it random
}
grid[y] = row;
}
return grid;
}
void OutputGrid(short** grid, short** trailGrid)
{
// This is terribly inefficent but I don't care since it is only for
// testing on my laptop
system("cls");
HANDLE hConsole;
hConsole = GetStdHandle(STD_OUTPUT_HANDLE);
for(int y = 0; y < HEIGHT; y++)
{
for(int x = 0; x < WIDTH; x++)
{
int curState = grid[y][x];
if (curState == 0 && _trail) // If it isn't alive then show the trail
curState = trailGrid[y][x];
switch (curState)
{
case 0: SetConsoleTextAttribute(hConsole, 0); break;
case 1: SetConsoleTextAttribute(hConsole, GetColour(0)); break;
case 2: SetConsoleTextAttribute(hConsole, GetColour(1)); break;
case -1: SetConsoleTextAttribute(hConsole, GetColour(2)); break;
}
//if (curState == 1 || curState == 2)
// std::cout << "*";
//else
std::cout << " ";
}
SetConsoleTextAttribute(hConsole, 15);
std::cout << std::endl;
}
}
int GetColour(int index)
{
int colour = 0;
switch(_colours[index])
{
case 'r': colour = 12; break;
case 'o': colour = 6; break;
case 'y': colour = 14; break;
}
colour = colour * 16;
return colour;
}
int ProcessCell(short** grid, int x, int y)
{
// Get the value for each of the surrounding cells
// We use the formula (x - 1 + WIDTH) % WIDTH because that means that if the
// Current cell is at 0,0 then top left is WIDTH-1,WIDTH-1 and so on.
// This makes the grid wrap around.
// We don't care if the cells value is 1 or 2 it is either live or dead
int topLeft = (
grid[(y - 1 + HEIGHT) % HEIGHT][(x - 1 + WIDTH) % WIDTH] > 0) ? 1 : 0;
int top = (grid[(y - 1 + HEIGHT) % HEIGHT][x] > 0) ? 1 : 0;
int topRight =
(grid[(y - 1 + HEIGHT) % HEIGHT][(x + 1 + WIDTH) % WIDTH] > 0) ? 1 : 0;
int left = (grid[y][(x - 1 + WIDTH) % WIDTH] > 0) ? 1 : 0;
int self = (grid[y][x] > 0) ? 1 : 0;
int right = (grid[y][(x + 1 + WIDTH) % WIDTH] > 0) ? 1 : 0;
int bottomLeft =
(grid[(y + 1 + HEIGHT) % HEIGHT][(x - 1 + WIDTH) % WIDTH] > 0) ? 1 : 0;
int bottom = (grid[(y + 1 + HEIGHT) % HEIGHT][x] > 0) ? 1 : 0;
int bottomRight =
(grid[(y + 1 + HEIGHT) % HEIGHT][(x + 1 + WIDTH) % WIDTH] > 0) ? 1 : 0;
// Count up the surrounding cells to decide the current cell's state
int liveCount = topLeft + top + topRight + left +
right + bottomLeft + bottom + bottomRight;
int live = 0;
if (self > 0)
{
// Both are alive, just different colours
if (liveCount == 2)
live = 1;
if (liveCount == 3)
live = 2;
}
else if (liveCount == 3)
{
// Brought back to life, we don't care that it is the wrong
// colour - it looks better
live = 1;
}
return live;
}
short** ProcessGrid(short** grid)
{
short** nextGrid = GenerateGrid(true);
for (int y = 0; y < HEIGHT; y++)
{
for (int x = 0; x < WIDTH; x++)
{
nextGrid[y][x] = ProcessCell(grid, x, y);
}
}
return nextGrid;
}
short** CalculateTrailGrid(short** grid, short** trailGrid)
{
// Any previously live cells are marked
short** nextGrid = GenerateGrid(true);
for (int y = 0; y < HEIGHT; y++)
{
for (int x = 0; x < WIDTH; x++)
{
int state = grid[y][x];
if (state == 0)
state = trailGrid[y][x]; // Not alive currently but was
if (state != 0)
state = -1;
nextGrid[y][x] = state;
}
}
return nextGrid;
}
Just a quick 5 min cleanup in notepad... should give you some ideas... avoids any possible memory leaks...
#include "stdafx.h"
#define _CRTDBG_MAP_ALLOC
#include <stdlib.h>
#include <crtdbg.h>
#include <vld.h>
#include <vector>
#define WIDTH 75
#define HEIGHT 88
#define GENERATION_COUNT_LIMIT -1
long _generationCount = 0;
// These get set by controls on the table
long _delay = 1000;
bool _run = true;
bool _trail = true;
bool _randomize = false;
char* _colours = "roy";
typedef std::vector<std::vector<short>> grid_t; // Use std::vector
int _tmain(int argc, _TCHAR* argv[])
{
system("pause");
grid_t grid = GenerateGrid(false);
grid_t trailGrid = GenerateGrid(true); // This is used to record all prev cells
while(_run)
{
if (_randomize)
{
grid = GenerateGrid(false);
trailGrid = GenerateGrid(true);
// Fade out LEDs
// Clear the historical grids that we compare
_randomize = false;
_generationCount = 0;
}
OutputGrid(grid, trailGrid);
if (_trail)
trailGrid = CalculateTrailGrid(grid, trailGrid);
grid_t nextGrid = ProcessGrid(grid);
// Release the old grid
grid = nextGrid;
// We don't want to just sleep we need to find out the start and end time
Sleep(_delay);
bool foundRecurance = false;
// Need to detect recurence, have a buffer of 5-10 prev grids and one
// hundredth ago, one thousanth etc that we compare to.
_generationCount++;
if (foundRecurance || _generationCount == GENERATION_COUNT_LIMIT)
_randomize = true;
_CrtDumpMemoryLeaks();
//system("pause");
}
return 0;
}
grid_t GenerateGrid(bool empty)
{
// The coordinates are y,x because it is simpler to output a row of chars
// when testing in the command line than it is to output a column of chars
grid_t grid;
for(int y = 0; y < HEIGHT; y++)
{
std::vector<short> row;
for(int x = 0; x < WIDTH; x++)
row[x] = empty ? 0 : rand() % 5 == 1 ? 1 : 0;
grid.push_back(row);
}
return grid;
}
void OutputGrid(const grid_t& grid, const grid_t& trailGrid)
{
// This is terribly inefficent but I don't care since it is only for
// testing on my laptop
system("cls");
HANDLE hConsole;
hConsole = GetStdHandle(STD_OUTPUT_HANDLE);
for(int y = 0; y < HEIGHT; y++)
{
for(int x = 0; x < WIDTH; x++)
{
int curState = grid[y][x];
if (curState == 0 && _trail) // If it isn't alive then show the trail
curState = trailGrid[y][x];
switch (curState)
{
case 0: SetConsoleTextAttribute(hConsole, 0); break;
case 1: SetConsoleTextAttribute(hConsole, GetColour(0)); break;
case 2: SetConsoleTextAttribute(hConsole, GetColour(1)); break;
case -1: SetConsoleTextAttribute(hConsole, GetColour(2)); break;
}
}
SetConsoleTextAttribute(hConsole, 15);
std::cout << std::endl;
}
}
int GetColour(int index)
{
switch(_colours[index])
{
case 'r': return 16 * 12;
case 'o': return 16 * 6;
case 'y': return 16 * 14;
default: return 0;
}
}
int ProcessCell(const grid_t& grid, int x, int y)
{
// Get the value for each of the surrounding cells
// We use the formula (x - 1 + WIDTH) % WIDTH because that means that if the
// Current cell is at 0,0 then top left is WIDTH-1,WIDTH-1 and so on.
// This makes the grid wrap around.
// We don't care if the cells value is 1 or 2 it is either live or dead
int topLeft = (grid[(y - 1 + HEIGHT) % HEIGHT][(x - 1 + WIDTH) % WIDTH] > 0) ? 1 : 0;
int top = (grid[(y - 1 + HEIGHT) % HEIGHT][x] > 0) ? 1 : 0;
int topRight = (grid[(y - 1 + HEIGHT) % HEIGHT][(x + 1 + WIDTH) % WIDTH] > 0) ? 1 : 0;
int left = (grid[y][(x - 1 + WIDTH) % WIDTH] > 0) ? 1 : 0;
int self = (grid[y][x] > 0) ? 1 : 0;
int right = (grid[y][(x + 1 + WIDTH) % WIDTH] > 0) ? 1 : 0;
int bottomLeft = (grid[(y + 1 + HEIGHT) % HEIGHT][(x - 1 + WIDTH) % WIDTH] > 0) ? 1 : 0;
int bottom = (grid[(y + 1 + HEIGHT) % HEIGHT][x] > 0) ? 1 : 0;
int bottomRight = (grid[(y + 1 + HEIGHT) % HEIGHT][(x + 1 + WIDTH) % WIDTH] > 0) ? 1 : 0;
// Count up the surrounding cells to decide the current cell's state
int liveCount = topLeft + top + topRight + left + right + bottomLeft + bottom + bottomRight;
int live = 0;
if (self > 0)
{
// Both are alive, just different colours
if (liveCount == 2)
live = 1;
if (liveCount == 3)
live = 2;
}
else if (liveCount == 3)
{
// Brought back to life, we don't care that it is the wrong
// colour - it looks better
live = 1;
}
return live;
}
grid_t ProcessGrid(const grid_t& grid)
{
grid_t nextGrid = GenerateGrid(true);
for (int y = 0; y < HEIGHT; y++)
{
for (int x = 0; x < WIDTH; x++)
nextGrid[y][x] = ProcessCell(grid, x, y);
}
return nextGrid;
}
grid_t CalculateTrailGrid(const grid_t& grid, const grid_t& trailGrid)
{
// Any previously live cells are marked
grid_t nextGrid = GenerateGrid(true);
for (int y = 0; y < HEIGHT; y++)
{
for (int x = 0; x < WIDTH; x++)
nextGrid[y][x] = state == 0 ? trailGrid[y][x] : -1;
}
return nextGrid;
}