DataMuseum.dk

Presents historical artifacts from the history of:

DKUUG/EUUG Conference tapes

This is an automatic "excavation" of a thematic subset of
artifacts from Datamuseum.dk's BitArchive.

See our Wiki for more about DKUUG/EUUG Conference tapes

Excavated with: AutoArchaeologist - Free & Open Source Software.


top - metrics - download
Index: T x

⟦c4eb47e77⟧ TextFile

    Length: 20707 (0x50e3)
    Types: TextFile
    Names: »xlife.c«

Derivation

└─⟦b20c6495f⟧ Bits:30007238 EUUGD18: Wien-båndet, efterår 1987
    └─⟦this⟧ »EUUGD18/X/Xlife/xlife.c« 

TextFile

/*
 * John Conway's Game of Life. With some simple interaction that makes it a
 * neat game - Mark Moraes.
 */
/*
 *  Possible enhancements: Load, Save, Go through patterns, etc Do it
 *  the right way with Translation management
 */
#include <stdio.h>
#include <X11/Xlib.h>
#include <X11/Intrinsic.h>
#include <X11/StringDefs.h>
#include <X11/Command.h>
#include <X11/cursorfont.h>
#include <X11/Xutil.h>
#include <fcntl.h>
#include <signal.h>
#include "random.h"
#include "object.h"

#ifndef PATFILE
#define PATFILE "xlife.pat"
#endif PATFILE

#define RightButton		Button3
#define MiddleButton		Button2
#define LeftButton		Button1
#define RightButtonMask		Button3Mask
#define MiddleButtonMask	Button2Mask
#define LeftButtonMask		Button1Mask

#define CELLWIDTH 4
#define CELLHEIGHT 4

#define XCELLS	150
#define YCELLS	150

#define xcell(i)		((i)*CELLWIDTH)
#define ycell(i)		((i)*CELLHEIGHT)

/* Storage for the old board and new board */
static int **boardrows1, **boardrows2;
/*
 *  Pointers to the curent board for the iteration, and the new board
 *  for the next iteration - these pointers point to boardrows[12]
 *  alternately.
 */
static int **nboard, **oboard;
/* Number of columns and rows of cells */
static int nxcells, nycells;
static int nrandom;

/* Boolean variables */
static int running = 0;	/* Are we running the simulation or not ? */
static int notover = 1;	/* zero if user wants to quit */

/* 
 *  These structures comprise the display list - instead of drawing as
 *  we change the cell, we store it in the display list, and update in
 *  batches - see the comments on the interrupt strategy at
 *  xw_setup_intr(). 
 */


typedef struct {
	int set;
	int x, y;
} RectList;

#define RECT_LIST_CHUNK 1024
RectList *rect_list = NULL;
int rect_list_size = 0;
int rect_list_count = 0;;

static void do_life();
static void makeboard();
static void fill_rectangle();
static void setcell();
static void xw_setup_intr();
static int xw_handle_intr();
static int enable_sigio();
static int disable_sigio();
static int set_sigio_state();
static void reentrant_xflush();
static void redraw();
static void xw_update();
static void readpatterns();
static void randomize();
static void clear();


/*
 *  This is my favourite way of handling X Toolkit arguments -
 *  startargs() and then, setarg(name, value) for all the args I want to
 *  set
 */
#define MAXARGS 32
static int nargs;
static Arg wargs[MAXARGS];
#define startargs()		nargs = 0
#define setarg(name, value)	XtSetArg(wargs[nargs], name, value), nargs++

/* X Windows related variables */
static Cursor WorkingCursor;
static Display *dpy;
static Window win;
static GC gc;
static GC cleargc;

/* X Defaults */
static int defaultWidth = CELLWIDTH * XCELLS;
static int defaultHeight = CELLWIDTH * YCELLS;

static int zero = 0;
static int Width, Height;
static Pixel fg, bg;
static char *progname;
static char *patfile;

