home *** CD-ROM | disk | FTP | other *** search
- /*
- * Life search program - user interactions module.
- * Author: David I. Bell.
- */
-
- #include "lifesrc.h"
-
-
- /*
- * Local procedures
- */
- static void usage();
- static void getsetting();
- static void clearunknowns();
- static void writegen();
- static STATUS loadstate();
- static STATUS readfile();
- static BOOL confirm();
- static long getnum();
- static char *getstr();
-
-
- main(argc, argv)
- char **argv;
- {
- char *str;
- STATUS status;
-
- if (--argc <= 0) {
- usage();
- exit(1);
- }
- argv++;
-
- while (argc-- > 0) {
- str = *argv++;
- if (*str++ != '-') {
- usage();
- exit(1);
- }
- switch (*str++) {
- case 'r': /* rows */
- rowmax = atoi(str);
- break;
-
- case 'c': /* columns */
- colmax = atoi(str);
- break;
-
- case 'g': /* generations */
- genmax = atoi(str);
- break;
-
- case 't': /* translation */
- switch (*str++) {
- case 'r':
- rowtrans = atoi(str);
- break;
- case 'c':
- coltrans = atoi(str);
- break;
- default:
- fprintf(stderr, "Bad translate\n");
- exit(1);
- }
- break;
-
- case 's': /* symmetry */
- switch (*str++) {
- case 'r':
- rowsym = TRUE;
- break;
- case 'c':
- colsym = TRUE;
- break;
- default:
- fprintf(stderr, "Bad symmetry\n");
- exit(1);
- }
- break;
-
- case 'd': /* dump frequency */
- dumpfreq = atol(str) * DUMPMULT;
- dumpfile = DUMPFILE;
- if ((argc > 0) && (**argv != '-')) {
- argc--;
- dumpfile = *argv++;
- }
- break;
-
- case 'v': /* view frequency */
- viewfreq = atol(str) * VIEWMULT;
- break;
-
- case 'l': /* load file */
- if (*str == 'n')
- nowait = TRUE;
- if ((argc <= 0) || (**argv == '-')) {
- fprintf(stderr, "Missing load file name\n");
- exit(1);
- }
- loadfile = *argv++;
- argc--;
- break;
-
- case 'i': /* initial file */
- if (*str == 'a')
- setall = TRUE;
- if ((argc <= 0) || (**argv == '-')) {
- fprintf(stderr, "Missing initial file name\n");
- exit(1);
- }
- initfile = *argv++;
- argc--;
- break;
-
- case 'o': /* output file */
- if ((argc <= 0) || (**argv == '-')) {
- fprintf(stderr, "Missing output file name\n");
- exit(1);
- }
- outputfile = *argv++;
- argc--;
- break;
-
- case 'm': /* max cell count */
- maxcount = atoi(str);
- break;
-
- case 'p': /* find parents only */
- parent = TRUE;
- break;
-
- case 'a':
- allobjects = TRUE; /* find all objects */
- break;
-
- case 'D': /* debugging output */
- debug = TRUE;
- break;
-
- default:
- fprintf(stderr, "Unknown option -%c\n",
- str[-1]);
- exit(1);
- }
- }
-
- if (parent && (rowtrans || coltrans)) {
- fprintf(stderr, "Cannot specify -tr or -tc with -p\n");
- exit(1);
- }
-
- if (ttyopen()) {
- fprintf(stderr, "Cannot initialize terminal\n");
- exit(1);
- }
-
- /*
- * Check for loading state from file or reading initial
- * object from file.
- */
- if (loadfile) {
- if (loadstate(loadfile) != OK) {
- ttyclose();
- exit(1);
- }
- } else {
- initcells();
- if (initfile) {
- if (readfile(initfile) != OK) {
- ttyclose();
- exit(1);
- }
- baseset = nextset;
- }
- }
-
- /*
- * If we are looking for parents, then set the current generation
- * to the last one so that it can be input easily. Then get the
- * commands to initialize the cells, unless we were told to not wait.
- */
- if (parent)
- curgen = genmax - 1;
-
- if (nowait)
- printgen(0);
- else
- getcommands();
-
- /*
- * Initial commands are complet, now look for the object.
- */
- while (TRUE) {
- if (curstatus == OK)
- curstatus = search();
-
- if (dumpfreq) {
- dumpcount = 0;
- dumpstate(dumpfile);
- }
-
- if ((curstatus == FOUND) && !allobjects && subperiods()) {
- curstatus = OK;
- continue;
- }
-
- curgen = 0;
-
- if (outputfile == NULL) {
- getcommands();
- continue;
- }
-
- /*
- * Here if results are going to a file.
- */
- if (curstatus == FOUND) {
- curstatus = OK;
- printgen(0);
- ttystatus("Object %ld found.\n", ++foundcount);
- writegen(outputfile, TRUE);
- continue;
- }
-
- if (foundcount == 0) {
- ttyclose();
- fprintf(stderr, "No objects found\n");
- exit(1);
- }
-
- ttyclose();
- printf("Search completed, file \"%s\" contains %ld object%s\n",
- outputfile, foundcount, (foundcount == 1) ? "" : "s");
- exit(0);
- }
- }
-
-
- /*
- * Get one or more user commands.
- * Commands are ended by a blank line.
- */
- void
- getcommands()
- {
- char *cp;
- char *cmd;
- char buf[LINESIZE];
-
- dumpcount = 0;
- viewcount = 0;
- printgen(curgen);
-
- while (TRUE) {
- ttyread("> ", buf, LINESIZE);
- cp = buf;
- while (isblank(*cp))
- cp++;
- cmd = cp;
- if (*cp)
- cp++;
- while (isblank(*cp))
- cp++;
-
- switch (*cmd) {
- case 'p': /* print previous generation */
- printgen((curgen + genmax - 1) % genmax);
- break;
-
- case 'n': /* print next generation */
- printgen((curgen + 1) % genmax);
- break;
-
- case 's': /* add a cell setting */
- getsetting(cp);
- break;
-
- case 'c': /* clear rest of generation */
- if (confirm("Clear all unknown cells? "))
- clearunknowns();
- break;
-
- case 'v': /* set view frequency */
- viewfreq = atol(cp) * VIEWMULT;
- printgen(curgen);
- break;
-
- case 'w': /* write generation to file */
- writegen(cp, FALSE);
- break;
-
- case 'd': /* dump state to file */
- dumpstate(cp);
- break;
-
- case 'N': /* find next object */
- if (curstatus == FOUND)
- curstatus = OK;
- return;
-
- case 'q': /* quit program */
- case 'Q':
- if (confirm("Really quit? ")) {
- ttyclose();
- exit(0);
- }
- break;
-
- case '\n': /* return from commands */
- case '\0':
- return;
-
- default:
- if (isdigit(*cmd)) {
- getsetting(cmd);
- break;
- }
- ttystatus("Unknown command\n");
- break;
- }
- }
- }
-
-
- /*
- * Get a cell to be set in the current generation.
- * The state of the cell is defaulted to ON.
- * Warning: Use of this routine invalidates backing up over
- * the setting, so that the setting is permanent.
- */
- static void
- getsetting(cp)
- char *cp;
- {
- int row;
- int col;
- STATE state;
-
- cp = getstr(cp, "Cell to set (row col [state]): ");
- if (*cp == '\0')
- return;
-
- row = getnum(&cp, -1);
- if (*cp == ',')
- cp++;
- col = getnum(&cp, -1);
- if (*cp == ',')
- cp++;
- state = getnum(&cp, 1);
-
- while (isblank(*cp))
- cp++;
- if (*cp != '\0') {
- ttystatus("Bad input line format\n");
- return;
- }
-
- if ((row <= 0) || (row > rowmax) || (col <= 0) || (col > colmax) ||
- (state < 0) || (state > 1))
- {
- ttystatus("Illegal cell value\n");
- return;
- }
-
- if (proceed(findcell(row, col, curgen), state, FALSE) != OK) {
- ttystatus("Inconsistent state for cell\n");
- return;
- }
-
- baseset = nextset;
- printgen(curgen);
- }
-
-
- /*
- * Clear all remaining unknown cells in the current generation.
- * This is used when searching for parents of a configuration.
- */
- static void
- clearunknowns()
- {
- int row;
- int col;
- CELL *cell;
-
- for (row = 1; row <= rowmax; row++) {
- for (col = 1; col <= colmax; col++) {
- cell = findcell(row, col, curgen);
- if (cell->state != UNK)
- continue;
-
- if (proceed(cell, OFF, FALSE) != OK)
- {
- ttystatus("Inconsistent state for cell\n");
- return;
- }
- }
- }
-
- baseset = nextset;
- printgen(curgen);
- }
-
-
- /*
- * Print out the current status of the specified generation.
- * This also sets the current generation.
- */
- void
- printgen(gen)
- {
- int row;
- int col;
- int count;
- CELL *cell;
- char *msg;
-
- curgen = gen;
-
- switch (curstatus) {
- case NOTEXIST: msg = "No such object"; break;
- case FOUND: msg = "Found object"; break;
- default: msg = ""; break;
- }
-
- count = 0;
- for (row = 1; row <= rowmax; row++) {
- for (col = 1; col <= colmax; col++) {
- count += (findcell(row, col, gen)->state == ON);
- }
- }
-
- ttyhome();
- ttyeeop();
-
- ttyprintf("%s (gen %d, cells %d) -g%d", msg, gen, count, genmax);
- if (rowtrans)
- ttyprintf(" -tr%d", rowtrans);
- if (coltrans)
- ttyprintf(" -tc%d", coltrans);
- if (rowsym)
- ttyprintf(" -sr");
- if (colsym)
- ttyprintf(" -sc");
- if (parent)
- ttyprintf(" -p");
- if (allobjects)
- ttyprintf(" -a");
- if (maxcount)
- ttyprintf(" -m%d", maxcount);
- if (viewfreq)
- ttyprintf(" -v%d", viewfreq / VIEWMULT);
- if (dumpfreq)
- ttyprintf(" -d%d %s", dumpfreq / DUMPMULT, dumpfile);
- if (outputfile)
- ttyprintf(" -o %s", outputfile);
- ttyprintf("\n");
-
- for (row = 1; row <= rowmax; row++) {
- for (col = 1; col <= colmax; col++) {
- cell = findcell(row, col, gen);
- switch (cell->state) {
- case OFF: msg = ". "; break;
- case ON: msg = "O "; break;
- case UNK: msg = "? "; break;
- }
-
- /*
- * If wide output, print only one character,
- * else print both characters.
- */
- ttywrite(msg, (colmax < 40) + 1);
- }
- ttywrite("\n", 1);
- }
-
- ttyhome();
- ttyflush();
- }
-
-
- /*
- * Write the current generation to the specified file.
- * Empty rows and columns are not written.
- * If no file is specified, it is asked for.
- */
- static void
- writegen(file, append)
- char *file; /* file name (or NULL) */
- BOOL append; /* TRUE to append instead of create */
- {
- FILE *fp;
- CELL *cell;
- int row;
- int col;
- int ch;
- int minrow, maxrow, mincol, maxcol;
-
- file = getstr(file, "Write object to file: ");
- if (*file == '\0')
- return;
-
- fp = fopen(file, append ? "a" : "w");
- if (fp == NULL) {
- ttystatus("Cannot create \"%s\"\n", file);
- return;
- }
-
- /*
- * First find the minimum bounds on the object.
- */
- minrow = rowmax;
- mincol = colmax;
- maxrow = 1;
- maxcol = 1;
-
- for (row = 1; row <= rowmax; row++) {
- for (col = 1; col <= colmax; col++) {
- cell = findcell(row, col, curgen);
- if (cell->state == OFF)
- continue;
- if (row < minrow)
- minrow = row;
- if (row > maxrow)
- maxrow = row;
- if (col < mincol)
- mincol = col;
- if (col > maxcol)
- maxcol = col;
- }
- }
-
- if (minrow > maxrow) {
- minrow = 1;
- maxrow = 1;
- mincol = 1;
- maxcol = 1;
- }
-
- /*
- * Now write out the bounded area.
- */
- for (row = minrow; row <= maxrow; row++) {
- for (col = mincol; col <= maxcol; col++) {
- cell = findcell(row, col, curgen);
- switch (cell->state) {
- case OFF: ch = '.'; break;
- case ON: ch = '*'; break;
- case UNK: ch = '?'; break;
- }
- fputc(ch, fp);
- }
- fputc('\n', fp);
- }
-
- if (append)
- fprintf(fp, "\n");
-
- if (fclose(fp))
- ttystatus("Error writing \"%s\"\n", file);
- else
- ttystatus("\"%s\" written\n", file);
- }
-
-
- /*
- * Dump the current state of the search in the specified file.
- * If no file is specified, it is asked for.
- */
- void
- dumpstate(file)
- char *file;
- {
- FILE *fp;
- CELL **set;
- CELL *cell;
-
- file = getstr(file, "Dump state to file: ");
- if (*file == '\0')
- return;
-
- fp = fopen(file, "w");
- if (fp == NULL) {
- ttystatus("Cannot create \"%s\"\n", file);
- return;
- }
-
- fprintf(fp, "V %d\n", DUMPVERSION);
- fprintf(fp, "I %d %d %d %d %d %d %d %d %d %d %d %d\n", rowmax, colmax,
- genmax, rowtrans, coltrans, rowsym, colsym, parent,
- allobjects, maxcount, cellcount, curstatus);
-
- set = settable;
- while (set != nextset) {
- cell = *set++;
- fprintf(fp, "S %d %d %d %d %d\n", cell->row, cell->col,
- cell->gen, cell->state, cell->free);
- }
-
- fprintf(fp, "T %d %d\n", baseset - settable, nextset - settable);
- fprintf(fp, "E\n");
-
- if (fclose(fp)) {
- ttystatus("Error writing \"%s\"\n", file);
- return;
- }
-
- ttystatus("State dumped to \"%s\"\n", file);
- }
-
-
- /*
- * Load a previously dumped state from a file.
- * Warning: Almost no checks are made for validity of the state.
- * Returns OK on success, ERROR on failure.
- */
- static STATUS
- loadstate(file)
- char *file;
- {
- FILE *fp;
- char *cp;
- int row;
- int col;
- int gen;
- STATE state;
- BOOL free;
- CELL *cell;
- char buf[LINESIZE];
-
- file = getstr(file, "Load state from file: ");
- if (*file == '\0')
- return OK;
-
- fp = fopen(file, "r");
- if (fp == NULL) {
- ttystatus("Cannot open state file \"%s\"\n", file);
- return ERROR;
- }
-
- buf[0] = '\0';
- fgets(buf, LINESIZE, fp);
- if (buf[0] != 'V') {
- ttystatus("Missing version line in file \"%s\"\n", file);
- fclose(fp);
- return ERROR;
- }
-
- cp = &buf[1];
- if (getnum(&cp, 0) != DUMPVERSION) {
- ttystatus("Unknown version in state file \"%s\"\n", file);
- fclose(fp);
- return ERROR;
- }
-
- fgets(buf, LINESIZE, fp);
- if (buf[0] != 'I') {
- ttystatus("Missing init line in state file\n");
- fclose(fp);
- return ERROR;
- }
- cp = &buf[1];
- rowmax = getnum(&cp, 0);
- colmax = getnum(&cp, 0);
- genmax = getnum(&cp, 0);
- rowtrans = getnum(&cp, 0);
- coltrans = getnum(&cp, 0);
- rowsym = getnum(&cp, 0);
- colsym = getnum(&cp, 0);
- parent = getnum(&cp, 0);
- allobjects = getnum(&cp, 0);
- maxcount = getnum(&cp, 0);
- cellcount = getnum(&cp, 0);
- curstatus = getnum(&cp, 0);
-
- initcells();
-
- newset = settable;
- for (;;) {
- buf[0] = '\0';
- fgets(buf, LINESIZE, fp);
- if (buf[0] != 'S')
- break;
-
- cp = &buf[1];
- row = getnum(&cp, 0);
- col = getnum(&cp, 0);
- gen = getnum(&cp, 0);
- state = getnum(&cp, 0);
- free = getnum(&cp, 0);
-
- cell = findcell(row, col, gen);
- cell->state = state;
- cell->free = free;
- *newset++ = cell;
- }
-
- if (buf[0] != 'T') {
- ttystatus("Missing table line in state file\n");
- fclose(fp);
- return ERROR;
- }
- cp = &buf[1];
- baseset = &settable[getnum(&cp, 0)];
- nextset = &settable[getnum(&cp, 0)];
-
- fgets(buf, LINESIZE, fp);
- if (buf[0] != 'E') {
- ttystatus("Missing end of file line in state file\n");
- fclose(fp);
- return ERROR;
- }
-
- if (fclose(fp)) {
- ttystatus("Error reading \"%s\"\n", file);
- return ERROR;
- }
-
- ttystatus("State loaded from \"%s\"\n", file);
- return OK;
- }
-
-
- /*
- * Read a file containing initial settings for either gen 0 or the last gen.
- * If setall is TRUE, both the ON and the OFF cells will be set.
- * Returns OK on success, ERROR on error.
- */
- static STATUS
- readfile(file)
- char *file;
- {
- FILE *fp;
- char *cp;
- char ch;
- int row;
- int col;
- int gen;
- STATE state;
- char buf[LINESIZE];
-
- file = getstr(file, "Read initial object from file: ");
- if (*file == '\0')
- return OK;
-
- fp = fopen(file, "r");
- if (fp == NULL) {
- CELL **set;
- ttystatus("Cannot open \"%s\"\n", file);
- return ERROR;
- }
-
- gen = (parent ? (genmax - 1) : 0);
- row = 0;
- while (fgets(buf, LINESIZE, fp)) {
- row++;
- cp = buf;
- col = 0;
- while (*cp && (*cp != '\n')) {
- col++;
- ch = *cp++;
- switch (ch) {
- case '?':
- continue;
- case '.':
- case ' ':
- if (!setall)
- continue;
- state = OFF;
- break;
- case 'O':
- case 'o':
- case '*':
- state = ON;
- break;
- default:
- ttystatus("Bad file format in line %d\n",
- row);
- fclose(fp);
- return ERROR;
- }
-
- if (proceed(findcell(row, col, gen), state, FALSE)
- != OK)
- {
- ttystatus("Inconsistent state for cell %d %d\n",
- row, col);
- fclose(fp);
- return ERROR;
- }
- }
- }
-
- if (fclose(fp)) {
- ttystatus("Error reading \"%s\"\n", file);
- return ERROR;
- }
- return OK;
- }
-
-
- /*
- * Check a string for being NULL, and if so, ask the user to specify a
- * value for it. Returned string may be static and thus is overwritten
- * for each call. Leading spaces in the string are skipped over.
- */
- static char *
- getstr(str, prompt)
- char *str; /* string to check for NULLness */
- char *prompt; /* message to prompt with */
- {
- static char buf[LINESIZE];
-
- if ((str == NULL) || (*str == '\0')) {
- ttyread(prompt, buf, LINESIZE);
- str = buf;
- }
- while (isblank(*str))
- str++;
- return str;
- }
-
-
- /*
- * Confirm an action by prompting with the specified string and reading
- * an answer. Entering 'y' or 'Y' indicates TRUE, everything else FALSE.
- */
- static BOOL
- confirm(prompt)
- char *prompt;
- {
- char ch;
-
- ch = *getstr(NULL, prompt);
- if ((ch == 'y') || (ch == 'Y'))
- return TRUE;
- return FALSE;
- }
-
-
- /*
- * Read a number from a string, eating any leading or trailing blanks.
- * Returns the value, and indirectly updates the string pointer.
- * Returns specified default if no number was found.
- */
- static long
- getnum(cpp, defnum)
- char **cpp;
- int defnum;
- {
- char *cp;
- long num;
-
- cp = *cpp;
- while (isblank(*cp))
- cp++;
- if (!isdigit(*cp)) {
- *cpp = cp;
- return defnum;
- }
- num = 0;
- while (isdigit(*cp))
- num = num * 10 + (*cp++ - '0');
- while (isblank(*cp))
- cp++;
- *cpp = cp;
- return num;
- }
-
-
- /*
- * Print usage text.
- */
- static void
- usage()
- {
- char **cpp;
- static char *text[] = {
- "Program to search for life oscillators or spaceships.",
- "",
- "lifesrc -r# -c# -g# -tr# -tc# -m# -sr -sc -p -a -v# -i[a] file -o file -d# file",
- "lifesrc -l[n] file -v# -o file -d# file",
- "",
- " -r Number of rows",
- " -c Number of columns",
- " -g Number of generations",
- " -tr Translate rows between last and first generation",
- " -tc Translate columns between last and first generation",
- " -m Maximum live cells for generation 0",
- " -sr Enforce symmetry on rows",
- " -sc Enforce symmetry on columns",
- " -p Only look for parents of last generation",
- " -a Find all objects (even those with subperiods)",
- " -v View object every N thousand searches",
- " -d Dump status to file every N thousand searches",
- " -l Load status from file",
- " -ln Load status without entering command mode",
- " -i Read initial object from file setting only ON cells",
- " -ia Read initial object setting both ON and OFF cells",
- " -o Output found objects to file (appending)",
- NULL
- };
-
- for (cpp = text; *cpp; cpp++)
- fprintf(stderr, "%s\n", *cpp);
- }
-
- /* END CODE */
-