|
DataMuseum.dkPresents historical artifacts from the history of: DKUUG/EUUG Conference tapes |
This is an automatic "excavation" of a thematic subset of
See our Wiki for more about DKUUG/EUUG Conference tapes Excavated with: AutoArchaeologist - Free & Open Source Software. |
top - metrics - downloadIndex: T p
Length: 23213 (0x5aad) Types: TextFile Names: »puzzle15.c«
└─⟦b20c6495f⟧ Bits:30007238 EUUGD18: Wien-båndet, efterår 1987 └─⟦this⟧ »EUUGD18/General/Puzzle15/puzzle15.c«
/* * This program simulates a simple toy with many many variations. * * Compile with -DARROWKEYS if your curses library understands about * arrow keys (and your keyboards have them). * Compile with -DNOSIGNAL if your machine does not support the signal * function to catch ctrl-C. * Compile with "-DDIECHAR='<somechar>'" if you would like the program * to exit gracefully when <somechar> is typed. All the quotes in and * around this argument are necessary. The double quotes prevent the * shell from stripping the single quotes. Instead of '<somechar' you * can also type the ascii value of the key. If you do so, the double * quotes are not needed. * * The copyright message is there to prevent uncontrolled spreading of * a zillion different versions. * * You can mail your improvements and suggestions to me. I may include * them in a future version of the program. * * I do not guarantee the fitness of this program for any purpose. * USE IT AT YOUR OWN RISK. * * Peter Knoppers - knop@duteca.UUCP * Bilderdijkhof 59 * 2624 ZG Delft * The Netherlands * * * The following lines are put in the compiled program by the C compiler * where they can be found by programs such as (Bsd) strings. */ char *this_is = "puzzle15 V2.0 Feb 24 1989"; char *author0 = "(C) Copyright 1985, 1988, 1989 Peter Knoppers"; char *author1 = "Arrow keys added by Paul Lew 1988"; char *author2 = "Clock added by Bo Kullmar 1988"; char *author3 = "Select-tile method suggested by Larry Hastings 1988"; char *authorx = "Additions merged by Peter Knoppers"; char *release1 = "Permission to use and redistribute unmodified "; char *release2 = "copies with source is granted to everyone"; char *release3 = "Distribution of modified copies is NOT allowed"; #include <curses.h> #ifndef NOSIGNAL #include <signal.h> #endif #include <ctype.h> #ifndef DIECHAR #define DIECHAR 999 /* any non-ASCII value will do */ #endif #define DEFSIZE 4 /* default size of board */ #define BLANK 0 #define UNSPECIFIED (-1) /* classification of arguments */ #define MOVEMETHOD 1 #define CURSORMETHOD 2 #define FACEMETHOD 3 /* magic values are required to be different, no other restrictions apply */ /* magic values for Movemethods */ #define MOVEBLANK 4 #define MOVETILE 5 #define SELECTTILE 6 /* magic values for Cursormethods */ #define VIKEYS 7 #ifdef ARROWKEYS #define ARROWK 8 #endif #define NUMBERKEYS 9 #define FACEKEYS 10 #define KEYS 11 /* magic values for Facemethods */ #define NUMBERFACES 12 #define ALPHAFACES 13 #define NUMBERALPHAFACES 14 #define ALPHANUMBERFACES 15 #define FACES 16 unsigned long Time_start; int Movecnt = 0; char *malloc (); /* to satisfy lint */ long time (); int Sizex = UNSPECIFIED; /* x-size of the board */ int Sizey = UNSPECIFIED; /* y-size of the board */ int Size2; /* total surface of the board */ int Movemethod = UNSPECIFIED; int Cursormethod = UNSPECIFIED; int Facemethod = UNSPECIFIED; char *Movename; /* name of the selected Movemethod */ char *Cursorname; /* name of the selected Cursormethod */ char *Cursorkeys; /* the user-defined Cursorkeys */ char *Facename; /* name of the selected Facemethod */ char *Facechars; /* the user-defined tile-faces */ int Up; /* key-codes for cursor movements */ int Down; int Left; int Right; int Newpos; /* new position for empty field */ char *Myname; /* name used to invoke this program */ /* * All possible keyword arguments are listed in the Argopts table. * This is done in order to * - ensure that every keyword appears at only one place in the program * (this makes the program easier to modify/maintain) * - put most of the knowledge about incompatible arguments in one place * instead of scattering it all over the program. * - simplify the argument parser * (I haven't been completely succesfull in this respect...) */ struct argtablemember { char *a_name; /* what the user types */ int a_type; /* what kind of spec. it is */ int a_magic; /* magic value */ } Argopts[] = { { "moveblank", MOVEMETHOD, MOVEBLANK }, { "movetile", MOVEMETHOD, MOVETILE }, { /* SELECTTILE must be before FACEKEYS */ "selecttile", MOVEMETHOD, SELECTTILE }, { "vikeys", CURSORMETHOD, VIKEYS }, #ifdef ARROWKEYS { "arrowkeys", CURSORMETHOD, ARROWK }, #endif { "numberkeys", CURSORMETHOD, NUMBERKEYS }, { "facekeys", CURSORMETHOD, FACEKEYS }, { "keys", CURSORMETHOD, KEYS }, { "numberfaces", FACEMETHOD, NUMBERFACES }, { "alphafaces", FACEMETHOD, ALPHAFACES }, { "numberalphafaces", FACEMETHOD, NUMBERALPHAFACES }, { "alphanumberfaces", FACEMETHOD, ALPHANUMBERFACES }, { "faces", FACEMETHOD, FACES }, { (char *) 0, 0, 0 } }; die () /* nice exit on ctrl-C */ { #ifndef NOSIGNAL signal (SIGINT, SIG_IGN); /* ignore ctrl-C */ #endif mvprintw (LINES - 1, 0, "Goodbye. "); clrtoeol (); refresh (); endwin (); /* shutdown curses, restore ttymode */ exit (0); } main (argc, argv) /* scan the arguments, call game() */ char **argv; { struct argtablemember *atmp;/* to walk Argopts table */ int swap; /* to revers directions */ Myname = *argv; /* save this for usage () */ /* * scan the arguments */ while (*++argv != ((char *) 0)) { /* walk argument list */ for (atmp = Argopts; atmp -> a_name != (char *) 0; atmp++) { /* scan the keyword list */ if (strcmp (*argv, atmp -> a_name) == 0) { /* found keyword */ switch (atmp -> a_type) { case MOVEMETHOD: if (Movemethod != UNSPECIFIED) conflict (Movename, atmp -> a_name); Movename = atmp -> a_name; Movemethod = atmp -> a_magic; break; case CURSORMETHOD: if (Cursormethod != UNSPECIFIED) conflict (Cursorname, atmp -> a_name); Cursorname = atmp -> a_name; Cursormethod = atmp -> a_magic; if (atmp -> a_magic == KEYS) { if ((Cursorkeys = *++argv) == (char *) 0) novalue (*--argv);/* never returns */ if (strlen (Cursorkeys) != 4) { printf ("%s need 4 cursorkeys\n", Cursorname); usage (); } } break; case FACEMETHOD: if (Facemethod != UNSPECIFIED) conflict (Facename, atmp -> a_name); Facename = atmp -> a_name; Facemethod = atmp -> a_magic; if (atmp -> a_magic == FACES) if ((Facechars = *++argv) == (char *) 0) novalue (*--argv); break; default: printf ("aargh: bad switch (atmp -> a_type = %d)\n", atmp -> a_type); exit (1); } break; } } if (atmp -> a_name == (char *) 0)/* not a keyword */ if (isdigit (**argv) && (Sizex == UNSPECIFIED)) { /* it's a boardsize specification */ if (sscanf (*argv, "%dx%d", &Sizex, &Sizey) != 2) if (sscanf (*argv, "%d", &Sizex) == 1) Sizey = Sizex; else { printf ("bad argument %s\n", *argv); usage (); } } else /* it's garbage */ { printf ("bad argument %s\n", *argv); usage (); } } /* * insert default values */ if (Sizex == UNSPECIFIED) Sizex = Sizey = DEFSIZE; if (Cursormethod == UNSPECIFIED) { /* find first CURSORMETHOD in Argopts */ for (atmp = Argopts; atmp -> a_name != (char *) 0; atmp++) if (atmp -> a_type == CURSORMETHOD) break; if (atmp -> a_name == (char *) 0) { printf ("aargh: can't find default Cursormethod\n"); exit (1); } Cursormethod = atmp -> a_magic; Cursorname = atmp -> a_name; } if (Facemethod == UNSPECIFIED) { /* find first FACEMETHOD in Argopts */ for (atmp = Argopts; atmp -> a_name != (char *) 0; atmp++) if (atmp -> a_type == FACEMETHOD) break; if (atmp -> a_name == (char *) 0) { printf ("aargh: can't find default Facemethod\n"); exit (1); } Facemethod = atmp -> a_magic; Facename = atmp -> a_name; } if (Movemethod == UNSPECIFIED) if (Cursormethod == FACEKEYS) Movemethod = SELECTTILE;/* by implication */ else { /* find first MOVEMETHOD in Argopts */ for (atmp = Argopts; atmp -> a_name != (char *) 0; atmp++) if (atmp -> a_type == MOVEMETHOD) break; if (atmp -> a_name == (char *) 0) { printf ("aargh: can't find default Movemethod\n"); exit (1); } Movemethod = atmp -> a_magic; Movename = atmp -> a_name; } if ((Cursormethod == FACEKEYS) && (Movemethod != SELECTTILE)) conflict (Cursorname, Movename);/* does not return */ switch (Cursormethod) { case VIKEYS: Up = 'k'; Down = 'j'; Left = 'h'; Right = 'l'; break; #ifdef ARROWKEYS case ARROWK: Up = KEY_UP; Down = KEY_DOWN; Left = KEY_LEFT; Right = KEY_RIGHT; break; #endif case NUMBERKEYS: Up = '8'; Down = '2'; Left = '4'; Right = '6'; break; case FACEKEYS: Up = 1000; /* values must not correspond to any ASCII */ Down = 1001; /* value, otherwise no restrictions */ Left = 1002; Right = 1003; break; case KEYS: Up = Cursorkeys[0]; Down = Cursorkeys[1]; Left = Cursorkeys[2]; Right = Cursorkeys[3]; break; default: printf ("aargh: bad switch (Cursormethod = %d)\n", Cursormethod); exit (1); } if (Movemethod == MOVETILE) { /* revers cursor directions */ swap = Up; Up = Down; Down = swap; swap = Left; Left = Right; Right = swap; } Size2 = Sizex * Sizey; /* surface area of the board */ switch (Facemethod) { case NUMBERFACES: if (Size2 <= 10) /* tiles can be labeled with 1 char */ { Facechars = "123456789"; Facemethod = NUMBERALPHAFACES; } break; case ALPHAFACES: Facechars = "abcdefghijklmnopqrstuvwxyz"; break; case NUMBERALPHAFACES: Facechars = "0123456789abcdefghijklmnopqrstuvwxyz"; break; case ALPHANUMBERFACES: Facechars = "abcdefghijklmnopqrstuvwxyz0123456789"; break; } if ((Size2 - 1) > ((Facemethod == NUMBERFACES) ? 99 : strlen (Facechars))) { printf ("sorry, too many tiles - not enough labels\n"); exit (0); } if ((Sizex < 2) || (Sizey < 2)) { printf ("sorry, board is too small for a non-trivial game\n"); exit (0); } /* * Initialize the curses screen handling functions */ #ifndef NOSIGNAL signal (SIGINT, SIG_IGN); /* protect this critical section */ #endif initscr (); crmode (); noecho (); #ifdef ARROWKEYS keypad (stdscr, TRUE); #endif #ifndef NOSIGNAL signal (SIGINT, die); /* die() knows how to restore ttymode */ #endif /* * From now on use die() to exit the program, otherwise the * ttymode will not be restored. * * Now that curses has been initialized we can check whether the board * fits on the screen. */ if (Sizex > (COLS - 1) / 5) /* COLS works only after initscr() */ { mvprintw (LINES - 2, 0, "sorry, your screen is not wide enough\n"); die (); } if (Sizey > (LINES - 4) / 2) { mvprintw (LINES - 2, 0, "sorry, your screen is not tall enough\n"); die (); } game (); /* play the game */ mvprintw (LINES - 2, 0, "You've reached the solution in %d moves.\n", Movecnt); die (); /* restore ttymode and exit */ } usage () { int curtype = UNSPECIFIED; int prevtype = UNSPECIFIED; char *selecttilename = (char *) 0; struct argtablemember *atmp;/* to walk Argopts table */ printf ("usage: %s [<width[x<height>]] %s", Myname, "[movemethod] [cursormethod] [facemethod]\n"); for (atmp = Argopts; atmp -> a_name != (char *) 0; atmp++) { if (atmp -> a_magic == SELECTTILE) selecttilename = atmp -> a_name; if (atmp -> a_type != curtype) { curtype = atmp -> a_type; if (curtype == MOVEMETHOD) printf ("possible movemethods:\n"); if (curtype == CURSORMETHOD) printf ("possible cursormethods:\n"); if (curtype == FACEMETHOD) printf ("possible facemethods:\n"); } switch (atmp -> a_magic) { case KEYS: printf ("\t\t%s <up><down><left><right>", atmp -> a_name); break; case FACES: printf ("\t\t%s <facecharacters>", atmp -> a_name); break; case FACEKEYS: if (selecttilename == (char *) 0) { printf ("aargh: can't find string for SELECTTILE\n"); exit (1); } printf ("\t\t%-20.20s (implies movemethod %s)", atmp -> a_name, selecttilename); break; default: printf ("\t\t%-20.20s", atmp -> a_name); break; } if (curtype != prevtype) printf (" (this is default)\n"); else printf ("\n"); prevtype = curtype; } exit (0); } conflict (word1, word2) char *word1; char *word2; { printf ("You may not specify both %s and %s\n", word1, word2); usage (); } novalue (word) char *word; { printf ("%s requires an argument\n", word); usage (); } game () /* initialize board, execute moves */ { register int i, j; /* generally used indices and counters */ int *board; /* pointer to malloc-ed board */ int empty; /* position of empty field */ int swap; /* used to swap two tiles */ int nswap = 0; /* to determine reachability */ int steps; /* number of tiles that move */ int unchanged = 0; /* used to indicate that board has changed */ int cursorx; /* to save cursorposition while */ int cursory; /* printing error messages */ int m; /* move, first character / direction */ int m2; /* second character of move */ /* * Set up the board and shuffle the tiles */ board = (int *) malloc ((unsigned) Size2 * sizeof (int)); if (board == NULL) { printf ("aargh: malloc failed\n"); die (); } srand ((int) time ((long) 0));/* initialize random number generator */ for (i = 1; i < Size2; i++) /* put tiles on the board in their */ board[i - 1] = i; /* final positions */ for (i = Size2 - 2; i > 0; i--)/* permutate tiles */ { j = rand () % i; swap = board[i]; board[i] = board[j]; board[j] = swap; } /* * Check that final position can be reached from current permutation */ for (i = 0; i < Size2 - 1; i++) for (j = 0; j < i; j++) if (board[j] > board[i]) nswap++; if ((nswap % 2) != 0) /* this position is unreachable */ { /* swap two adjacent tiles */ swap = board[1]; board[1] = board[0]; board[0] = swap; } empty = Size2 - 1; /* empty field starts in lower right */ board[empty] = BLANK; /* corner */ Newpos = empty; Time_start = time ((long *) 0);/* start the clock */ while (1) /* until final position is reached */ { if (unchanged == 0) /* the board must be (re-)printed */ { printboard (board); /* also puts cursor at Newpos */ unchanged++; /* the board on the screen is up to date */ /* * Check if final position is reached */ for (i = 0; i < Size2 - 1; i++) if (board[i] != i + 1) break; /* final position is not yet reached */ if (i == Size2 - 1) /* all tiles are in final positions */ return; /* game ends */ } /* * Let the user make a move */ m = getch (); if (m == DIECHAR) die (); if (Movemethod == SELECTTILE) { if (Cursormethod == FACEKEYS) { if (Facemethod == NUMBERFACES) { if (!isdigit (m)) { getyx (stdscr, cursory, cursorx); mvprintw (LINES - 1, 0, "use one of the keys"); for (i = 0; (i <= Size2) && (i < 10); i++) printw (" %d", i); clrtoeol (); move (cursory, cursorx); refresh (); continue; } if ((m - '0') > (Size2 / 10)) m -= '0'; else /* we need a second digit */ { m = (m - '0') * 10; m2 = getch (); if (m == DIECHAR) die (); if ((!isdigit (m2)) || (m + m2 - '0' >= Size2)) { getyx (stdscr, cursory, cursorx); mvprintw (LINES - 1, 0, "use one of the keys"); for (i = 0; (i < Size2 % 10) && (i + m < Size2); i++) printw (" %d", i); clrtoeol (); move (cursory, cursorx); refresh (); continue; } m += m2 - '0'; } /* * find out where this tile is on the board */ for (Newpos = 0; Newpos < Size2; Newpos++) if (board[Newpos] == m) break;/* found tile */ if (Newpos == Size2)/* no tile with face m */ { mvprintw (LINES - 2, 0, "aargh: can't find tile %d on board\n", m); die (); } } else { /* * Facemethod != NUMBERFACES * This means that a single keystroke identifies the * tile that is to be moved. */ for (Newpos = 0; Newpos < Size2; Newpos++) if (board[Newpos] > 0) if (Facechars[board[Newpos] - 1] == m) break;/* found tile */ if (Newpos == Size2) { getyx (stdscr, cursory, cursorx); mvprintw (LINES - 1, 0, "use one of the keys "); for (i = 0; (i < Size2 - 1) && (i < 30); i++) printw ("%c", Facechars[i]); if (i < Size2 - 1) printw ("..."); clrtoeol (); move (cursory, cursorx); refresh (); continue; } } } else /* Cursormethod != FACEKEYS */ { if (m == Up) { if (Newpos >= Sizex) Newpos -= Sizex; unchanged = 0;/* board must be reprinted */ continue; } if (m == Down) { if (Newpos + Sizex < Size2) Newpos += Sizex; unchanged = 0; continue; } if (m == Left) { if ((Newpos % Sizex) != 0) Newpos--; unchanged = 0; continue; } if (m == Right) { if (((Newpos + 1) % Sizex) != 0) Newpos++; unchanged = 0; continue; } /* * If a key not in the set { Up, Down, Left, Right } was * typed we fall through and try to move the empty field to * Newpos. */ } /* * The user has indicated a new location for the empty field. * The new position of the empty field in the array board is in * Newpos. * We must now check that the new position is on the same row * or the same column as the current position and we must * determine how many tiles must be moved. */ if (Newpos == empty) continue; /* nothing changed */ steps = 0; if (Newpos > empty) { if ((empty % Sizex + Newpos - empty) < Sizex) { m = Right; steps = Newpos - empty; } else if (((Newpos - empty) % Sizex) == 0) { m = Down; steps = (Newpos - empty) / Sizex; } } else { if (empty % Sizex + Newpos - empty >= 0) { m = Left; steps = empty - Newpos; } else if (((empty - Newpos) % Sizex) == 0) { m = Up; steps = (empty - Newpos) / Sizex; } } if (steps == 0) { getyx (stdscr, cursory, cursorx); mvprintw (LINES - 1, 0, "tile must be in same row as empty field\n"); move (cursory, cursorx); refresh (); continue; } } else /* Movemethod is MOVEBLANK or MOVETILE */ steps = 1; /* one step per move */ /* * m should now be one of the four directions, but it may be an * illegal key. This can not happen if Movemethod == SELECTTILE. * * steps indicates how many tiles are to be moved */ if ((m != Up) && (m != Down) && (m != Left) && (m != Right)) { getyx (stdscr, cursory, cursorx); #ifdef ARROWKEYS if (Cursormethod == ARROWK) mvprintw (LINES - 1, 0, "Use the arrow keys for up, down, left and right"); else #endif if (Movemethod == MOVETILE) { mvprintw (LINES - 1, 0, "use %c for up, %c for down, ", Down, Up); printw ("%c for left and %c for right", Right, Left); } else { mvprintw (LINES - 1, 0, "use %c for up, %c for down, ", Up, Down); printw ("%c for left and %c for right", Left, Right); } clrtoeol (); move (cursory, cursorx); refresh (); continue; } /* * m contains the direction to move * steps contains the number of tiles to move * Apply the move to the board. */ if (m == Up) if (empty >= Sizex) while (steps-- > 0) { board[empty] = board[empty - Sizex]; board[empty - Sizex] = BLANK; empty -= Sizex; Movecnt++; } if (m == Down) if (empty + Sizex < Size2) while (steps-- > 0) { board[empty] = board[empty + Sizex]; board[empty + Sizex] = BLANK; empty += Sizex; Movecnt++; } if (m == Left) if ((empty % Sizex) != 0) while (steps-- > 0) { board[empty] = board[empty - 1]; board[empty - 1] = BLANK; empty--; Movecnt++; } if (m == Right) if (((empty + 1) % Sizex) != 0) while (steps-- > 0) { board[empty] = board[empty + 1]; board[empty + 1] = BLANK; empty++; Movecnt++; } if (steps == 1) /* you ran into a wall */ { getyx (stdscr, cursory, cursorx); mvprintw (LINES - 1, 0, "Your can't cross that wall\n"); clrtoeol (); move (cursory, cursorx); refresh (); continue; } if (steps != -1) /* something is very wrong */ { mvprintw (LINES - 2, 0, "aargh: couldn't move enough tiles (steps = %d)\n", steps); die (); } Newpos = empty; unchanged = 0; /* the board must be reprinted */ } } printboard (board) int *board; { register int i, j; int tilewidth; unsigned long time_used; unsigned minutes; unsigned seconds; unsigned long time_now; tilewidth = ((Facemethod == NUMBERFACES) && (Size2 > 10)) ? 5 : 4; mvprintw ((LINES - 4 - 2 * Sizey) / 2, (COLS - 1 - tilewidth * Sizex) / 2, "+"); /* print top edge of board */ for (j = 0; j < Sizex; j++) if (tilewidth == 5) printw ("----+"); else printw ("---+"); for (i = 0; i < Sizey; i++) { mvprintw ((LINES - 4 - 2 * Sizey) / 2 + i * 2 + 1, (COLS - 1 - tilewidth * Sizex) / 2, "| "); for (j = 0; j < Sizex; j++) if (tilewidth == 5) if (board[Sizex * i + j] != BLANK) if ((Size2 > 9) && (Cursormethod == FACEKEYS) && (board[Sizex * i + j] <= Size2 / 10)) printw ("%02d | ", board[Sizex * i + j]); else printw ("%2d | ", board[Sizex * i + j]); else printw (" | "); else if (board[Sizex * i + j] != BLANK) printw ("%c | ", Facechars[board[Sizex * i + j] - 1]); else printw (" | "); mvprintw ((LINES - 4 - 2 * Sizey) / 2 + i * 2 + 2, (COLS - 1 - tilewidth * Sizex) / 2, "+"); for (j = 0; j < Sizex; j++) if (tilewidth == 5) printw ("----+"); else printw ("---+"); } mvprintw (LINES - 1, 0, "\n");/* erase error messages */ /* * Update the clock */ time_now = time ((long *) 0); time_used = time_now - Time_start; minutes = time_used / 60; seconds = time_used % 60; mvprintw (LINES - 3, 0, "Time used: %02d min %02d sec Move: %d", minutes, seconds, Movecnt); /* * Put cursor on the position indicated by Newpos */ move ((LINES - 4 - 2 * Sizey) / 2 + (Newpos / Sizex) * 2 + 1, (COLS - 1 - tilewidth * Sizex) / 2 + (Newpos % Sizex) * tilewidth + tilewidth / 2); refresh (); /* put all this on the screen */ }