/* Application Resources - no particular widget */
static XtResource application_resources[] = {
	{"name", "Name", XtRString, sizeof(char *),
		(Cardinal)&progname, XtRString, "xlife"},
	{"width", "Width", XtRInt, sizeof(int),
		(Cardinal)&Width, XtRInt, (caddr_t) &defaultWidth},
	{"height", "Height", XtRInt, sizeof(int),
		(Cardinal)&Height, XtRInt, (caddr_t) &defaultHeight},
	{"random", "Random", XtRInt, sizeof(int),
		(Cardinal)&nrandom, XtRInt, (caddr_t) &zero},
	{"foreground", "Foreground", XtRPixel, sizeof(Pixel),
		(Cardinal)&fg, XtRString, (caddr_t) "Black"},
	{"background", "Background", XtRPixel, sizeof(Pixel),
		(Cardinal)&bg, XtRString, (caddr_t) "White"},
	{"patternfile", "PatternFile", XtRString, sizeof(char *),
		(Cardinal)&patfile, XtRString, PATFILE},
};

/*
 *  Command line options table. The command line is parsed for these,
 *  and it sets/overrides the appropriate values in the resource
 *  database
 */
static XrmOptionDescRec optionDescList[] = {
{"-width",	"*width",	XrmoptionSepArg, 	(caddr_t) NULL},
{"-height",	"*height",	XrmoptionSepArg,	(caddr_t) NULL},
{"-fg",		"*foreground",	XrmoptionSepArg,	(caddr_t) NULL},
{"-bg",		"*background",	XrmoptionSepArg,	(caddr_t) NULL},
{"-random",	"*random",	XrmoptionSepArg,	(caddr_t) NULL},
{"-patfile",	"*patternfile",	XrmoptionSepArg,	(caddr_t) NULL},
};


main(argc, argv)
int argc;
char **argv;
{
	static void RepaintCanvas();
	static void RecordMapStatus();
	static void MouseInput();
	Widget toplevel;
	Widget w;
	XGCValues gcv;

	/*
 	 * Create the top level Widget that represents encloses the
	 * application.
	 */
	toplevel = XtInitialize(argv[0], "XLife",
		optionDescList, XtNumber(optionDescList), &argc, argv);

	XtGetApplicationResources(toplevel, 0, application_resources,
		XtNumber(application_resources), NULL, 0 );


	if (argc != 1) {
		(void) fprintf(stderr, "Usage: %s [Xt options]\n", argv[0]);
		exit(-1);
	}

	makeboard(Width/CELLWIDTH, Height/CELLHEIGHT);
	readpatterns(patfile);
	
	/*
	 * Create a simple Core class widget which we'll use for the actual
	 * game.  A Core class widget is basically just a window, with a
	 * simple Xt "wrapper" around it.
	 */
	startargs();
	setarg(XtNwidth, xcell(nxcells));
	setarg(XtNheight, xcell(nycells));
	w = XtCreateManagedWidget(argv[0], widgetClass, toplevel, 
		wargs, nargs);

	/*
	 * Set the procedures for various X Windows actions - exposure events
	 * which arrive when a window needs to be redrawn. The map event lets
	 * you know that the window is now on the screen so you can actually
	 * do stuff. The ButtonPress event lets you know that a mouse button
	 * was pressed.
	 */
	XtAddEventHandler(w, (Cardinal) ExposureMask, NULL, 
	 RepaintCanvas, "redraw_data");
	XtAddEventHandler(w, (Cardinal) StructureNotifyMask, NULL, 
	 RecordMapStatus, "map_data");
	/* One day, we'll use the translation manager here */
	XtAddEventHandler(w, 
	 (Cardinal) ButtonPressMask | ButtonMotionMask | KeyPressMask, NULL,
	 MouseInput, "input_data"); 

	/*
	 * Create the windows, and set their attributes according to the Widget
	 * data.
	 */
	XtRealizeWidget(toplevel);
	
	/* We need these for the raw Xlib calls */
	win = XtWindow(w);
	dpy = XtDisplay(w);

	WorkingCursor = XCreateFontCursor(dpy, XC_top_left_arrow);
	XDefineCursor(dpy, win, WorkingCursor);

	/* 
	 *  make the X Graphic Contexts here - one for copy (setting the
	 *  square to foreground colour), one for erase (setting the
	 *  square to background colour).
	 */
	gcv.foreground = fg;
	gcv.background = bg;
	gcv.function = GXcopy;
	gc = XCreateGC(dpy, win, GCForeground | GCBackground 
	 | GCFunction, &gcv);
	gcv.foreground = bg;
	cleargc = XCreateGC(dpy, win, GCForeground | GCBackground 
	 | GCFunction, &gcv);

	
	(void) seedrnd(getpid());
	randomize(nrandom);
	XFlush(dpy);

	/* 
	 *  Now process the events. Our event processing is
	 *  asynchronous, like GNUPLOT, from which the event code is
	 *  adapted. This is unlike most X applications which are
	 *  interactive, and therefore go into a XtMainLoop here. We go
	 *  into a "display loop", where we start doing output, and get
	 *  interrupted when there's an event to be processed.  as we
	 *  can.
	 */
	
	xw_setup_intr();
	while(notover) {
		if (running)
			do_life();
		else
			pause();
	}
}

