Wireworld/C: Difference between revisions

3,543 bytes added ,  12 years ago
replaced with much more usable code.
(Moving from main task page to shorten it)
 
(replaced with much more usable code.)
 
Line 1:
Run the program to see a short help message about key bindings.
Evolution is key-driven instead of being time-driven: any key (except 'q' which terminates the program) evolves the system.
 
{{libheader|OpenGL}}
 
{{libheader|GLUT}}
 
Compile with <code>gcc -lpthread -lglut -lGL -lGLU</code>. Run with <code>a.out file_name</code>. There are a couple of larger test files on the talk page. By default the program is compiled to use grayscale; compile time macro is available to switch to RGB.
<lang c>#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/mman.h>
#include <err.h>
#include <pthread.h>
#include <GL/glut.h>
#include <assertGL/gl.h>
#include <GL/glu.h>
 
enum { s_blank = 0, s_condu, s_etail, s_ehead };
#define WIDTH 20
typedef struct { unsigned char n, mark, t;} cell_t;
#define HEIGHT 20
typedef struct { int x, y; } pos_t;
#define MAX_SIZE 256
 
#define USE_RGB 0 /* enable this for hideous colors */
// Pixels Per Point (every "point" is an "element" of the wireworld)
#if USE_RGB
int PPP = 10;
int width = WIDTH, height = HEIGHT;
 
typedef struct { unsigned char r, g, b; } rgb_t;
int win;
rgb_t colors[] = {
{0, 0, 0},
{33, 45, 10},
{30, 100, 80},
{255, 0, 0},
};
 
#define TEX_COMP 3
int fc = 0;
#define TEX_MODE GL_RGB
char *field[2];
 
#else /* grayscale, less texture transfer */
 
typedef unsigned char rgb_t;
// free mem hook
rgb_t colors[] = { 0, 64, 144, 255 };
void freemem(void)
#define TEX_COMP 1
#define TEX_MODE GL_LUMINANCE
 
#endif
 
rgb_t **tex;
 
int tex_w = 1;
 
cell_t **cells;
pos_t *heads, *bakup;
int n_heads, n_bakup;
int rows = 0, cols = 0, n_cond = 0;
int evolve_delay = 128, show_delay = 16;
int paused = 0, single = 0;
int min_y, max_y;
 
pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
 
int gwin;
GLuint texture;
 
void mark_neighbors(pos_t *p)
{
int x, y;
free(field[0]);
for (y = p->y - 1; y <= p->y + 1 && y < rows; y++) {
free(field[1]);
if (y < 0) continue;
for (x = p->x - 1; x <= p->x + 1 && x < cols; x++) {
if ( x < 0
|| (x == p->x && y == p->y)
|| cells[y][x].t != s_condu ) continue;
 
cells[y][x].n++;
 
if (!cells[y][x].mark) {
cells[y][x].mark = 1;
bakup[n_bakup].x = x;
bakup[n_bakup].y = y;
n_bakup ++;
}
}
}
}
 
void evolve()
// BLACK
{
#define EMPTY 0.0, 0.0, 0.0
int i;
// head WHITE
pos_t *p;
#define E_HEAD 1.0, 1.0, 1.0
cell_t *c;
// tail GRAY
n_bakup = 0;
#define E_TAIL 0.7, 0.7, 0.7
for (i = 0, p = heads; i < n_heads; i++, p++) {
// conductor RED
c = &cells[p->y][p->x];
#define CONDUCTOR 1.0, 0.0, 0.0
switch(c->t) {
case s_ehead: mark_neighbors(p);
case s_etail: bakup[n_bakup++] = *p;
}
}
 
for ( i = 0; i < n_bakup; c->n = 0) {
#define cEMPTY ' '
p = bakup + i;
#define cHEAD 'H'
c = &cells[p->y][p->x];
#define cTAIL 't'
c->mark = 0;
#define cCONDUCTOR '.'
if (p->y < min_y) min_y = p->y;
if (p->y > max_y) max_y = p->y;
 
switch(c->t) {
// GL coords are from -1 to 1
case s_ehead: c->t = s_etail; i++;
#define GLCOORD_X(a) ( (double)(2.0*(a))/(double)(width*PPP) - 1.0 )
tex[p->y][p->x] = colors[s_etail];
#define GLCOORD_Y(a) ( (double)(2.0*(a))/(double)(height*PPP) - 1.0 )
continue;
case s_etail: c->t = s_condu;
tex[p->y][p->x] = colors[s_condu];
break;
case s_condu: if (c->n > 2) break;
c->t = s_ehead;
tex[p->y][p->x] = colors[s_ehead];
i++;
continue;
}
*p = bakup[--n_bakup];
}
 
n_heads = n_bakup;
// show wireworld
p = heads; heads = bakup; bakup = p;
void wireworld_show(void)
if (single)
paused = 1;
}
 
