123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445 |
- /*
- * This file is part of the UCB release of Plan 9. It is subject to the license
- * terms in the LICENSE file found in the top-level directory of this
- * distribution and at http://akaros.cs.berkeley.edu/files/Plan9License. No
- * part of the UCB release of Plan 9, including this file, may be copied,
- * modified, propagated, or distributed except according to the terms contained
- * in the LICENSE file.
- */
- /*
- * life - john conways's game of life (and variations),
- * sci. am. 223, october 1970, pp. 120—123, or
- * http://en.wikipedia.org/wiki/Conway's_Game_of_Life
- */
- #include <u.h>
- #include <libc.h>
- #include <bio.h>
- #include <draw.h>
- #include <event.h>
- enum {
- NLIFE = 256, /* life array size */
- PX = 4, /* cell spacing */
- BX = PX - 1, /* box size */
- NADJUST = NLIFE * NLIFE,
- };
- /*
- * life[i][j] stores L+2*N, where L is 0 if the cell is dead and 1
- * if alive, and N is the number of the cell's 8-connected neighbours
- * which live.
- * row[i] indicates how many cells are alive in life[i][*].
- * col[j] indicates how many cells are alive in life[*][j].
- * Adjust contains pointers to cells that need to have their neighbour
- * counts adjusted in the second pass of the generation procedure.
- */
- char life[NLIFE][NLIFE];
- int row[NLIFE];
- int col[NLIFE];
- char action[18]; /* index by cell contents to find action */
- char *adjust[NADJUST];
- int delay;
- Point cen;
- Image *box;
- int i0, i1, j0, j1;
- int needresize;
- void birth(int, int);
- void centerlife(void);
- void death(int, int);
- int generate(void);
- int interest(int [NLIFE], int);
- void main(int, char *[]);
- int min(int, int);
- void readlife(char *);
- void redraw(void);
- void setrules(char *);
- void window(void);
- static void reshape(void);
- static void
- setbox(int i, int j)
- {
- Point loc;
- loc = Pt(cen.x + j*PX, cen.y + i*PX);
- draw(screen, Rpt(loc, addpt(loc, Pt(BX, BX))), box, nil, ZP);
- }
- static void
- clrbox(int i, int j)
- {
- Point loc;
- loc = Pt(cen.x + j*PX, cen.y + i*PX);
- draw(screen, Rpt(loc, addpt(loc, Pt(BX, BX))), display->white, nil, ZP);
- }
- void
- setrules(char *r)
- {
- char *a;
- for (a = action; a != &action[nelem(action)]; *a++ = *r++)
- ;
- }
- static void
- g9err(Display *d, char *err)
- {
- fprint(2, "%s: %s (%r)\n", argv0, err);
- exits(err);
- }
- void
- usage(void)
- {
- fprint(2, "Usage: %s [-3o] [-r rules] file\n", argv0);
- exits("usage");
- }
- void
- idle(void)
- {
- int c;
- while (ecanmouse())
- emouse(); /* throw away mouse events */
- while (ecankbd()){
- c = ekbd();
- if (c == 'q' || c == 0177) /* watch keyboard ones */
- exits(nil);
- if (c >= '1' && c <= '9')
- delay = (c - '0') * 100;
- else if (c == '0')
- delay = 1000;
- }
- if (needresize)
- reshape();
- }
- void
- main(int argc, char *argv[])
- {
- delay = 1000;
- setrules(".d.d..b..d.d.d.d.d"); /* regular rules */
- ARGBEGIN {
- case '3':
- setrules(".d.d.db.b..d.d.d.d");
- break; /* 34-life */
- case 'o':
- setrules(".d.d.db.b.b..d.d.d");
- break; /* lineosc? */
- case 'r': /* rules from cmdline */
- setrules(EARGF(usage()));
- break;
- default:
- usage();
- } ARGEND
- if (argc != 1)
- usage();
- initdraw(g9err, 0, argv0);
- einit(Emouse|Ekeyboard); /* implies rawon() */
- cen = divpt(subpt(addpt(screen->r.min, screen->r.max),
- Pt(NLIFE * PX, NLIFE * PX)), 2);
- box = allocimage(display, Rect(0, 0, BX, BX), RGB24, 1, DBlack);
- assert(box != nil);
- redraw();
- readlife(argv[0]);
- do {
- flushimage(display, 1);
- idle();
- sleep(delay);
- idle();
- } while (generate());
- exits(nil);
- }
- /*
- * We can only have interest in a given row (or column) if there
- * is something alive in it or in the neighbouring rows (or columns.)
- */
- int
- interest(int rc[NLIFE], int i)
- {
- return(rc[i-1] != 0 || rc[i] != 0 || rc[i+1] != 0);
- }
- /*
- * A life generation proceeds in two passes. The first pass identifies
- * cells that have births or deaths. The `alive bit' is updated, as are
- * the screen and the row/col count deltas. Also, a record is made
- * of the cell's address. In the second pass, the neighbours of all changed
- * cells get their neighbour counts updated, and the row/col deltas get
- * merged into the row/col count arrays.
- *
- * The border cells (i==0 || i==NLIFE-1 || j==0 || j==NLIFE-1) are not
- * processed, purely for speed reasons. With a little effort, a wrap-around
- * universe could be implemented.
- *
- * Generate returns 0 if there was no change from the last generation,
- * and 1 if there were changes.
- */
- #define neighbour(di, dj, op) lp[(di)*NLIFE+(dj)] op 2
- #define neighbours(op)\
- neighbour(-1, -1, op);\
- neighbour(-1, 0, op);\
- neighbour(-1, 1, op);\
- neighbour( 0, -1, op);\
- neighbour( 0, 1, op);\
- neighbour( 1, -1, op);\
- neighbour( 1, 0, op);\
- neighbour( 1, 1, op)
- int
- generate(void)
- {
- char *lp;
- char **p, **addp, **delp;
- int i, j, j0 = NLIFE, j1 = -1;
- int drow[NLIFE], dcol[NLIFE];
- for (j = 1; j != NLIFE - 1; j++) {
- drow[j] = dcol[j] = 0;
- if (interest(col, j)) {
- if (j < j0)
- j0 = j;
- if (j1 < j)
- j1 = j;
- }
- }
- addp = adjust;
- delp = &adjust[NADJUST];
- for (i = 1; i != NLIFE - 1; i++)
- if (interest(row, i)) {
- for (j = j0, lp = &life[i][j0]; j <= j1; j++, lp++)
- switch (action[(int)*lp]) {
- case 'b':
- ++*lp;
- ++drow[i];
- ++dcol[j];
- setbox(i, j);
- *addp++ = lp;
- break;
- case 'd':
- --*lp;
- --drow[i];
- --dcol[j];
- clrbox(i, j);
- *--delp = lp;
- break;
- }
- }
- if (addp == adjust && delp == &adjust[NADJUST])
- return 0;
- if (delp < addp)
- sysfatal("Out of space (delp < addp)");
- p = adjust;
- while (p != addp) {
- lp = *p++;
- neighbours(+=);
- }
- p = delp;
- while (p != &adjust[NADJUST]) {
- lp = *p++;
- neighbours(-=);
- }
- for (i = 1; i != NLIFE - 1; i++) {
- row[i] += drow[i];
- col[i] += dcol[i];
- }
- if (row[1] || row[NLIFE-2] || col[1] || col[NLIFE-2])
- centerlife();
- return 1;
- }
- /*
- * Record a birth at (i,j).
- */
- void
- birth(int i, int j)
- {
- char *lp;
- if (i < 1 || NLIFE - 1 <= i || j < 1 || NLIFE - 1 <= j ||
- life[i][j] & 1)
- return;
- lp = &life[i][j];
- ++*lp;
- ++row[i];
- ++col[j];
- neighbours(+=);
- setbox(i, j);
- }
- /*
- * Record a death at (i,j)
- */
- void
- death(int i, int j)
- {
- char *lp;
- if (i < 1 || NLIFE - 1 <= i || j < 1 || NLIFE - 1 <= j ||
- !(life[i][j] & 1))
- return;
- lp = &life[i][j];
- --*lp;
- --row[i];
- --col[j];
- neighbours(-=);
- clrbox(i, j);
- }
- void
- readlife(char *filename)
- {
- int c, i, j;
- char name[256];
- Biobuf *bp;
- if ((bp = Bopen(filename, OREAD)) == nil) {
- snprint(name, sizeof name, "/sys/games/lib/life/%s", filename);
- if ((bp = Bopen(name, OREAD)) == nil)
- sysfatal("can't read %s: %r", name);
- }
- draw(screen, screen->r, display->white, nil, ZP);
- for (i = 0; i != NLIFE; i++) {
- row[i] = col[i] = 0;
- for (j = 0; j != NLIFE; j++)
- life[i][j] = 0;
- }
- c = 0;
- for (i = 1; i != NLIFE - 1 && c >= 0; i++) {
- j = 1;
- while ((c = Bgetc(bp)) >= 0 && c != '\n')
- if (j != NLIFE - 1)
- switch (c) {
- case '.':
- j++;
- break;
- case 'x':
- birth(i, j);
- j++;
- break;
- }
- }
- Bterm(bp);
- centerlife();
- }
- int
- min(int a, int b)
- {
- return(a < b ? a : b);
- }
- void
- centerlife(void)
- {
- int i, j, di, dj, iinc, jinc, t;
- window();
- di = NLIFE / 2 - (i0 + i1) / 2;
- if (i0 + di < 1)
- di = 1 - i0;
- else if (i1 + di >= NLIFE - 1)
- di = NLIFE - 2 - i1;
- dj = NLIFE / 2 - (j0 + j1) / 2;
- if (j0 + dj < 1)
- dj = 1 - j0;
- else if (j1 + dj >= NLIFE - 1)
- dj = NLIFE - 2 - j1;
- if (di != 0 || dj != 0) {
- if (di > 0) {
- iinc = -1;
- t = i0;
- i0 = i1;
- i1 = t;
- } else
- iinc = 1;
- if (dj > 0) {
- jinc = -1;
- t = j0;
- j0 = j1;
- j1 = t;
- } else
- jinc = 1;
- for (i = i0; i * iinc <= i1 * iinc; i += iinc)
- for (j = j0; j * jinc <= j1 * jinc; j += jinc)
- if (life[i][j] & 1) {
- birth(i + di, j + dj);
- death(i, j);
- }
- }
- }
- void
- redraw(void)
- {
- int i, j;
- window();
- draw(screen, screen->r, display->white, nil, ZP);
- for (i = i0; i <= i1; i++)
- for (j = j0; j <= j1; j++)
- if (life[i][j] & 1)
- setbox(i, j);
- }
- void
- window(void)
- {
- for (i0 = 1; i0 != NLIFE - 2 && row[i0] == 0; i0++)
- ;
- for (i1 = NLIFE - 2; i1 != i0 && row[i1] == 0; --i1)
- ;
- for (j0 = 1; j0 != NLIFE - 2 && col[j0] == 0; j0++)
- ;
- for (j1 = NLIFE - 2; j1 != j0 && col[j1] == 0; --j1)
- ;
- }
- static void
- reshape(void)
- {
- // int dy12;
- // if (needresize) {
- // sqwid = Dx(screen->r) / (1 + bdp->cols + 1);
- // dy12 = Dy(screen->r) / (1 + bdp->rows + 1 + 2);
- // if (sqwid > dy12)
- // sqwid = dy12;
- // recompute(bdp, sqwid);
- // }
- sleep(1000);
- needresize = 0;
- cen = divpt(subpt(addpt(screen->r.min, screen->r.max),
- Pt(NLIFE * PX, NLIFE * PX)), 2);
- redraw();
- flushimage(display, 1);
- }
- /* called from graphics library */
- void
- eresized(int callgetwin)
- {
- needresize = callgetwin + 1;
- /* new window? */
- /* was using Refmesg */
- if (needresize > 1 && getwindow(display, Refnone) < 0)
- sysfatal("can't reattach to window: %r");
- /* destroyed window? */
- if (Dx(screen->r) == 0 || Dy(screen->r) == 0)
- exits("window gone");
- reshape();
- }
|