/* Actual game of life iteration */
static void
do_life()
{
	int **tmp;
	register int *nrow, *orow, *orowprev, *orownext;
	register int i, j;
	register int neighbours;
#ifdef DEBUG
	int done = 0;
	int filled = 0;
#endif

	rect_list_count = 0;
	orow = oboard[0];
	orownext = oboard[1];
	for(i = 1; i <= nycells; i++) {
		orowprev = orow;
		orow = orownext;	/* orow = oboard[i] */
		orownext = oboard[i+1];
		nrow = nboard[i];
		for(j = 1; j <= nxcells; j++) {
			/* calculate cell from oboard to new board */
			neighbours =
			 orowprev[j-1] + orowprev[j] + orowprev[j+1] +
			 orow[j-1] + orow[j+1] +
			 orownext[j-1] + orownext[j] + orownext[j+1];
			if (neighbours == 3 && orow[j] == 0) {
				nrow[j] = 1;
				fill_rectangle(j, i, 1);
#ifdef DEBUG
				done++;
#endif
			} else if (neighbours != 2 && neighbours != 3 &&
			 orow[j] != 0) {
			 	nrow[j] = 0;
				fill_rectangle(j, i, 0);
#ifdef DEBUG
				done++;
#endif
			} else {
				nrow[j] = orow[j];
				/* Since it hasn't changed, we don't update */
			}
#ifdef DEBUG
			if (nrow[j])
				filled++;
#endif
		}
	}
	/* Stop running if we haven't had a change in the cell */
	if (rect_list_count == 0)
		running = 0;
	xw_update();
	tmp = nboard;
	nboard = oboard;
	oboard = tmp;
/*	reentrant_redraw();*/
#ifdef DEBUG
	printf("Done an iteration - changed = %d, filled = %d, running = %d\n",
	 done, filled, running);
#endif
}


/*
 *  Add a rectangle to the display_list. If the last rectangle was the
 *  same colour as the one now (determined by 'set'), then add it to the
 *  current rect_list element by incrementing the count, and putting it
 *  in rect_buf. If it is different, then start a new rect_list
 *  element.
 */
static void
fill_rectangle(x, y, set)
{
	register RectList *cur_rect;

	test_and_grow_object(rect_list, rect_list_count,
	 rect_list_size, RECT_LIST_CHUNK, RectList);
	
	cur_rect = &rect_list[rect_list_count];
	cur_rect->x = xcell(x-1);
	cur_rect->y = ycell(y-1);
	cur_rect->set = set;
	rect_list_count++;
}

static int isMapped = 0;

/*ARGSUSED*/
static void 
RepaintCanvas(w, data, ev)
Widget w;
caddr_t data;
XEvent *ev;
{
	if (!isMapped)
		return;
	/*
	 * Redraw the array
	 */
	if (ev && ev->xexpose.count == 0) {
		XEvent event;
		/* Skip all excess redraws */
		while (XCheckTypedEvent(dpy, Expose, &event))
			;
		redraw();
	}

		
#ifdef WINDOWDEBUG
	printf("repaint\n");
#endif
	XFlush(dpy);
}

/*ARGSUSED*/
static void 
RecordMapStatus(w, data, ev)
Widget w;
caddr_t data;
XEvent *ev;
{
	if (ev->type == MapNotify) {
#ifdef WINDOWDEBUG
		printf("window mapped\n");
#endif
		isMapped = TRUE;
	} else if (ev->type = ConfigureNotify) {
#ifdef WINDOWDEBUG
		printf("window resized\n");
#endif
	}
}