#define die(act) err(1, "Can't %s %s", act, fn);
int read_file(char *fn)
{
struct stat st;
int j, i;
size_t ofs;
int fd = open(fn, O_RDONLY);
if (fd == -1) die("open");
if (fstat(fd, &st)) die("stat");
char *map = mmap(0, st.st_size, PROT_READ, MAP_PRIVATE, fd, 0);
if (map == (void*)-1) die("mmap");
 
int i = 0;
glClearColor(EMPTY, 1.0);
/* pass 1, get various sizes */
glClear(GL_COLOR_BUFFER_BIT);
for (ofs = 0; ofs < st.st_size; ofs++) {
switch(map[ofs]) {
case '\n': break;
case 't':
case 'H':
case '.': n_cond++;
case ' ': i++;
case '\r': continue;
default: printf("Bad char %c at row %d, col %d\n",
map[ofs], rows, i);
goto bail;
}
 
if (i > cols) cols = i;
for(j=0; j < height; j++) {
rows ++;
for(i=0; i < width; i++) {
i = 0;
switch( *(field[fc%2] + j*width + i) ) {
}
case cTAIL:
 
glColor3d(E_TAIL);
heads = malloc(sizeof(pos_t) * n_cond);
break;
bakup = malloc(sizeof(pos_t) * n_cond);
case cHEAD:
 
glColor3d(E_HEAD);
cells = malloc(sizeof(cell_t*) * rows);
break;
cells[0] = calloc(cols * rows, sizeof(cell_t));
case cCONDUCTOR:
 
glColor3d(CONDUCTOR);
for (i = 1; i < breakrows; i++)
cells[i] = cells[i - 1] + cols;
default:
 
glColor3d(EMPTY);
while (tex_w < cols) tex_w <<= 1;
break;
while (tex_w < rows) tex_w <<= 1;
}
 
glRectd(GLCOORD_X(i*PPP), GLCOORD_Y(j*PPP),
tex = malloc(sizeof(rgb_t *) * tex_w);
GLCOORD_X((i+1)*PPP-1), GLCOORD_Y((j+1)*PPP-1));
tex[0] = calloc(tex_w * tex_w, sizeof(rgb_t));
}
 
}
for (i = 1; i < tex_w; i++)
glFlush();
tex[i] = tex[i - 1] + tex_w;
 
/* pass 2, convert char file to data */
int j = i = n_heads = 0;
for (ofs = 0; ofs < st.st_size; ofs++) {
switch(map[ofs]) {
default: continue;
case '\n': i++; j = 0; continue;
case '.': cells[i][j++].t = s_condu; continue;
case ' ': cells[i][j++].t = s_blank; continue;
case 't': cells[i][j].t = s_etail; goto add_cell;
case 'H': cells[i][j].t = s_ehead; goto add_cell;
}
add_cell: heads[n_heads].x = j++;
heads[n_heads].y = i;
n_heads++;
}
bail: munmap(map, st.st_size);
close(fd);
 
return 1;
}
 
void * updater(void * _)
{
while (1) {
if (evolve_delay)
usleep(evolve_delay * 1000);
if (paused) continue;
 
pthread_mutex_lock(&lock);
#define THIS(I,J) *(field[fc%2] + (I) + (J)*width)
evolve();
#define NEXT(I,J) *(field[(fc+1)%2] + (I) + (J)*width)
pthread_mutex_unlock(&lock);
}
 
return 0;
int count_heads(int i, int j)
}
 
void render()
{
double x, y;
int e = 0;
 
int ax, ay;
glClear(GL_COLOR_BUFFER_BIT);
for(ax=-1; ax <= 1; ax++) {
glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);
for(ay=-1; ay <= 1; ay++) {
 
if ( ((i+ax) < 0) || ( (j+ay) < 0) || ( (i+ax) >= width ) ||
pthread_mutex_lock(&lock);
( (j+ay) >= height ) ) continue;
 
if ( (ax==0) && (ay==0) ) continue;
glBindTexture(GL_TEXTURE_2D, texture);
if ( THIS(i+ax, j+ay) == cHEAD ) e++;
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, min_y, tex_w, max_y - min_y + 1,
}
TEX_MODE, GL_UNSIGNED_BYTE, tex[min_y]);
}
max_y = 0; min_y = rows;
return e;
 
glBegin(GL_QUADS);
 
x = (double)cols / tex_w;
y = (double)rows / tex_w;
glTexCoord2f(0, 0); glVertex2i( 0, 0);
glTexCoord2f(x, 0); glVertex2i(cols, 0);
glTexCoord2f(x, y); glVertex2i(cols, rows);
glTexCoord2f(0, y); glVertex2i( 0, rows);
 
glEnd();
 
glFlush();
glFinish();
pthread_mutex_unlock(&lock);
 
x = (evolve_delay < show_delay) ? evolve_delay : show_delay;
if (x) usleep(x * 1000);
}
 
void evolve_wireworldset_delay(voidint dec)
{
if (dec) evolve_delay >>= 1;
int j, i;
else{
int ehn;
evolve_delay <<= 1;
if (!evolve_delay)
for(i=0; i < width; i++) {
evolve_delay = 1;
for(j=0; j < height; j++) {
}
switch( THIS(i, j) )
 
{
if (evolve_delay > 1024) {
case cHEAD:
evolve_delay = 1024;
NEXT(i, j) = cTAIL;
paused = 1;
break;
} else
case cTAIL:
paused = 0;
NEXT(i, j) = cCONDUCTOR;
printf("Delay: %d ms\n", evolve_delay);
break;
if (paused) printf("Paused\n");
case cCONDUCTOR:
ehn = count_heads(i, j);
if ( (ehn == 1) || (ehn == 2) )
NEXT(i, j) = cHEAD;
else
NEXT(i, j) = cCONDUCTOR;
break;
default:
NEXT(i, j) = THIS(i, j);
}
}
}
fc++;
}
 
