Initial commit
commit
763537bb61
|
@ -0,0 +1,104 @@
|
|||
colony
|
||||
check
|
||||
life
|
||||
|
||||
|
||||
# Created by https://www.gitignore.io/api/c,linux,windows
|
||||
# Edit at https://www.gitignore.io/?templates=c,linux,windows
|
||||
|
||||
### C ###
|
||||
# Prerequisites
|
||||
*.d
|
||||
|
||||
# Object files
|
||||
*.o
|
||||
*.ko
|
||||
*.obj
|
||||
*.elf
|
||||
|
||||
# Linker output
|
||||
*.ilk
|
||||
*.map
|
||||
*.exp
|
||||
|
||||
# Precompiled Headers
|
||||
*.gch
|
||||
*.pch
|
||||
|
||||
# Libraries
|
||||
*.lib
|
||||
*.a
|
||||
*.la
|
||||
*.lo
|
||||
|
||||
# Shared objects (inc. Windows DLLs)
|
||||
*.dll
|
||||
*.so
|
||||
*.so.*
|
||||
*.dylib
|
||||
|
||||
# Executables
|
||||
*.exe
|
||||
*.out
|
||||
*.app
|
||||
*.i*86
|
||||
*.x86_64
|
||||
*.hex
|
||||
|
||||
# Debug files
|
||||
*.dSYM/
|
||||
*.su
|
||||
*.idb
|
||||
*.pdb
|
||||
|
||||
# Kernel Module Compile Results
|
||||
*.mod*
|
||||
*.cmd
|
||||
.tmp_versions/
|
||||
modules.order
|
||||
Module.symvers
|
||||
Mkfile.old
|
||||
dkms.conf
|
||||
|
||||
### Linux ###
|
||||
*~
|
||||
|
||||
# temporary files which can be created if a process still has a handle open of a deleted file
|
||||
.fuse_hidden*
|
||||
|
||||
# KDE directory preferences
|
||||
.directory
|
||||
|
||||
# Linux trash folder which might appear on any partition or disk
|
||||
.Trash-*
|
||||
|
||||
# .nfs files are created when an open file is removed but is still being accessed
|
||||
.nfs*
|
||||
|
||||
### Windows ###
|
||||
# Windows thumbnail cache files
|
||||
Thumbs.db
|
||||
Thumbs.db:encryptable
|
||||
ehthumbs.db
|
||||
ehthumbs_vista.db
|
||||
|
||||
# Dump file
|
||||
*.stackdump
|
||||
|
||||
# Folder config file
|
||||
[Dd]esktop.ini
|
||||
|
||||
# Recycle Bin used on file shares
|
||||
$RECYCLE.BIN/
|
||||
|
||||
# Windows Installer files
|
||||
*.cab
|
||||
*.msi
|
||||
*.msix
|
||||
*.msm
|
||||
*.msp
|
||||
|
||||
# Windows shortcuts
|
||||
*.lnk
|
||||
|
||||
# End of https://www.gitignore.io/api/c,linux,windows
|
|
@ -0,0 +1,19 @@
|
|||
default: c
|
||||
|
||||
c: check.c colony.c life.c
|
||||
gcc -std=c99 check.c -o check
|
||||
gcc -std=c99 colony.c -o colony
|
||||
gcc $(X) -std=c99 -O3 -pedantic -Wall life.c canvas.c player.c -o life -lm -lSDL2
|
||||
|
||||
test:
|
||||
./colony 0 | ./life | ./check 0
|
||||
./colony 1 | ./life | ./check 1
|
||||
./colony 2 | ./life | ./check 2
|
||||
./colony 3 | ./life | ./check 3
|
||||
./colony 4 | ./life | ./check 4
|
||||
./colony 5 | ./life | ./check 5
|
||||
./colony 6 | ./life | ./check 6
|
||||
|
||||
clean:
|
||||
rm -f *.hi *.o
|
||||
rm check colony life
|
|
@ -0,0 +1,126 @@
|
|||
# Infinite and Generic Cellular Automaton Simulator
|
||||
|
||||
Defaults to Game of Life. **See screenshot folder**.
|
||||
|
||||
An infinite game of life universe, with a graphical player.
|
||||
|
||||
License: MIT
|
||||
|
||||
![](screenshots/player_annotated.png)
|
||||
|
||||
## Overview
|
||||
|
||||
* Chunk based infinite universe. Automatically expands.
|
||||
* Interactive player and universe editor using SDL.
|
||||
* Can set the Born/Survive ruleset code (from command line).
|
||||
* Can choose the number to steps to take before opening the player
|
||||
or outputting to stdout (from command line).
|
||||
* Has tests.
|
||||
|
||||
## Contents
|
||||
|
||||
* Usage Guide
|
||||
* Dependencies.
|
||||
* Compilation and Tests.
|
||||
* Command line tool.
|
||||
* Using the player.
|
||||
* Development
|
||||
* Extensions.
|
||||
* Inspiration / Sources.
|
||||
* Technical Details
|
||||
* The world.
|
||||
* Stepping.
|
||||
* Limitations / Possible improvements.
|
||||
|
||||
# Usage Guide
|
||||
|
||||
## Dependencies
|
||||
|
||||
Requires SDL2.
|
||||
|
||||
## Compilation and Tests
|
||||
|
||||
# Compilation.
|
||||
make c
|
||||
|
||||
# Run test.
|
||||
./test.sh # does make test, but uses program return codes.
|
||||
./life --tests # runs life's tests.
|
||||
|
||||
## Command line tool
|
||||
|
||||
All the following commands either have or implicitly use --stdout
|
||||
|
||||
# Accept a colony from stdin and step it.
|
||||
./life
|
||||
|
||||
# Accept a colony from stdin and step it n times.
|
||||
./life n
|
||||
# eg:
|
||||
./live 100
|
||||
|
||||
# Accept a colony from stdin and use the B1357/S1357 ruleset
|
||||
./life B1357/S1357
|
||||
|
||||
# Run tests, then accept stdin, and step it, if tests successful.
|
||||
./life --tests --stdout
|
||||
|
||||
# Step a colony from ./colony
|
||||
./colony 6 | ./life
|
||||
|
||||
## Using the player
|
||||
|
||||
Space to toggle between pause and play.
|
||||
S to step / advance one frame.
|
||||
R to randomly set cells to dead or alive.
|
||||
Left click to toggle a cell's state.
|
||||
Drag the view using the middle or right mouse button.
|
||||
|
||||
All the following commands have --player
|
||||
|
||||
# Accept a colony from stdin and open the player immediately.
|
||||
./life --player
|
||||
|
||||
# Accept a colony from stdin, step it n times, then open the player.
|
||||
./life n --player
|
||||
|
||||
# Accept a colony from stdin, use the B1357/S1357 ruleset and open the player immediately.
|
||||
./life B1357/S1357 --player
|
||||
|
||||
# Run tests, accept stdin and step it, if tests successful.
|
||||
./life --tests --player
|
||||
|
||||
# Open the player.
|
||||
./life --player --no-stdin
|
||||
|
||||
# Load and play a colony from ./colony
|
||||
./colony 6 | ./life --player
|
||||
|
||||
|
||||
# Technical Details
|
||||
|
||||
## The world
|
||||
|
||||
The world is split up into "Chunks". Each chunk has a pointer to the chunks
|
||||
surrounding it. Chunks are also in a hash table to speed up searching for a chunk
|
||||
at a position.
|
||||
|
||||
## Stepping
|
||||
|
||||
The step logic is double buffered. An int array is used in order to count the
|
||||
neighbours, as each cell accessing the cells around it would be quite expensive.
|
||||
Whilst updating a chunk the neighbour finding logic looks at surrounding chunks
|
||||
for overhanging cells.
|
||||
|
||||
## Limitations / Possible Improvements
|
||||
|
||||
* A possible improvement would be to store the neighbours int array in the
|
||||
chunk, and then only update it when a cell is set.
|
||||
* A looping universe may be an interesting thing to experiment with.
|
||||
* Store chunk contents as a 64 bit integer instead of a bool array. I was
|
||||
originally planning this, but instead spent the time doing the player instead.
|
||||
* A zooming function, a fill function, or a rectangle drag function would
|
||||
be nice.
|
||||
* Being able to change the ruleset at runtime in the editor would be nice -
|
||||
however I rejected this as it would require some sort of SDL GUI library.
|
||||
Ctrl+D would have disabled stdin by the point of the player running.
|
|
@ -0,0 +1,435 @@
|
|||
#include "canvas.h"
|
||||
#include <math.h>
|
||||
#include <ctype.h>
|
||||
#include <string.h>
|
||||
#include <time.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
Chunk *createChunk(int x, int y)
|
||||
{
|
||||
Chunk *c = malloc(sizeof(Chunk));
|
||||
c->x = x;
|
||||
c->y = y;
|
||||
|
||||
for (int i = 0; i < 8; ++i)
|
||||
c->around[i] = NULL;
|
||||
|
||||
c->count = 0;
|
||||
c->current = malloc(sizeof(bool) * CHUNK_SIZE * CHUNK_SIZE);
|
||||
c->next = malloc(sizeof(bool) * CHUNK_SIZE * CHUNK_SIZE);
|
||||
c->down = NULL;
|
||||
for (int i = 0; i < CHUNK_SIZE * CHUNK_SIZE; ++i) {
|
||||
c->current[i] = false;
|
||||
c->next[i] = false;
|
||||
}
|
||||
|
||||
return c;
|
||||
}
|
||||
|
||||
void chunkPrint(Chunk *c, int w, int h)
|
||||
{
|
||||
if (w <= 0 || w > CHUNK_SIZE)
|
||||
w = CHUNK_SIZE;
|
||||
if (h <= 0 || h > CHUNK_SIZE)
|
||||
h = CHUNK_SIZE;
|
||||
|
||||
for (int y = 0; y < w; y++) {
|
||||
for (int x = 0; x < w; x++) {
|
||||
if (c->current[x + y * CHUNK_SIZE])
|
||||
printf("#");
|
||||
else
|
||||
printf(".");
|
||||
}
|
||||
printf("\n");
|
||||
}
|
||||
}
|
||||
|
||||
#define HASH(W, X, Y) ((X) + 100*(Y)) % (W)->hash_table_size
|
||||
|
||||
Chunk *canvasGetChunk(Canvas *w, int cx, int cy)
|
||||
{
|
||||
int hash = HASH(w, cx, cy);
|
||||
if (hash < 0)
|
||||
hash += w->hash_table_size;
|
||||
|
||||
Chunk *cursor = w->hash_table[hash];
|
||||
while (cursor) {
|
||||
if (cursor->x == cx && cursor->y == cy)
|
||||
return cursor;
|
||||
else
|
||||
cursor = cursor->down;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
||||
void chunkLink(EDIR dir1, EDIR dir2, Chunk *one, Chunk *two)
|
||||
{
|
||||
if (two) {
|
||||
one->around[dir1] = two;
|
||||
two->around[dir2] = one;
|
||||
} //else {
|
||||
//fprintf(stderr, "No link possible for (%d, %d)\n", one->x, one->y);
|
||||
//}
|
||||
}
|
||||
|
||||
Chunk *canvasCreateChunk(Canvas *w, int cx, int cy)
|
||||
{
|
||||
Chunk *c = createChunk(cx, cy);
|
||||
|
||||
int hash = HASH(w, cx, cy);
|
||||
if (hash < 0)
|
||||
hash += w->hash_table_size;
|
||||
|
||||
if (w->hash_table[hash]) {
|
||||
Chunk *cursor = w->hash_table[hash];
|
||||
while (cursor->down)
|
||||
cursor = cursor->down;
|
||||
cursor->down = c;
|
||||
} else
|
||||
w->hash_table[hash] = c;
|
||||
|
||||
chunkLink(ED_T, ED_B, c, canvasGetChunk(w, cx, cy - 1));
|
||||
chunkLink(ED_TR, ED_BL, c, canvasGetChunk(w, cx + 1, cy - 1));
|
||||
chunkLink(ED_R, ED_L, c, canvasGetChunk(w, cx + 1, cy));
|
||||
chunkLink(ED_BR, ED_TL, c, canvasGetChunk(w, cx + 1, cy + 1));
|
||||
chunkLink(ED_B, ED_T, c, canvasGetChunk(w, cx, cy + 1));
|
||||
chunkLink(ED_BL, ED_TR, c, canvasGetChunk(w, cx - 1, cy + 1));
|
||||
chunkLink(ED_L, ED_R, c, canvasGetChunk(w, cx - 1, cy));
|
||||
chunkLink(ED_TL, ED_BR, c, canvasGetChunk(w, cx - 1, cy - 1));
|
||||
|
||||
return c;
|
||||
}
|
||||
|
||||
Canvas *createCanvas()
|
||||
{
|
||||
Canvas *world = malloc(sizeof(Canvas));
|
||||
world->hash_table_size = 1000;
|
||||
world->hash_table = malloc(sizeof(Chunk*) * world->hash_table_size);
|
||||
for (int i = 0; i < world->hash_table_size; ++i)
|
||||
world->hash_table[i] = NULL;
|
||||
|
||||
world->center = canvasCreateChunk(world, 0, 0);
|
||||
|
||||
for (int i = 0; i < 8; ++i) {
|
||||
world->rule_birth[i] = false;
|
||||
world->rule_survive[i] = false;
|
||||
}
|
||||
// Ruleset will be set by calling canvasParseRuleset().
|
||||
|
||||
return world;
|
||||
}
|
||||
|
||||
typedef enum {
|
||||
EPS_None = 0,
|
||||
EPS_B,
|
||||
EPS_S
|
||||
} EParseState;
|
||||
|
||||
bool canvasParseRuleset(Canvas *world, const char *ruleset)
|
||||
{
|
||||
fprintf(stderr, "Ruleset: '%s'.\n", ruleset);
|
||||
for (int i = 0; i < 8; ++i) {
|
||||
world->rule_birth[i] = false;
|
||||
world->rule_survive[i] = false;
|
||||
}
|
||||
|
||||
EParseState state = EPS_None;
|
||||
for (int i = 0; i < strlen(ruleset); ++i) {
|
||||
char ch = ruleset[i];
|
||||
if (ch == 'B')
|
||||
state = EPS_B;
|
||||
else if (ch == 'S')
|
||||
state = EPS_S;
|
||||
else if (isdigit(ch)) {
|
||||
if (state == EPS_B)
|
||||
world->rule_birth[ch - '0'] = true;
|
||||
else if (state == EPS_S)
|
||||
world->rule_survive[ch - '0'] = true;
|
||||
else {
|
||||
fprintf(stderr, "Ruleset parse error: expecting B or S first, "
|
||||
"before any numbers!\n");
|
||||
return false;
|
||||
}
|
||||
} else
|
||||
state = EPS_None;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void canvasSetCell(Canvas *w, int x, int y, int cell)
|
||||
{
|
||||
int cx = floor((float)x / (float)CHUNK_SIZE);
|
||||
int cy = floor((float)y / (float)CHUNK_SIZE);
|
||||
|
||||
Chunk *c = canvasGetChunk(w, cx, cy);
|
||||
if (!c)
|
||||
c = canvasCreateChunk(w, cx, cy);
|
||||
|
||||
int inner_x = x % CHUNK_SIZE;
|
||||
int inner_y = y % CHUNK_SIZE;
|
||||
if (inner_x < 0)
|
||||
inner_x = inner_x + CHUNK_SIZE;
|
||||
if (inner_y < 0)
|
||||
inner_y = inner_y + CHUNK_SIZE;
|
||||
|
||||
bool b4 = c->current[inner_x + inner_y * CHUNK_SIZE];
|
||||
if (cell == 2)
|
||||
cell = !b4;
|
||||
|
||||
if (cell == 1 && !b4)
|
||||
c->count++;
|
||||
else if (cell == 0 && b4)
|
||||
c->count--;
|
||||
|
||||
c->current[inner_x + inner_y * CHUNK_SIZE] = cell;
|
||||
}
|
||||
|
||||
void chunkCollectNeighbours(Chunk *c, int neighbours[CHUNK_SIZE][CHUNK_SIZE])
|
||||
{
|
||||
for (int inner_y = 0; inner_y < CHUNK_SIZE; ++inner_y)
|
||||
for (int inner_x = 0; inner_x < CHUNK_SIZE; ++inner_x) {
|
||||
neighbours[inner_y][inner_x] = 0;
|
||||
}
|
||||
|
||||
for (int inner_y = 0; inner_y < CHUNK_SIZE; ++inner_y)
|
||||
for (int inner_x = 0; inner_x < CHUNK_SIZE; ++inner_x) {
|
||||
if (c->current[inner_x + inner_y * CHUNK_SIZE]) {
|
||||
bool has_above = (inner_y > 0);
|
||||
bool has_left = (inner_x > 0);
|
||||
bool has_below = (inner_y < CHUNK_SIZE - 1);
|
||||
bool has_right = (inner_x < CHUNK_SIZE - 1);
|
||||
|
||||
// Left column
|
||||
if (has_left) {
|
||||
if (has_above)
|
||||
neighbours[inner_y - 1][inner_x - 1] += 1;
|
||||
|
||||
neighbours[inner_y][inner_x - 1] += 1;
|
||||
|
||||
if (has_below)
|
||||
neighbours[inner_y + 1][inner_x - 1] += 1;
|
||||
}
|
||||
|
||||
// Middle column
|
||||
{
|
||||
if (has_above)
|
||||
neighbours[inner_y - 1][inner_x] += 1;
|
||||
|
||||
if (has_below)
|
||||
neighbours[inner_y + 1][inner_x] += 1;
|
||||
}
|
||||
|
||||
// Right column
|
||||
if (has_right) {
|
||||
if (has_above)
|
||||
neighbours[inner_y - 1][inner_x + 1] += 1;
|
||||
|
||||
neighbours[inner_y][inner_x + 1] += 1;
|
||||
|
||||
if (has_below)
|
||||
neighbours[inner_y + 1][inner_x + 1] += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Horizontal Scans
|
||||
Chunk *other = NULL;
|
||||
other = c->around[ED_T];
|
||||
if (other) {
|
||||
for (int i = 0; i < CHUNK_SIZE; ++i) {
|
||||
int x = i;
|
||||
int y = CHUNK_SIZE - 1;
|
||||
if (other->current[x + y * CHUNK_SIZE]) {
|
||||
if (i > 0)
|
||||
neighbours[0][i - 1] += 1;
|
||||
|
||||
neighbours[0][i] += 1;
|
||||
|
||||
if (i < CHUNK_SIZE - 1)
|
||||
neighbours[0][i + 1] += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
other = c->around[ED_B];
|
||||
if (other) {
|
||||
for (int i = 0; i < CHUNK_SIZE; ++i) {
|
||||
if (other->current[i]) {
|
||||
|
||||
if (i > 0)
|
||||
neighbours[CHUNK_SIZE - 1][i - 1] += 1;
|
||||
|
||||
neighbours[CHUNK_SIZE - 1][i] += 1;
|
||||
|
||||
if (i < CHUNK_SIZE - 1)
|
||||
neighbours[CHUNK_SIZE - 1][i + 1] += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Vertical Scans
|
||||
other = c->around[ED_R];
|
||||
if (other) {
|
||||
for (int i = 0; i < CHUNK_SIZE; ++i) {
|
||||
if (other->current[i * CHUNK_SIZE]) {
|
||||
if (i > 0)
|
||||
neighbours[i - 1][CHUNK_SIZE - 1] += 1;
|
||||
|
||||
neighbours[i][CHUNK_SIZE - 1] += 1;
|
||||
|
||||
if (i < CHUNK_SIZE - 1)
|
||||
neighbours[i + 1][CHUNK_SIZE - 1] += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
other = c->around[ED_L];
|
||||
if (other) {
|
||||
for (int i = 0; i < CHUNK_SIZE; ++i) {
|
||||
if (other->current[CHUNK_SIZE - 1 + i * CHUNK_SIZE]) {
|
||||
if (i > 0)
|
||||
neighbours[i - 1][0] += 1;
|
||||
|
||||
neighbours[i][0] += 1;
|
||||
|
||||
if (i < CHUNK_SIZE - 1)
|
||||
neighbours[i + 1][0] += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Diagonals
|
||||
other = c->around[ED_TL];
|
||||
if (other && other->current[CHUNK_SIZE - 1 + (CHUNK_SIZE - 1) * CHUNK_SIZE])
|
||||
neighbours[0][0] += 1;
|
||||
|
||||
other = c->around[ED_BL];
|
||||
if (other && other->current[CHUNK_SIZE - 1])
|
||||
neighbours[CHUNK_SIZE - 1][0] += 1;
|
||||
|
||||
other = c->around[ED_TR];
|
||||
if (other && other->current[(CHUNK_SIZE - 1) * CHUNK_SIZE])
|
||||
neighbours[0][CHUNK_SIZE - 1] += 1;
|
||||
|
||||
other = c->around[ED_BR];
|
||||
if (other && other->current[0])
|
||||
neighbours[CHUNK_SIZE - 1][CHUNK_SIZE - 1] += 1;
|
||||
}
|
||||
|
||||
void chunkStep(Canvas *world, Chunk *c)
|
||||
{
|
||||
// As an optimisation, collect neighbours by only looping through cells once
|
||||
//fprintf(stderr, " - Collecting neighbours.\n");
|
||||
int neighbours[CHUNK_SIZE][CHUNK_SIZE];
|
||||
chunkCollectNeighbours(c, neighbours);
|
||||
|
||||
// Print neighbours
|
||||
/*fprintf(stderr, " - Calculating neighbours:\n");
|
||||
for (int inner_y = 0; inner_y < CHUNK_SIZE; ++inner_y) {
|
||||
fprintf(stderr, " ");
|
||||
for (int inner_x = 0; inner_x < CHUNK_SIZE; ++inner_x) {
|
||||
fprintf(stderr, "%d ", neighbours[inner_y][inner_x]);
|
||||
}
|
||||
fprintf(stderr, "\n");
|
||||
}*/
|
||||
|
||||
// Now loop through all cells and update their state
|
||||
for (int inner_y = 0; inner_y < CHUNK_SIZE; ++inner_y)
|
||||
for (int inner_x = 0; inner_x < CHUNK_SIZE; ++inner_x) {
|
||||
bool alive = c->current[inner_x + inner_y * CHUNK_SIZE];
|
||||
int cneigh = neighbours[inner_y][inner_x];
|
||||
if (alive && !world->rule_survive[cneigh]) {
|
||||
c->next[inner_x + inner_y * CHUNK_SIZE] = false;
|
||||
c->count--;
|
||||
} else if (!alive && world->rule_birth[cneigh]) {
|
||||
c->next[inner_x + inner_y * CHUNK_SIZE] = true;
|
||||
c->count++;
|
||||
} else {
|
||||
c->next[inner_x + inner_y * CHUNK_SIZE] = alive;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void canvasStep(Canvas *w)
|
||||
{
|
||||
clock_t start = clock();
|
||||
|
||||
for (int i = 0; i < w->hash_table_size; ++i) {
|
||||
Chunk *c = w->hash_table[i];
|
||||
while (c) {
|
||||
chunkStep(w, c);
|
||||
c = c->down;
|
||||
}
|
||||
}
|
||||
|
||||
int count = 0;
|
||||
for (int i = 0; i < w->hash_table_size; ++i) {
|
||||
Chunk *c = w->hash_table[i];
|
||||
while (c) {
|
||||
count++;
|
||||
bool *tmp = c->current;
|
||||
c->current = c->next;
|
||||
c->next = tmp;
|
||||
c = c->down;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
clock_t end = clock();
|
||||
|
||||
fprintf(stderr, "Stepped %d cells (%d chunks). Took %d ms.\n",
|
||||
count * CHUNK_SIZE * CHUNK_SIZE, count,
|
||||
(int)floor(1000.0f * ((double) (end - start)) / CLOCKS_PER_SEC));
|
||||
}
|
||||
|
||||
void canvasGrow(Canvas *world)
|
||||
{
|
||||
for (int i = 0; i < world->hash_table_size; ++i) {
|
||||
Chunk *c = world->hash_table[i];
|
||||
while (c) {
|
||||
if (c->count > 0) {
|
||||
if (!c->around[ED_T])
|
||||
canvasCreateChunk(world, c->x, c->y - 1);
|
||||
if (!c->around[ED_B])
|
||||
canvasCreateChunk(world, c->x, c->y + 1);
|
||||
|
||||
if (!c->around[ED_L])
|
||||
canvasCreateChunk(world, c->x - 1, c->y);
|
||||
if (!c->around[ED_R])
|
||||
canvasCreateChunk(world, c->x + 1, c->y);
|
||||
|
||||
if (!c->around[ED_TL])
|
||||
canvasCreateChunk(world, c->x - 1, c->y - 1);
|
||||
if (!c->around[ED_TR])
|
||||
canvasCreateChunk(world, c->x + 1, c->y - 1);
|
||||
|
||||
if (!c->around[ED_BL])
|
||||
canvasCreateChunk(world, c->x - 1, c->y + 1);
|
||||
if (!c->around[ED_BR])
|
||||
canvasCreateChunk(world, c->x + 1, c->y + 1);
|
||||
}
|
||||
|
||||
c = c->down;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void canvasRandomise(Canvas *world)
|
||||
{
|
||||
for (int i = 0; i < world->hash_table_size; ++i) {
|
||||
Chunk *c = world->hash_table[i];
|
||||
while (c) {
|
||||
c->count = 0;
|
||||
for (int y = 0; y < CHUNK_SIZE; ++y)
|
||||
for (int x = 0; x < CHUNK_SIZE; ++x) {
|
||||
if (rand() % 2) {
|
||||
c->count++;
|
||||
c->current[x + y * CHUNK_SIZE] = true;
|
||||
} else{
|
||||
c->current[x + y * CHUNK_SIZE] = false;
|
||||
}
|
||||
}
|
||||
c = c->down;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,60 @@
|
|||
#ifndef CANVAS_H_INCLUDED
|
||||
#define CANVAS_H_INCLUDED
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
typedef enum {
|
||||
ED_T = 0,
|
||||
ED_TR,
|
||||
ED_R,
|
||||
ED_BR,
|
||||
ED_B,
|
||||
ED_BL,
|
||||
ED_L,
|
||||
ED_TL
|
||||
} EDIR;
|
||||
|
||||
#define CHUNK_SIZE 8
|
||||
struct SChunk
|
||||
{
|
||||
int count;
|
||||
bool *current;
|
||||
bool *next;
|
||||
int x;
|
||||
int y;
|
||||
|
||||
struct SChunk *down;
|
||||
struct SChunk *around[8];
|
||||
};
|
||||
typedef struct SChunk Chunk;
|
||||
|
||||
struct SCanvas
|
||||
{
|
||||
Chunk *center;
|
||||
|
||||
Chunk **hash_table;
|
||||
int hash_table_size;
|
||||
|
||||
bool rule_birth[8];
|
||||
bool rule_survive[8];
|
||||
};
|
||||
typedef struct SCanvas Canvas;
|
||||
|
||||
Chunk *createChunk(int x, int y);
|
||||
void chunkPrint(Chunk *c, int w, int h);
|
||||
void chunkLink(EDIR dir1, EDIR dir2, Chunk *one, Chunk *two);
|
||||
|
||||
Chunk *canvasGetChunk(Canvas *w, int cx, int cy);
|
||||
Chunk *canvasCreateChunk(Canvas *w, int cx, int cy);
|
||||
Canvas *createCanvas();
|
||||
bool canvasParseRuleset(Canvas *world, const char *ruleset);
|
||||
void canvasSetCell(Canvas *w, int x, int y, int cell);
|
||||
|
||||
void chunkCollectNeighbours(Chunk *c, int neighbours[CHUNK_SIZE][CHUNK_SIZE]);
|
||||
void canvasStep(Canvas *w);
|
||||
void canvasGrow(Canvas *world);
|
||||
void canvasRandomise(Canvas *world);
|
||||
|
||||
#endif
|
|
@ -0,0 +1,139 @@
|
|||
/* Test a colony which has been output from a game-of-life program. */
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdbool.h>
|
||||
#include <string.h>
|
||||
#include <ctype.h>
|
||||
|
||||
// The number of rows or columns allowed for an example, plus one to allow for
|
||||
// terminators.
|
||||
enum { MAX=10 };
|
||||
|
||||
// A colony is a 2D array of characters. Each row is a null-terminated string,
|
||||
// and the rows are terminated by an empty string.
|
||||
typedef char colony[MAX][MAX];
|
||||
|
||||
// Define the example colonies, each one tick further on than the corresponding
|
||||
// one in colony.c.
|
||||
colony colonies[] = {
|
||||
{
|
||||
".....",
|
||||
"..#..",
|
||||
"..#..",
|
||||
"..#..",
|
||||
".....",
|
||||
""
|
||||
}, {
|
||||
".....",
|
||||
".....",
|
||||
".###.",
|
||||
".....",
|
||||
".....",
|
||||
""
|
||||
}, {
|
||||
"....",
|
||||
".##.",
|
||||
".##.",
|
||||
"....",
|
||||
""
|
||||
}, {
|
||||
"......",
|
||||
"......",
|
||||
".#.#..",
|
||||
"..##..",
|
||||
"..#...",
|
||||
"......",
|
||||
""
|
||||
}, {
|
||||
"......",
|
||||
"......",
|
||||
"...#..",
|
||||
".#.#..",
|
||||
"..##..",
|
||||
"......",
|
||||
""
|
||||
}, {
|
||||
"......",
|
||||
"......",
|
||||
"..#...",
|
||||
"...##.",
|
||||
"..##..",
|
||||
"......",
|
||||
""
|
||||
}, {
|
||||
"......",
|
||||
"......",
|
||||
"...#..",
|
||||
"....#.",
|
||||
"..###.",
|
||||
"......",
|
||||
""
|
||||
}
|
||||
};
|
||||
|
||||
// Find the number of colonies in the above list.
|
||||
int ncolonies = sizeof(colonies) / sizeof(colony);
|
||||
|
||||
void fail() {
|
||||
fprintf(stderr, "Use e.g.: ./check 3\n");
|
||||
fprintf(stderr, "to check example 3 of a life colony\n");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
// Read a line of text from the standard input into the given character array.
|
||||
// Make sure that any pending text on the standard output has been printed
|
||||
// first. Remove the newline (\r or \n or both).
|
||||
void readline(int n, char line[n]) {
|
||||
fflush(stdout);
|
||||
fgets(line, n, stdin);
|
||||
line[strcspn(line, "\r\n")] = '\0';
|
||||
}
|
||||
|
||||
// Read a colony from the standard input into the given matrix, with the rows
|
||||
// terminated by an empty string.
|
||||
void readColony(colony col) {
|
||||
for (int i=0; ! feof(stdin); i++) {
|
||||
readline(MAX, col[i]);
|
||||
if (feof(stdin)) strcpy(col[i], "");
|
||||
}
|
||||
}
|
||||
|
||||
// Print out a colony.
|
||||
void print(colony col) {
|
||||
for (int i=0; strlen(col[i]) != 0; i++) {
|
||||
printf("%s\n", col[i]);
|
||||
}
|
||||
}
|
||||
|
||||
// Check a colony against the n'th example.
|
||||
void check(colony actual, int n) {
|
||||
bool ok = true, end = false;
|
||||
for (int i=0; ! end; i++) {
|
||||
if (strcmp(actual[i], colonies[n][i]) != 0) ok = false;
|
||||
if (strlen(colonies[n][i]) == 0) end = true;
|
||||
}
|
||||
if (ok) {
|
||||
printf("OK\n");
|
||||
}
|
||||
else {
|
||||
printf("actual colony read in:\n");
|
||||
print(actual);
|
||||
printf("colony expected:\n");
|
||||
print(colonies[n]);
|
||||
}
|
||||
}
|
||||
|
||||
// Get the colony number from the command line, then check to see if
|
||||
// the input colony matches the expected one.
|
||||
int main(int n, char *args[n]) {
|
||||
if (n != 2) fail();
|
||||
char *s = args[1];
|
||||
int len = strlen(s);
|
||||
if (len < 1) fail();
|
||||
for (int i=0; i<len; i++) if (! isdigit(s[i])) fail();
|
||||
int index = atoi(s);
|
||||
if (index < 0 | index >= ncolonies) fail();
|
||||
colony col;
|
||||
readColony(col);
|
||||
check(col, index);
|
||||
}
|
|
@ -0,0 +1,98 @@
|
|||
/* Generate a colony as test input to a game-of-life program. */
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <ctype.h>
|
||||
|
||||
// The number of rows or columns allowed for an example, plus one to allow for
|
||||
// terminators.
|
||||
enum { MAX=10 };
|
||||
|
||||
// A colony is a 2D array of characters. Each row is a null-terminated string,
|
||||
// and the rows are terminated by an empty string.
|
||||
typedef char colony[MAX][MAX];
|
||||
|
||||
// Define the example colonies.
|
||||
colony colonies[] = {
|
||||
{
|
||||
".....",
|
||||
".....",
|
||||
".###.",
|
||||
".....",
|
||||
".....",
|
||||
""
|
||||
}, {
|
||||
".....",
|
||||
"..#..",
|
||||
"..#..",
|
||||
"..#..",
|
||||
".....",
|
||||
""
|
||||
}, {
|
||||
"....",
|
||||
".##.",
|
||||
".##.",
|
||||
"....",
|
||||
""
|
||||
}, {
|
||||
"......",
|
||||
"..#...",
|
||||
"...#..",
|
||||
".###..",
|
||||
"......",
|
||||
"......",
|
||||
""
|
||||
}, {
|
||||
"......",
|
||||
"......",
|
||||
".#.#..",
|
||||
"..##..",
|
||||
"..#...",
|
||||
"......",
|
||||
""
|
||||
}, {
|
||||
"......",
|
||||
"......",
|
||||
"...#..",
|
||||
".#.#..",
|
||||
"..##..",
|
||||
"......",
|
||||
""
|
||||
}, {
|
||||
"......",
|
||||
"......",
|
||||
"..#...",
|
||||
"...##.",
|
||||
"..##..",
|
||||
"......",
|
||||
""
|
||||
}
|
||||
};
|
||||
|
||||
// Find the number of colonies in the above list.
|
||||
int ncolonies = sizeof(colonies) / sizeof(colony);
|
||||
|
||||
void fail() {
|
||||
fprintf(stderr, "Use: ./colony i\n");
|
||||
fprintf(stderr, "where i is a number from 0 to %d\n", ncolonies - 1);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
// Print out the n'th colony.
|
||||
void print(int n) {
|
||||
for (int i=0; strlen(colonies[n][i]) != 0; i++) {
|
||||
printf("%s\n", colonies[n][i]);
|
||||
}
|
||||
}
|
||||
|
||||
// Get the colony number from the command line, then call print.
|
||||
int main(int n, char *args[n]) {
|
||||
if (n != 2) fail();
|
||||
char *s = args[1];
|
||||
int len = strlen(s);
|
||||
if (len < 1) fail();
|
||||
for (int i=0; i<len; i++) if (! isdigit(s[i])) fail();
|
||||
int index = atoi(s);
|
||||
if (index < 0 | index >= ncolonies) fail();
|
||||
print(index);
|
||||
}
|
|
@ -0,0 +1,300 @@
|
|||
#include "canvas.h"
|
||||
#include <time.h>
|
||||
#include <ctype.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include "player.h"
|
||||
|
||||
#define MAX_STDIN_SIZE 1024
|
||||
int readFromStdin(Canvas *world, int *w, int *h)
|
||||
{
|
||||
int y = 0;
|
||||
char line[MAX_STDIN_SIZE];
|
||||
fprintf(stderr, "Waiting for world input.\n Ctrl+D to stop. '.' is dead, '#' is alive\n\n");
|
||||
while (!feof(stdin)) {
|
||||
while (fgets(line, MAX_STDIN_SIZE, stdin) == NULL && !feof(stdin));
|
||||
if (feof(stdin))
|
||||
break;
|
||||
|
||||
// Process Input
|
||||
for (int x = 0; x < MAX_STDIN_SIZE; x++) {
|
||||
char ch = line[x];
|
||||
if (ch == '\0')
|
||||
break;
|
||||
else if (ch == '#')
|
||||
canvasSetCell(world, x, y, true);
|
||||
else if (ch == '.')
|
||||
canvasSetCell(world, x, y, false);
|
||||
else if (ch == '\n') // row number is here in case of long lines in files
|
||||
y++;
|
||||
else {
|
||||
fprintf(stderr, "Syntax error on line %d character %d ('%c')\n", x, y, ch);
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (x > (*w))
|
||||
(*w) = x;
|
||||
}
|
||||
}
|
||||
(*h) = y;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
#define TEST(cond) if (!(cond)) { printf("Test failed: " #cond "\n"); return false;}
|
||||
|
||||
bool run_tests()
|
||||
{
|
||||
Canvas *world = createCanvas();
|
||||
|
||||
canvasSetCell(world, -1, -1, true);
|
||||
canvasSetCell(world, -1, 0, true);
|
||||
canvasSetCell(world, 4, -1, true);
|
||||
canvasSetCell(world, 0, -1, true);
|
||||
canvasSetCell(world, 0, 0, true);
|
||||
|
||||
Chunk *cursor = world->center;
|
||||
int neighbours[CHUNK_SIZE][CHUNK_SIZE];
|
||||
|
||||
TEST(cursor);
|
||||
TEST(cursor->x == 0);
|
||||
TEST(cursor->y == 0);
|
||||
TEST(cursor->around[ED_T]);
|
||||
TEST(cursor->around[ED_T]->current[0 + (CHUNK_SIZE - 1) * CHUNK_SIZE]);
|
||||
TEST(cursor->around[ED_L]);
|
||||
TEST(cursor->around[ED_TL]);
|
||||
TEST(!cursor->around[ED_TR]);
|
||||
TEST(!cursor->around[ED_R]);
|
||||
TEST(!cursor->around[ED_BR]);
|
||||
TEST(!cursor->around[ED_B]);
|
||||
TEST(!cursor->around[ED_BL]);
|
||||
{
|
||||
chunkCollectNeighbours(cursor, neighbours);
|
||||
int expected[CHUNK_SIZE][CHUNK_SIZE] = {
|
||||
{3, 2, 0, 1, 1, 1, 0, 0},
|
||||
{2, 1, 0, 0, 0, 0, 0, 0},
|
||||
{0, 0, 0, 0, 0, 0, 0, 0},
|
||||
{0, 0, 0, 0, 0, 0, 0, 0},
|
||||
{0, 0, 0, 0, 0, 0, 0, 0},
|
||||
{0, 0, 0, 0, 0, 0, 0, 0},
|
||||
{0, 0, 0, 0, 0, 0, 0, 0},
|
||||
{0, 0, 0, 0, 0, 0, 0, 0}
|
||||
};
|
||||
for (int x = 0; x < CHUNK_SIZE; ++x)
|
||||
for (int y = 0; y < CHUNK_SIZE; ++y) {
|
||||
if (neighbours[y][x] != expected[y][x])
|
||||
fprintf(stderr, "Check (%d, %d), got %d expected %d\n",
|
||||
x, y, neighbours[y][x], expected[y][x]);
|
||||
|
||||
TEST(neighbours[y][x] == expected[y][x]);
|
||||
}
|
||||
}
|
||||
|
||||
cursor = cursor->around[ED_T];
|
||||
TEST(cursor);
|
||||
TEST(cursor->around[ED_B]);
|
||||
TEST(cursor->around[ED_L]);
|
||||
TEST(cursor->around[ED_BL]);
|
||||
TEST(!cursor->around[ED_TL]);
|
||||
TEST(!cursor->around[ED_T]);
|
||||
TEST(!cursor->around[ED_TR]);
|
||||
TEST(!cursor->around[ED_R]);
|
||||
TEST(!cursor->around[ED_BR]);
|
||||
{
|
||||
chunkCollectNeighbours(cursor, neighbours);
|
||||
int expected[CHUNK_SIZE][CHUNK_SIZE] = {
|
||||
{0, 0, 0, 0, 0, 0, 0, 0},
|
||||
{0, 0, 0, 0, 0, 0, 0, 0},
|
||||
{0, 0, 0, 0, 0, 0, 0, 0},
|
||||
{0, 0, 0, 0, 0, 0, 0, 0},
|
||||
{0, 0, 0, 0, 0, 0, 0, 0},
|
||||
{0, 0, 0, 0, 0, 0, 0, 0},
|
||||
{2, 1, 0, 1, 1, 1, 0, 0},
|
||||
{3, 2, 0, 1, 0, 1, 0, 0}
|
||||
};
|
||||
for (int x = 0; x < CHUNK_SIZE; ++x)
|
||||
for (int y = 0; y < CHUNK_SIZE; ++y) {
|
||||
if (neighbours[y][x] != expected[y][x])
|
||||
fprintf(stderr, "Check (%d, %d), got %d expected %d\n",
|
||||
x, y, neighbours[y][x], expected[y][x]);
|
||||
|
||||
TEST(neighbours[y][x] == expected[y][x]);
|
||||
}
|
||||
}
|
||||
|
||||
cursor = cursor->around[ED_L];
|
||||
TEST(cursor);
|
||||
TEST(cursor->around[ED_B]);
|
||||
TEST(!cursor->around[ED_L]);
|
||||
TEST(!cursor->around[ED_BL]);
|
||||
TEST(!cursor->around[ED_TL]);
|
||||
TEST(!cursor->around[ED_T]);
|
||||
TEST(!cursor->around[ED_TR]);
|
||||
TEST(cursor->around[ED_R]);
|
||||
TEST(cursor->around[ED_BR]);
|
||||
{
|
||||
chunkCollectNeighbours(cursor, neighbours);
|
||||
int expected[CHUNK_SIZE][CHUNK_SIZE] = {
|
||||
{0, 0, 0, 0, 0, 0, 0, 0},
|
||||
{0, 0, 0, 0, 0, 0, 0, 0},
|
||||
{0, 0, 0, 0, 0, 0, 0, 0},
|
||||
{0, 0, 0, 0, 0, 0, 0, 0},
|
||||
{0, 0, 0, 0, 0, 0, 0, 0},
|
||||
{0, 0, 0, 0, 0, 0, 0, 0},
|
||||
{0, 0, 0, 0, 0, 0, 1, 2},
|
||||
{0, 0, 0, 0, 0, 0, 2, 3}
|
||||
};
|
||||
for (int x = 0; x < CHUNK_SIZE; ++x)
|
||||
for (int y = 0; y < CHUNK_SIZE; ++y) {
|
||||
if (neighbours[y][x] != expected[y][x])
|
||||
fprintf(stderr, "Check (%d, %d), got %d expected %d\n",
|
||||
x, y, neighbours[y][x], expected[y][x]);
|
||||
|
||||
TEST(neighbours[y][x] == expected[y][x]);
|
||||
}
|
||||
}
|
||||
|
||||
cursor = cursor->around[ED_B];
|
||||
TEST(cursor);
|
||||
TEST(!cursor->around[ED_B]);
|
||||
TEST(!cursor->around[ED_L]);
|
||||
TEST(!cursor->around[ED_BL]);
|
||||
TEST(!cursor->around[ED_TL]);
|
||||
TEST(cursor->around[ED_T]);
|
||||
TEST(cursor->around[ED_TR]);
|
||||
TEST(cursor->around[ED_R]);
|
||||
TEST(!cursor->around[ED_BR]);
|
||||
{
|
||||
chunkCollectNeighbours(cursor, neighbours);
|
||||
int expected[CHUNK_SIZE][CHUNK_SIZE] = {
|
||||
{0, 0, 0, 0, 0, 0, 2, 3},
|
||||
{0, 0, 0, 0, 0, 0, 1, 2},
|
||||
{0, 0, 0, 0, 0, 0, 0, 0},
|
||||
{0, 0, 0, 0, 0, 0, 0, 0},
|
||||
{0, 0, 0, 0, 0, 0, 0, 0},
|
||||
{0, 0, 0, 0, 0, 0, 0, 0},
|
||||
{0, 0, 0, 0, 0, 0, 0, 0},
|
||||
{0, 0, 0, 0, 0, 0, 0, 0}
|
||||
};
|
||||
for (int x = 0; x < CHUNK_SIZE; ++x)
|
||||
for (int y = 0; y < CHUNK_SIZE; ++y) {
|
||||
if (neighbours[y][x] != expected[y][x])
|
||||
fprintf(stderr, "Check (%d, %d), got %d expected %d\n",
|
||||
x, y, neighbours[y][x], expected[y][x]);
|
||||
|
||||
TEST(neighbours[y][x] == expected[y][x]);
|
||||
}
|
||||
}
|
||||
|
||||
fprintf(stderr, "All auto tests passed!\n");
|
||||
return true;
|
||||
}
|
||||
|
||||
void usage()
|
||||
{
|
||||
fprintf(stderr, "USAGE:\n"
|
||||
" ./life\n"
|
||||
" ./life --player\n"
|
||||
" ./life --player --no-stdin\n"
|
||||
" ./life --tests\n"); // , ./life n, where n is the number of steps, defaults to 1.
|
||||
}
|
||||
|
||||
typedef enum {
|
||||
EM_Auto = 0,
|
||||
EM_Stdout,
|
||||
EM_Player
|
||||
} OutputMode;
|
||||
|
||||
int main(int n, char *args[n])
|
||||
{
|
||||
// Parse command line input
|
||||
int steps = -1;
|
||||
char *ruleset = "B3/S23";
|
||||
OutputMode mode_output = EM_Auto;
|
||||
bool mode_from_stdin = true;
|
||||
bool mode_enable_tests = false;
|
||||
for (int i = 1; i < n; ++i) {
|
||||
char *arg = args[i];
|
||||
if (strlen(arg) >= 2 && strncmp("--", arg, 2) == 0) {
|
||||
arg += 2;
|
||||
if (strcmp(arg, "tests") == 0) {
|
||||
mode_enable_tests = true;
|
||||
} else if (strcmp(arg, "player") == 0) {
|
||||
mode_output = EM_Player;
|
||||
} else if (strcmp(arg, "stdout") == 0) {
|
||||
mode_output = EM_Stdout;
|
||||
} else if (strcmp(arg, "no-stdin") == 0) {
|
||||
mode_from_stdin = false;
|
||||
} else if (strcmp(arg, "stdin") == 0) {
|
||||
mode_from_stdin = true;
|
||||
} else {
|
||||
fprintf(stderr, "Invalid switch: %s\n", arg);
|
||||
usage();
|
||||
return 1;
|
||||
}
|
||||
} else {
|
||||
fprintf(stderr, "Mode: %s\n", arg);
|
||||
char *ptr = NULL;
|
||||
long a = strtol(arg, &ptr, 10);
|
||||
if (ptr == arg + strlen(arg) && a >= 0) {
|
||||
fprintf(stderr, "Set steps from command line: %ld.\n", a);
|
||||
steps = a;
|
||||
} else if (toupper(arg[0]) == 'B') {
|
||||
fprintf(stderr, "Set ruleset from command line: '%s'.\n", arg);
|
||||
ruleset = arg;
|
||||
} else {
|
||||
fprintf(stderr, "Not a valid number or ruleset: '%s'.\n", arg);
|
||||
usage();
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
srand(time(NULL));
|
||||
|
||||
|
||||
// Do tests?
|
||||
if (mode_enable_tests) {
|
||||
if (!run_tests())
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (mode_output != EM_Auto || !mode_enable_tests) {
|
||||
Canvas *world = createCanvas();
|
||||
|
||||
if (!canvasParseRuleset(world, ruleset))
|
||||
return 1;
|
||||
|
||||
// Read from stdin
|
||||
int w = 0;
|
||||
int h = 0;
|
||||
if (mode_from_stdin) {
|
||||
int ret = readFromStdin(world, &w, &h);
|
||||
if (ret > 0)
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (mode_output == EM_Player) {
|
||||
if (steps < 0)
|
||||
steps = 0;
|
||||
|
||||
for (int i = 0; i < steps; ++i) {
|
||||
canvasStep(world);
|
||||
canvasGrow(world);
|
||||
}
|
||||
return runPlayer(world);
|
||||
} else {
|
||||
if (steps < 0)
|
||||
steps = 1;
|
||||
|
||||
for (int i = 0; i < steps; ++i) {
|
||||
canvasStep(world);
|
||||
canvasGrow(world);
|
||||
}
|
||||
chunkPrint(world->center, w, h);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
|
@ -0,0 +1,201 @@
|
|||
#include "player.h"
|
||||
#include "math.h"
|
||||
#include <SDL2/SDL.h>
|
||||
|
||||
struct SPlayerState
|
||||
{
|
||||
bool paused;
|
||||
Canvas *world;
|
||||
int x;
|
||||
int y;
|
||||
|
||||
int last_mouse_x;
|
||||
int last_mouse_y;
|
||||
|
||||
SDL_Window *window;
|
||||
SDL_Surface *screenSurface;
|
||||
SDL_Renderer *renderer;
|
||||
};
|
||||
typedef struct SPlayerState PlayerState;
|
||||
|
||||
const int PIXEL_SIZE = 10;
|
||||
const int SCREEN_WIDTH = 640;
|
||||
const int SCREEN_HEIGHT = 480;
|
||||
|
||||
bool initSDL(PlayerState *state)
|
||||
{
|
||||
// Init SDL
|
||||
if (SDL_Init(SDL_INIT_VIDEO) < 0) {
|
||||
printf("SDL could not initialize! SDL_Error: %s\n", SDL_GetError());
|
||||
return false;
|
||||
}
|
||||
|
||||
// Create window
|
||||
state->window = SDL_CreateWindow("Game of Life",
|
||||
SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, SCREEN_WIDTH,
|
||||
SCREEN_HEIGHT, SDL_WINDOW_SHOWN);
|
||||
if (!state->window) {
|
||||
printf("Window could not be created! SDL_Error: %s\n", SDL_GetError());
|
||||
return false;
|
||||
}
|
||||
|
||||
// Create surface
|
||||
state->screenSurface = SDL_GetWindowSurface(state->window);
|
||||
if (!state->screenSurface) {
|
||||
printf("Surface could not be created! SDL_Error: %s\n", SDL_GetError());
|
||||
return false;
|
||||
}
|
||||
|
||||
// Create Renderer
|
||||
state->renderer = SDL_CreateRenderer(state->window, 0, SDL_RENDERER_ACCELERATED);
|
||||
if (!state->renderer) {
|
||||
printf("Renderer could not be created! SDL_Error: %s\n", SDL_GetError());
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool acceptInput(PlayerState *state)
|
||||
{
|
||||
SDL_Event e;
|
||||
while (SDL_PollEvent(&e) != 0) {
|
||||
if (e.type == SDL_QUIT) {
|
||||
return false;
|
||||
} else if (e.type == SDL_KEYUP) {
|
||||
switch (e.key.keysym.sym) {
|
||||
case SDLK_SPACE:
|
||||
state->paused = !state->paused;
|
||||
break;
|
||||
case SDLK_s:
|
||||
canvasStep(state->world);
|
||||
canvasGrow(state->world);
|
||||
break;
|
||||
case SDLK_r:
|
||||
canvasRandomise(state->world);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (e.type == SDL_MOUSEBUTTONDOWN) {
|
||||
if (e.button.button != SDL_BUTTON_LEFT) {
|
||||
int x, y;
|
||||
SDL_GetMouseState(&x, &y);
|
||||
state->last_mouse_x = x;
|
||||
state->last_mouse_y = y;
|
||||
}
|
||||
} else if (e.type == SDL_MOUSEBUTTONUP) {
|
||||
if (e.button.button == SDL_BUTTON_LEFT) {
|
||||
// Do set
|
||||
int x, y;
|
||||
SDL_GetMouseState(&x, &y);
|
||||
x = floor((float)(x + state->x) / (float)PIXEL_SIZE);
|
||||
y = floor((float)(y + state->y) / (float)PIXEL_SIZE);
|
||||
fprintf(stderr, "User toggled %d, %d\n", x, y);
|
||||
|
||||
canvasSetCell(state->world, x, y, 2);
|
||||
} else {
|
||||
state->last_mouse_x = -1;
|
||||
state->last_mouse_y = -1;
|
||||
}
|
||||
} else if (e.type == SDL_MOUSEMOTION &&
|
||||
state->last_mouse_x > -1 && state->last_mouse_y > -1) {
|
||||
int x, y;
|
||||
SDL_GetMouseState(&x, &y);
|
||||
|
||||
int dx = x - state->last_mouse_x;
|
||||
int dy = y - state->last_mouse_y;
|
||||
state->last_mouse_x = x;
|
||||
state->last_mouse_y = y;
|
||||
state->x -= dx;
|
||||
state->y -= dy;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void renderChunk(PlayerState *state, Chunk *c)
|
||||
{
|
||||
int px = c->x * PIXEL_SIZE * CHUNK_SIZE;
|
||||
int py = c->y * PIXEL_SIZE * CHUNK_SIZE;
|
||||
if (px > state->x + 700 || px < state->x - 700 ||
|
||||
py > state->y + 550 || py < state->y - 550)
|
||||
return;
|
||||
|
||||
for (int x = 0; x < CHUNK_SIZE; x++)
|
||||
for (int y = 0; y < CHUNK_SIZE; y++) {
|
||||
if (c->current[x + y *CHUNK_SIZE]) {
|
||||
SDL_SetRenderDrawColor(state->renderer, 0xFF, 0xFF, 0xFF, 0xFF);
|
||||
} else {
|
||||
SDL_SetRenderDrawColor(state->renderer, 0x22, 0x22, 0x22, 0xFF);
|
||||
}
|
||||
|
||||
|
||||
SDL_Rect fillRect = {
|
||||
(c->x * CHUNK_SIZE + x) * PIXEL_SIZE - state->x + 1,
|
||||
(c->y * CHUNK_SIZE + y) * PIXEL_SIZE - state->y + 1,
|
||||
PIXEL_SIZE - 2,
|
||||
PIXEL_SIZE - 2};
|
||||
SDL_RenderFillRect(state->renderer, &fillRect);
|
||||
}
|
||||
}
|
||||
|
||||
void render(PlayerState *state)
|
||||
{
|
||||
SDL_SetRenderDrawColor(state->renderer, 0x00, 0x00, 0x00, 0xFF);
|
||||
SDL_RenderClear(state->renderer);
|
||||
|
||||
SDL_SetRenderDrawColor(state->renderer, 0xFF, 0xFF, 0xFF, 0xFF);
|
||||
|
||||
|
||||
for (int i = 0; i < state->world->hash_table_size; ++i) {
|
||||
Chunk *c = state->world->hash_table[i];
|
||||
while (c) {
|
||||
renderChunk(state, c);
|
||||
c = c->down;
|
||||
}
|
||||
}
|
||||
|
||||
SDL_RenderPresent(state->renderer);
|
||||
}
|
||||
|
||||
int runPlayer(Canvas *world)
|
||||
{
|
||||
fprintf(stderr, "Running graphical player!\n");
|
||||
|
||||
PlayerState state;
|
||||
state.paused = false;
|
||||
state.world = world;
|
||||
state.x = 0;
|
||||
state.y = 0;
|
||||
state.last_mouse_x = -1;
|
||||
state.last_mouse_y = -1;
|
||||
|
||||
if (!initSDL(&state))
|
||||
return 1;
|
||||
|
||||
bool running = true;
|
||||
int timer = SDL_GetTicks();
|
||||
float count = 0;
|
||||
while (running) {
|
||||
int now = SDL_GetTicks();
|
||||
float dtime = (float)(now - timer) / 1000.0f;
|
||||
timer = now;
|
||||
|
||||
if (state.paused)
|
||||
count = 0;
|
||||
else {
|
||||
count += dtime;
|
||||
if (count > 0.1) {
|
||||
count = 0;
|
||||
canvasStep(world);
|
||||
canvasGrow(world);
|
||||
}
|
||||
}
|
||||
|
||||
running = acceptInput(&state);
|
||||
render(&state);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
#ifndef PLAYER_H_INCLUDED
|
||||
#define PLAYER_H_INCLUDED
|
||||
|
||||
#include "canvas.h"
|
||||
|
||||
int runPlayer(Canvas *world);
|
||||
|
||||
#endif
|
Binary file not shown.
After Width: | Height: | Size: 7.7 KiB |
Binary file not shown.
After Width: | Height: | Size: 8.8 KiB |
Binary file not shown.
After Width: | Height: | Size: 12 KiB |
Binary file not shown.
After Width: | Height: | Size: 14 KiB |
Binary file not shown.
After Width: | Height: | Size: 7.2 KiB |
Binary file not shown.
After Width: | Height: | Size: 8.0 KiB |
Loading…
Reference in New Issue