/*ARGSUSED*/
static void 
MouseInput(w, data, ev)
Widget w;
caddr_t data;
XEvent *ev;
{
	char ch;
	
#ifdef WINDOWDEBUG
	printf("Input to canvas - %d (0x%x)\n", ev->xany.type, ev->xany.type);
#endif
	switch (ev->xany.type) {
	case ButtonPress:
		if (ev->xbutton.button == LeftButton) {
			/* Turn on cell */
			setcell((int) (ev->xbutton.x / CELLWIDTH),
				(int) (ev->xbutton.y / CELLHEIGHT), 1);
		} else if (ev->xbutton.button == MiddleButton) {
			/* Toggle simulation on/off */
			running = !running;
		} else if (ev->xbutton.button == RightButton && !running) {
			/* Turn off cell */
			setcell((int) (ev->xbutton.x / CELLWIDTH),
				(int) (ev->xbutton.y / CELLHEIGHT), 0);
		}
		break;
	case MotionNotify:
		if (ev->xmotion.state & LeftButtonMask) 
			/* Turn on cell */
			setcell((int) (ev->xmotion.x / CELLWIDTH),
				(int) (ev->xmotion.y / CELLHEIGHT), 1);
		else if ((ev->xmotion.state & RightButtonMask) && !running)
			/* Turn off cell */
			setcell((int) (ev->xmotion.x / CELLWIDTH),
				(int) (ev->xmotion.y / CELLHEIGHT), 0);
		break;
	case KeyPress:
		XLookupString(ev, &ch, 1, NULL, NULL);
#ifdef WINDOWDEBUG
		printf("Keypress - %c (0x%x)\n", ch, ch);
#endif
		switch(ch) {
		case '0': case '1': case '2': case '3': case '4': 
		case '5': case '6': case '7': case '8': case '9':
			break;
		case 'c':
			clear();
			break;
		case 'r':
			redraw();
			break;
		case 'q':
			exit(0);
			/*NOTREACHED*/
		default:
			break;
		}
		break;
	default:
		(void) fprintf(stderr, "Got an event of type %d (%x).\n",
		       ev->xany.type, ev->xany.type);
		break;
	}
}

static void
setcell(x, y, set)
int x, y, set;
{
	int xreal = x * CELLWIDTH;
	int yreal = y * CELLHEIGHT;
	register int i = y + 1;
	register int j = x + 1;

	if (x < 0 || x >= nxcells || y < 0 || y >= nycells)
		return;
	if (set)
		oboard[i][j] = nboard[i][j] = 1;
	else
		oboard[i][j] = nboard[i][j] = 0;

	XFillRectangle(dpy, win, set ? gc : cleargc, xreal, yreal,
	 CELLWIDTH, CELLHEIGHT);
}

static void
makeboard(width, height)
int width, height;
{
	register int i;
	
	nxcells = width;
	nycells = height;
	boardrows1 = (int **) XtCalloc(height+2, sizeof(int *));
	boardrows2 = (int **) XtCalloc(height+2, sizeof(int *));
	for(i = 0; i < height+2; i++) {
		boardrows1[i] = (int *) XtCalloc(width+2, sizeof(int));
		boardrows2[i] = (int *) XtCalloc(width+2, sizeof(int));
	}
	oboard = boardrows1;
	nboard = boardrows2;
}


static int display_socket_flags = 0;	/*
					 *  nonzero=> server input will
					 *  cause a SIGIO interrupt
					 */

/*
 *  The following code is adapted from GNUPLOT, and uses a pretty neat
 *  scheme for using X events properly in applications that aren't event
 *  driven, but do continuous graphic output, responding asynchronously
 *  to keyboard input. The X and Xt library calls are not known to be
 *  reentrant.  Therefore, because expose events must cause a SIGIO
 *  interrupt handler to redraw the screen and these interrupts can
 *  arrive in the middle of X/Xt library calls, the interrupts must be
 *  disabled while the calls are taking place.  In order to not take a
 *  bad performance hit from calling sigmask on every library request,
 *  the requests are batched.  xw_update then flushes the batched
 *  requests.  It passes the requests off to xw_display, which disables
 *  SIGIO and then processes each item in a specified part of the
 *  display list.  xw_display is also called by the interrupt handler to
 *  redraw the entire window.  No attempt is made to process any
 *  exposure events other than EXPOSE.
 */