void show_help()
{
printf("Keys:\n\t'<': slow down\n\t'>': speed up\n\t'h': this message\n\t"
"'s': single step\n\t<space>: pause\n\t'q', Esc: quit\n");
}
 
void keypress(unsigned char key, int x, int y)
// key hit
void key_hit(unsigned char k, int x, int y)
{
switch(key) {
if ( k == 'q' )
case ' ': if ((paused = !paused)) printf("Paused\n");
{
return;
glFinish();
case 'S':
glutDestroyWindow(win);
case 's': printf((single = !single) ? "Single step\n" : "Continuous\n");
exit(EXIT_SUCCESS);
return;
} else {
case 'q':
evolve_wireworld();
case 27: glFinish();
wireworld_show();
glutDestroyWindow(gwin);
}
return;
case ',':
case '<': set_delay(0); return;
case '.':
case '>': set_delay(1); return;
case 'h':
case 'H': show_help(); return;
}
}
 
void resize(int w, int h)
{
int dx = 0, dy = 0;
double scale;
 
w -= 10; h -= 10;
glViewport(5, 5, w, h);
if (w * rows > h * cols) {
scale = (double)h / rows;
dx = (w / scale - cols) / 2;
} else {
scale = (double)w / cols;
dy = (h / scale - rows) / 2;
}
 
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
glOrtho(-dx, cols + dx, rows + dy, -dy, -1, 1);
glMatrixMode(GL_MODELVIEW);
}
 
void set_texture()
int main(int argc, char *argv[])
{
int i, j;
FILE *p;
for (i int= 0; i, j< rows; i++)
for (j = 0; j < cols; j++)
tex[i][j] = colors[cells[i][j].t];
 
glEnable(GL_TEXTURE_2D);
 
glGenTextures(1, &texture);
if ( argc < 2 ) {
glBindTexture(GL_TEXTURE_2D, texture);
fprintf(stderr, "specify a wireworld initial state\n");
exit(EXIT_FAILURE);
}
 
glTexImage2D(GL_TEXTURE_2D, 0, TEX_COMP, tex_w, tex_w,
0, TEX_MODE, GL_UNSIGNED_BYTE, tex[0]);
 
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
if ( (p = fopen(argv[1], "r")) != NULL ) {
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
min_y = rows;
max_y = 0;
}
 
void init_gfx(char *prog)
field[0] = malloc(MAX_SIZE*MAX_SIZE*sizeof(char)); assert(field[0] != NULL); //lazy a-check
{
field[1] = malloc(MAX_SIZE*MAX_SIZE*sizeof(char)); assert(field[1] != NULL);
int one = 1;
atexit(freemem);
glutInit(&one, &prog);
fc = 0;
glutInitDisplayMode(GLUT_RGB);
glutInitWindowSize(320, 240);
glutDisplayFunc(render);
glutIdleFunc(render);
glutDisplayFunc(render);
 
gwin = glutCreateWindow("Wireworld");
char buf[MAX_SIZE];
j = 0;
while( !feof(p) && fgets(buf, MAX_SIZE, p) && ( j < MAX_SIZE )) {
for(i=0; (buf[i] != 0) && (buf[i] != 10) && (i < MAX_SIZE); i++ ) {
*(field[fc%2] + j*width + i) = buf[i];
}
for( ; i < MAX_SIZE; i++) *(field[fc%2] + j*width + i) = 0;
j++;
}
fclose(p);
 
glutKeyboardFunc(keypress);
// no more error check :)
glutReshapeFunc(resize);
glutInit(&argc, argv);
 
set_texture();
}
 
int main(int c, char **v)
{
pthread_t th;
 
if (c < 2) {
win = glutCreateWindow("Wireworld");
printf("Usage: %s input_file\n", v[0]);
glutInitWindowPosition(0,0);
return 0;
glutInitWindowSize(width*PPP, height*PPP);
}
 
if (!read_file(v[1])) return 0;
glutDisplayFunc(wireworld_show);
glutKeyboardFunc(key_hit);
 
show_help();
// (-1,-1) from lower leftmost to upper leftmost corner
init_gfx(v[0]);
float matrix[16] = {
1, 0, 0, 0,
0, -1, 0, 0,
0, 0, 1, 0,
0, 0, 0, 1
};
glLoadMatrixf(matrix);
 
pthread_create(&th, 0, updater, 0);
glutMainLoop();
pthread_detach(th);
} else {
fprintf(stderr, "cannot open %s\n", argv[1]);
exit(EXIT_FAILURE);
}
 
glutMainLoop();
exit(EXIT_SUCCESS);
return 0;
}</lang>
Anonymous user