/*
 * The next routine sets up the interrupt handler for asynchronous
 *  refresh on expose events.  Method:
 *	1. Remember previous status flags for socket, as these flags
 *	   will be diddled to request and unrequest SIGIO interrupts later.
 *	2. Use signal() to catch SIGIO interrupts with the interrupt
 *		 handler.  We must do this before calling enable_sigio,
 *	   otherwise some signals could happen with nobody to catch
 *		 them, causing a core dump.
 *	3. Use fcntl() to request that the current process be the one
 *	   to which SIGIO interrupts are sent when I/O generates them.
 *	   Otherwise, they are sent to process 0 for some reason.
 *	4. Call enable_sigio(), which uses fcntl() again to request a SIGIO
 *	   when data appears on the X server socket.  Supposedly this
 *	   will work in 4.3 and in Ultrix 2.0, even though certain BSD
 *	   doc says that this call only works when the channel passed it
 *	   is a tty channel.  (Other docs say it works on ttys and
 *	   sockets).  So it is possible that this will lose on some old
 *	   4.2bsd systems.  I am not sure what to do then.
 */
static void
xw_setup_intr ()
{
	static int xw_handle_intr ();

	int socket = XConnectionNumber(dpy); /* 
				       *  channel number of the tcp
				       *  socket to the server
				       */
	
	display_socket_flags = fcntl (socket, F_GETFL, 0);

	(void) signal (SIGIO, xw_handle_intr);
	(void) fcntl (socket, F_SETOWN, getpid());
	(void) enable_sigio ();
}

/*
 *  Request a SIGIO interrupt to be sent whenever input is available on
 *  the socket connected to the X server.  Return old state.
 */
static int
enable_sigio ()
{
	return set_sigio_state (display_socket_flags | FASYNC);
}
 
/*
 *  Cancel request for SIGIO interrupt on input from the server.  The
 *  interrupt should be turned off whenever "normal" traffic (i.e.,
 *  plotting and anything else that does not have to asynchronously
 *  notify xlife that something is happening) is taking place.  Return
 *  old state.
 */
static int
disable_sigio ()
{
	return set_sigio_state (display_socket_flags & ~FASYNC);
}

/*
 *  Set state of sigio to VALUE, if it was something different.  Return
 *  old value so it can be restored.
 */
static int
set_sigio_state (value)
int value;
{
	int socket = XConnectionNumber(dpy);/*
					    *  channel number of the tcp
					    *  socket to the server
					    */
	int oldval = display_socket_flags;

	/* Only call fcntl if we're asking for a real state change (old
	 state different from new state). */
	if (display_socket_flags != value) {
		(void) fcntl (socket, F_SETFL, value);
		display_socket_flags = value;
	}
	return oldval;
}

/*
 *  actually handle the interrupt by simply repeating everything on the
 *  display list up to the last xw_flush().
 */
static int
xw_handle_intr()
{
	XEvent event;
	int old_intr;

	/*
	 *  first, we need to get rid of the X events on the queue.
	 *  However, since reading the events in order to throw them
	 *  away involves Xlib calls, we need to disable SIGIO while it
	 *  is happening.
	 */

	old_intr = disable_sigio ();

	/* Check the X queue for actual exposure events.  This is necessary
	 for two reasons:
	 1. SIGIO interrupts seem to get sent here even though the SIGIO
		request is carefully turned off during any activity that can
		cause traffic on the socket (a kernel bug?).
	 2. The Xlib documentation lies when it says XSelectInput
		masks events other than the ones selected.  I.e., sometimes
		events can arrive that were not asked for.  Therefore,
		we must make sure the purported exposure event really is one.
	*/

	while (XtPending() > 0) {
		XtNextEvent (&event);
		XtDispatchEvent(&event);
	}
	(void) set_sigio_state (old_intr);
}

/*
 *  Cause an XFlush() call, with interrupts disabled.
 */
static void
reentrant_xflush ()
{
	int old_sigio = disable_sigio ();
	XFlush (dpy);
	(void) set_sigio_state (old_sigio);
}

/* If signals aren't off when redraw is called, bad things may happen */
static void
redraw()
{
	int i, j;
	for(i = 1; i <= nycells; i++) {
		for(j = 1; j <= nxcells; j++) {
			if(oboard[i][j])
				XFillRectangle(dpy, win, gc,
				 xcell(j-1), ycell(i-1),
				 CELLWIDTH, CELLHEIGHT);
		}
	}
}

static void
reentrant_redraw ()
{
	int old_sigio = disable_sigio ();
	XClearWindow(dpy, win);
	redraw();
	XFlush(dpy);
	(void) set_sigio_state (old_sigio);
}

static void
xw_update()
{
	int old_sigio = disable_sigio ();
	register int i;
	register RectList *cur_rect = rect_list;

#ifdef DEBUG
	printf("-- update: %d rects\n", rect_list_count);
#endif
	for(i = 0; i < rect_list_count; i++, cur_rect++) {
		XFillRectangle(dpy, win, rect_list[i].set ? gc : cleargc,
		 cur_rect->x, cur_rect->y, CELLWIDTH, CELLHEIGHT);
	}
	rect_list_count = 0;
	XFlush(dpy);
	
	(void) set_sigio_state (old_sigio);
}

typedef struct {
	int i, j;
} Coord;

#define PATTERN_BUF_CHUNK 256
static Coord *pattern_buf = NULL;
static int pattern_buf_size = 0;
static int pattern_buf_count = 0;

typedef struct {
	Coord *coord;
	int ncoords;
} Pattern;

#define PATTERN_CHUNK 32
static Pattern *patterns = NULL;
static int patterns_size = 0;
static int patterns_count = 0;

/* Opens file and appends patterns to list of predefined patterns. */
static void
readpatterns(file)
char *file;
{
	FILE *fp;
	char buf[128];
	int i, j;
	int newpattern = 0;

	if ((fp = fopen(file, "r")) == NULL) {
		(void) fprintf(stderr, "%s: Couldn't open file %s\n", 
		 progname, file);
		return;
	}
	test_and_grow_object(patterns, patterns_count,
	 patterns_size, PATTERN_CHUNK, Pattern);
	test_and_grow_object(pattern_buf, pattern_buf_count,
	 pattern_buf_size, PATTERN_BUF_CHUNK, Coord);
	patterns[patterns_count].coord = &pattern_buf[pattern_buf_count];
	patterns[patterns_count].ncoords = 0;
	while(fgets(buf, sizeof(buf), fp) != NULL) {
		if (sscanf(buf, " %d %d", &i, &j) == 2) {
			newpattern++;
			test_and_grow_object(pattern_buf, pattern_buf_count,
			 pattern_buf_size, PATTERN_BUF_CHUNK, Coord);
			pattern_buf[pattern_buf_count].i = i;
			pattern_buf[pattern_buf_count].j = j;
			pattern_buf_count++;
		} else if (newpattern != 0) {
			/* Lousy parsing - we assume end of pattern */
			patterns[patterns_count].ncoords = newpattern;
			patterns_count++;
			test_and_grow_object(patterns, patterns_count,
			 patterns_size, PATTERN_CHUNK, Pattern);
			patterns[patterns_count].coord =
			 &pattern_buf[pattern_buf_count];
			patterns[patterns_count].ncoords = 0;
			newpattern = 0;
		}
	}
}


/* Puts pattern m with origin at col i, row j */
static void
putpattern(m, x, y)
{
	register int k;
	Coord *p;
	register int yoffset = y;
	register int xoffset = x;

	p = patterns[m].coord;
	k = patterns[m].ncoords;
	while(--k >= 0)
		setcell(xoffset + p[k].j, yoffset + p[k].i, 1);
}


static void
randomize(nrandom)
{
	int x, y;
	int i;
	int nx, ny;

	if (patterns_count == 0)
		return;
	nx = nxcells - 2;
	ny = nycells - 2;
	if (nrandom == 0)
		nrandom = (nxcells + nycells) / 5;
	while(nrandom-- > 0) {
		x = rnd(nx) + 1;
		y = rnd(ny) + 1;
		i = rnd(patterns_count);
		putpattern(i, x, y);
	}
}

static void
clear()
{
	register int i, j;
	int old_sigio = disable_sigio ();
	for(i = 0; i < nycells; i++)
		for(j = 0; j < nxcells; j++)
			oboard[i][j] = nboard[i][j] = 0;
	XClearWindow(dpy, win);
	XSync(dpy, True);
	(void) set_sigio_state (old_sigio);
}