diff options
Diffstat (limited to 'nmm.c')
-rw-r--r-- | nmm.c | 1366 |
1 files changed, 981 insertions, 385 deletions
@@ -29,11 +29,12 @@ #include <curses.h> #include <err.h> #include <errno.h> +#include <libgen.h> #include <stdio.h> #include <stdlib.h> #include <string.h> -#define VERSION "0.1" +#define VERSION "1.0~beta1" #define EMPTY 'E' #define WHITEC 'W' @@ -41,14 +42,30 @@ #define WHITE 0 #define BLACK 1 #define NORTH 0 -#define EAST 1 -#define SOUTH 2 -#define WEST 3 -#define NORTHC 'n' -#define EASTC 'e' -#define SOUTHC 's' -#define WESTC 'w' -#define PIECECOUNT 8 +#define SOUTH 1 +#define WEST 2 +#define EAST 3 +#define NE 4 +#define SW 5 +#define NW 6 +#define SE 7 +#define MINDIR NORTH +#define MAXDIR SE +#define NORTHC "n" +#define EASTC "e" +#define SOUTHC "s" +#define WESTC "w" +#define NEC "ne" +#define NWC "nw" +#define SEC "se" +#define SWC "sw" + +#define ROWS 3 +#define COLS 9 + +#define NMM 4 +#define TMM 3 +#define TWMM 12 #define sbrow 3 /* score box */ #define sbcol 37 @@ -59,69 +76,98 @@ #define msgcol 7 #define promptcol 16 #define promptrow 7 +#define versrow 17 /* version col calculated based on helpcol */ +#define helpcol 65 +#define helprow 0 /* Board & game construction */ -struct point { +typedef struct point { char v; /* Value: what's currently here */ - struct point* n[4]; /* Our neighbours, indexed by NORTH, SOUTH, EAST, WEST */ -}; + struct point* n[8]; /* Our neighbours, indexed by NORTH, SOUTH, WEST, EAST, + NE, SW, NW, SE, see dirc below */ +} point; -typedef struct point nboard[3][8]; +typedef point* nboard[ROWS][COLS]; -struct game { +typedef struct game { nboard board; int state; int pieces[2]; int totalpieces; int phase; -}; + int type; +} game; -struct scrgame { +typedef struct scrgame { struct game *game; WINDOW *score_w; WINDOW *board_w; WINDOW *msg_w; +} scrgame; + +/* Let's make looking up directions -> indices easier */ +struct strkey { char *k; int v; }; + +static const struct strkey dirc[] = { + { NORTHC , NORTH }, { SOUTHC, SOUTH }, { EASTC, EAST }, { WESTC, WEST }, + { NEC, NE }, { NWC, NW }, { SEC, SE }, { SWC, SW }, + { 0 } /* GCC will incorrectly complain about this, GCC BUG#53119 */ }; -void joinns(struct point *, struct point *); -void joinwe(struct point *, struct point *); -void freepoint(struct point *); -struct point makepoint(const char, struct point *, struct point *, - struct point *, struct point *); -void updatepoint(struct point *, const char, struct point *, struct point *, - struct point *, struct point *); -void initboard(nboard); -void initgame(struct game *); -int gameend(struct scrgame *); -/* Rendering functions */ -WINDOW *create_scorebox(const struct game *); -void update_scorebox(WINDOW *, const struct game *); -WINDOW *create_board(const struct game *); -void update_board(WINDOW *, const struct game *); -WINDOW *create_msgbox(void); -void update_msgbox(WINDOW *, const char *); -void initall(struct scrgame *); -/* During game */ -void printgame(const struct game *); -int inmill(const struct point *); -struct point *mill_handler(struct scrgame *, char *, int); -int validcoords(const char *); -int dirtoindex(const char); -char statechar(const struct game *); -char *getmove(struct scrgame *, char *, int); -struct point *getpoint(struct game *, const char *); -struct point *placepiece(struct scrgame *, const char *); -int checkadjacent(const struct point *, const struct point *); -struct point *removepiece(struct game *, struct point *); -struct point *jumppiece(struct point *, struct game *, const char *); -struct point *movepiece(struct point *, char); -struct point *phaseone(struct scrgame *); -struct point *phasetwo(struct scrgame *); -int checkdir(const char *); -char* prompt(const char *, char *, const int); -char* promptcheck(const char *, char *, const int, const int, - const char *, int (*function)(const char *)); -int main(void); +int joinpts(const int, point *, point *); +void joinns(point *, point *); +void joinwe(point *, point *); +void joinnesw(point *, point *); +void joinnwse(point *, point *); +void freepoint(point *); +int dirtoindex(const char *); +point makepoint(const char, point *, point *, + point *, point *); +void updatepoint(point *, const char, point *, point *, + point *, point *); +void init3board(nboard); +void init9board(nboard); +void init12board(nboard); +void initgame(game *, const int); +void initall(scrgame *, const int); +/* Rendering functions */ +WINDOW *create_scorebox(const game *); +void update_scorebox(WINDOW *, const game *); +WINDOW *create_board(const game *); +WINDOW *create_3board(const game *); +WINDOW *create_9board(const game *); +WINDOW *create_12board(const game *); +void update_board(WINDOW *, const game *); +void update_3board(WINDOW *, const game *); +void update_9board(WINDOW *, const game *); +WINDOW *create_msgbox(void); +void update_msgbox(WINDOW *, const char *); +char *getgname(const game *); +void full_redraw(scrgame *); +void printinstrs(scrgame *); +int gameend(const scrgame *); +__dead void quit(void); +/* During game */ +int inmill(const point *); +point *mill_handler(scrgame *, char *, int); +char statechar(const game *); +int validcoords(const game *, const char *); +int valid3coords(const char *); +int valid9coords(const char *); +int checkdir(const char *); +char *getinput(scrgame *, char *, const int); +char *getmove(scrgame *, char *, int); +point *getpoint(const game *, const char *); +point *get3point(const game *, const char *); +point *get9point(const game *, const char *); +point *placepiece(const scrgame *, const char *); +point *movepiece(point *, const char *); +point *jumppiece(point *, game *, const char *); +point *removepiece(game *, point *); +point *phaseone(scrgame *); +point *phasetwothree(scrgame *); +char *lower(char *); +int main(int, char **); const char *const instructions[] = { "The board consists of positions connected with dashed lines, initially\n", @@ -130,74 +176,143 @@ const char *const instructions[] = { "reduce the opponent to 2 pieces. Gameplay is split into three phases.\n", "\n", "Phase 1. Users alternatingly place a piece on the board, until each\n", - " player has placed 7 pieces. The location is selected by entering its\n", - " coordinates, e.g. `a1' or `g7'. A mill can be formed by aligning\n", + " player has placed 3/9/12 pieces. The location is selected by entering\n", + " its coordinates, e.g. `a1' or `g7'. A mill can be formed by aligning\n", " three pieces along a dashed line, at which point an opponent's piece\n", " is removed. This piece is selected by entering its coordinates.\n", "Phase 2. Users alternatingly slide one of their pieces along dashed line\n", " to an adjacent empty position. The piece is selected by entering its\n", - " coordinates and its direction is chosen from n s e w, for North, South,\n", - " East, West. For example, `a1n' moves the piece at a1 north, `g7s' moves\n", - " the piece at g7 south. Mills are formed and pieces removed as in\n", - " phase 1. Phase 2 continues until a player only has 3 pieces remaining.\n", + " coordinates and its direction is chosen from n s e w ne nw se sw, for\n", + " North, South, East, West, etc. For example, `a1n' moves the piece at\n", + " a1 north, `g7sw' moves the piece at g7 south west. Mills are formed\n", + " and pieces removed as in phase 1. Phase 2 continues until a player\n", + " only has 3 pieces remaining.\n", "Phase 3. The player with three pieces may move a piece to any empty\n", " location, e.g. `a1g7' moves the piece at a1 to location g7. The player\n", " with more than three pieces moves as in phase 2. The first player to\n", " reduce the opponent to two pieces wins.\n", "\n", + "Press `?' to display these instructions, and `q' to quit.\n", + "\n", "Press any key to continue...", NULL }; -/* +/* ************************** * Board and game creation + * ************************** */ + +/* + * Connect two points, with p1 in direction L1 of p2 */ +int +joinpts(const int L1, /*@null@*/ point *p1, /*@null@*/ point *p2) +{ + /* Check L1 % 2 == 0 to make sure L1 == NORTH,WEST,NE,NW */ + if (MINDIR <= L1 && L1 <= MAXDIR && L1 % 2 == 0) { + if (p1) { + p1->n[L1+1] = p2; + freepoint(p1); + } + if (p2) { + p2->n[L1] = p1; + freepoint(p2); + } + return 1; + } + return 0; +} +/* + * Join the north point n to the south point s + */ void -joinns(/*@null@*/ struct point *n, /*@null@*/ struct point *s) +joinns(/*@null@*/ point *n, /*@null@*/ point *s) { - if (n) { - n->n[SOUTH] = s; - freepoint(n); - } - if (s) { - s->n[NORTH] = n; - freepoint(s); - } + joinpts(NORTH, n, s); +} + +/* + * Join the west point w to the east point e + */ +void +joinwe(/*@null@*/ point *w, /*@null@*/ point *e) +{ + joinpts(WEST, w, e); } +/* + * Join the north east point ne to the south west point sw + */ void -joinwe(/*@null@*/ struct point *w, /*@null@*/ struct point *e) +joinnesw(/*@null@*/ point *ne, /*@null@*/ point *sw) { - if (e) { - e->n[WEST] = w; - freepoint(e); + joinpts(NE, ne, sw); +} + +/* + * Join the north west point nw to the south east point se + */ +void +joinnwse(/*@null@*/ point *nw, /*@null@*/ point *se) +{ + joinpts(NW, nw, se); +} + +/* + * Free the memory allocated to a point if it has no neighbours + */ +void +freepoint(point *p) +{ + int i, joined = 0; + for (i = 0; i <= MAXDIR; i++) { + if (p->n[i]) { + joined = 1; + break; + } } - if (w) { - w->n[EAST] = e; - freepoint(w); + if (!joined) { + /* p has no neighbours */ + /* FIXME: Why does this crash with modified chunk-pointer? */ + /* free(p); */ } } -void freepoint(struct point *p) +/* + * Converts a direction to a neighbour index + */ +int +dirtoindex(const char *dir) { - if (!p->n[NORTH] && !p->n[SOUTH] && !p->n[EAST] && !p->n[WEST]) { - /* free(p); */ + const struct strkey *ptr; + /* dirc is bound by a sentinal with null key */ + for (ptr = dirc; ptr->k; ptr++) { + if (strncmp(ptr->k, dir, 2) == 0) { + return ptr->v; + } } + return -1; } -struct point -makepoint(const char v, /*@null@*/ struct point *n, /*@null@*/ struct point *s, - /*@null@*/ struct point *e, /*@null@*/ struct point *w) +/* + * Given a label and four main neighbours, create a point + */ +point +makepoint(const char v, /*@null@*/ point *n, /*@null@*/ point *s, + /*@null@*/ point *e, /*@null@*/ point *w) { - struct point temp; + point temp; updatepoint(&temp, v, n, s, e, w); return temp; } +/* + * Update the value and four main neighbours of a point p + */ void -updatepoint(struct point *p, const char v, struct point *n, struct point *s, - struct point *e, struct point *w) +updatepoint(point *p, const char v, point *n, point *s, + point *w, point *e) { p->v = v; joinns(p, s); @@ -206,33 +321,97 @@ updatepoint(struct point *p, const char v, struct point *n, struct point *s, joinwe(p, e); } +/* + * Create the board for Three Man Morris + */ void -initboard(nboard b) +init3board(nboard b) { int r = 0; /* Initialize everything to an empty grid */ for (r = 0; r < 3; r++) { - updatepoint(&b[r][0], EMPTY, NULL, NULL, NULL, NULL); /* Top mid */ - updatepoint(&b[r][1], EMPTY, NULL, NULL, &b[r][0], NULL); /* Top right */ - updatepoint(&b[r][2], EMPTY, &b[r][1], NULL, NULL, NULL); /* Mid right */ - updatepoint(&b[r][3], EMPTY, &b[r][2], NULL, NULL, NULL); /* Bot right */ - updatepoint(&b[r][4], EMPTY, NULL, NULL, &b[r][3], NULL); /* Bot mid */ - updatepoint(&b[r][5], EMPTY, NULL, NULL, &b[r][4], NULL); /* Bot left */ - updatepoint(&b[r][6], EMPTY, NULL, &b[r][5], NULL, NULL); /* Mid left */ - updatepoint(&b[r][7], EMPTY, NULL, &b[r][6], &b[r][0], NULL); /* Top left */ + updatepoint(b[r][0], EMPTY, NULL, NULL, NULL, NULL); + updatepoint(b[r][1], EMPTY, NULL, NULL, b[r][0], NULL); + updatepoint(b[r][2], EMPTY, NULL, NULL, b[r][1], NULL); + b[r][3] = NULL; } for (r = 0; r < 2; r++) { - joinns(&b[r][0], &b[r+1][0]); /* Top vertical line */ - joinns(&b[r+1][4], &b[r][4]); /* Bot vertical line */ - joinwe(&b[r][6], &b[r+1][6]); /* Left horiz line */ - joinwe(&b[r+1][2], &b[r][2]); /* Right horiz line */ + joinns(b[r][0], b[r+1][0]); + joinns(b[r][1], b[r+1][1]); + joinns(b[r][2], b[r+1][2]); } } +/* + * Create the board for Nine Man Morris + */ void -initgame(struct game *g) +init9board(nboard b) { - initboard(g->board); + int r = 0; + /* Initialize everything to an empty grid */ + for (r = 0; r < 3; r++) { + updatepoint(b[r][0], EMPTY, NULL, NULL, b[r][7], NULL); /* Top mid */ + updatepoint(b[r][1], EMPTY, NULL, NULL, b[r][0], NULL); /* Top right */ + updatepoint(b[r][2], EMPTY, b[r][1], NULL, NULL, NULL); /* Mid right */ + updatepoint(b[r][3], EMPTY, b[r][2], NULL, NULL, NULL); /* Bot right */ + updatepoint(b[r][4], EMPTY, NULL, NULL, NULL, b[r][3]); /* Bot mid */ + updatepoint(b[r][5], EMPTY, NULL, NULL, NULL, b[r][4]); /* Bot left */ + updatepoint(b[r][6], EMPTY, NULL, b[r][5], NULL, NULL); /* Mid left */ + updatepoint(b[r][7], EMPTY, NULL, b[r][6], NULL, NULL); /* Top left */ + b[r][8] = NULL; + } + for (r = 0; r < 2; r++) { + printw("\n\n%d\n\n\n", r); + joinns(b[r][0], b[r+1][0]); /* Top vertical line */ + joinns(b[r+1][4], b[r][4]); /* Bot vertical line */ + joinwe(b[r][6], b[r+1][6]); /* Left horiz line */ + joinwe(b[r+1][2], b[r][2]); /* Right horiz line */ + } +} + +/* + * Create the board for Twelve Man Morris + */ +void +init12board(nboard b) +{ + int r = 0; + init9board(b); + for (r = 0; r < 2; r++) { + /* Top right */ + joinnesw(b[r][1], b[r+1][1]); + /* Bottom right */ + joinnwse(b[r+1][3], b[r][3]); + /* Bottom left */ + joinnesw(b[r+1][5], b[r][5]); + /* Top left */ + joinnwse(b[r][7], b[r+1][7]); + } +} + +/* + * Initialize a game with the appropriate boards, reset scores, etc. + */ +void +initgame(game *g, const int type) +{ + switch (type) + { + case TMM: + init3board(g->board); + g->type = TMM; + break; + + case TWMM: + init12board(g->board); + g->type = TWMM; + break; + + default: + init9board(g->board); + g->type = NMM; + } /* We'll assume that, as in chess, black moves first */ g->state = BLACK; g->phase = 1; @@ -241,21 +420,25 @@ initgame(struct game *g) g->pieces[BLACK] = 0; } +/* + * Initialize the board and all of the windors in a scrgame + */ void -initall(struct scrgame *sg) +initall(scrgame *sg, const int type) { - initgame(sg->game); - sg->score_w = create_scorebox(sg->game); - sg->board_w = create_board(sg->game); - sg->msg_w = create_msgbox(); + initgame(sg->game, type); + full_redraw(sg); } -/* +/* ******************** * Drawing functions - */ + * ******************** */ -WINDOW -*create_scorebox(const struct game *g) +/* + * Create the scorebox / prompt window + */ +WINDOW * +create_scorebox(const game *g) { WINDOW *local_win; @@ -266,14 +449,28 @@ WINDOW /* Uncomment the following for the curses box, but I prefer the "rustic" boxes drawn with pipes, hyphens and plusses */ /* box(local_win, 0, 0); */ - mvwprintw(local_win, 1, 2, "Nine Man Morris"); + switch (g->type) + { + case TMM: + mvwprintw(local_win, 1, 2, "Three Man Morris"); + break; + case TWMM: + mvwprintw(local_win, 1, 2, "Twelve Man Morris"); + break; + default: + mvwprintw(local_win, 1, 2, "Nine Man Morris"); + break; + } /* update will call a refresh for us */ update_scorebox(local_win, g); return local_win; } +/* + * Update the score, phase, and current player + */ void -update_scorebox(WINDOW *w, const struct game *g) +update_scorebox(WINDOW *w, const game *g) { mvwprintw(w, 3, 2, "Phase %d", g->phase); mvwprintw(w, 4, 2, "White pieces: %d", g->pieces[WHITE]); @@ -286,8 +483,60 @@ update_scorebox(WINDOW *w, const struct game *g) wrefresh(w); } -WINDOW -*create_board(const struct game *g) +/* + * Determine the type of board to draw and draw it + */ +WINDOW * +create_board(const game *g) +{ + switch (g->type) + { + case TMM: + return create_3board(g); + + case TWMM: + return create_12board(g); + + default: + return create_9board(g); + } +} + +/* + * Draw board for Three Man Morris + */ +WINDOW * +create_3board(const game *g) +{ + WINDOW *local_win; + int r = 0; + char c = 'a'; + + local_win = newwin(13 + legendsep, 21 + legendsep, brdrow, brdcol); + + /* Draw the legend */ + for (r = 0; r < 3; r++) { + mvwprintw(local_win, 6*r, 0, "%d", 3-r); + } + c = 'a'; + for (r = 0; r < 21; r += 9) { + mvwaddch(local_win, 14, legendsep + r, c++); + } + /* Draw the board lines */ + for (r = 0; r < 3; r++) { + mvwhline(local_win, 6*r, legendsep, '-', 18); + mvwvline(local_win, 1, legendsep + 9*r, '|', 11); + } + + update_3board(local_win, g); + return local_win; +} + +/* + * Draw board for Nine Man Morris + */ +WINDOW * +create_9board(const game *g) { WINDOW *local_win; int r = 0; @@ -301,8 +550,7 @@ WINDOW } c = 'a'; for (r = 0; r < 21; r += 3) { - mvwaddch(local_win, 14, legendsep + r, c); - c++; + mvwaddch(local_win, 14, legendsep + r, c++); } /* Draw the board lines */ @@ -325,33 +573,100 @@ WINDOW /* RH crossline */ mvwhline(local_win, 6, legendsep + 13, '-', 5); - update_board(local_win, g); + update_9board(local_win, g); return local_win; } +/* + * Draw board for Twelve Man Morris + */ +WINDOW * +create_12board(const game *g) +{ + WINDOW *local_win; + int r = 0; + local_win = create_9board(g); + /* Draw the diagonal lines */ + for (r = 0; r < 2; r++) { + /* Top left */ + mvwaddch(local_win, 2*r + 1, legendsep + 3*r + 2, '\\'); + /* Top right */ + mvwaddch(local_win, 2*r + 1, legendsep + 16 - 3*r, '/'); + /* Bottom left */ + mvwaddch(local_win, 11 - 2*r, legendsep + 3*r + 2, '/'); + /* Bottom right */ + mvwaddch(local_win, 11 - 2*r, legendsep + 16 - 3*r, '\\'); + } + wrefresh(local_win); + return local_win; +} + +/* + * Select the correct function to redraw points + */ +void +update_board(WINDOW *w, const game *g) +{ + switch (g->type) + { + case TMM: + update_3board(w, g); + break; + + case TWMM: + case NMM: + default: + update_9board(w, g); + } +} + +/* + * Redraw points for Three Man Morris + */ void -update_board(WINDOW *w, const struct game *g) +update_3board(WINDOW *w, const game *g) { int r = 0; for (r = 0; r < 3; r++) { - mvwaddch(w, 2*r, legendsep + 9, g->board[r][0].v); - mvwaddch(w, 2*r, legendsep + 18 - 3*r, g->board[r][1].v); - mvwaddch(w, 6, legendsep + 18 - 3*r, g->board[r][2].v); - mvwaddch(w, 12-2*r, legendsep + 18 - 3*r, g->board[r][3].v); - mvwaddch(w, 12-2*r, legendsep + 9, g->board[r][4].v); - mvwaddch(w, 12-2*r, legendsep + 3*r, g->board[r][5].v); - mvwaddch(w, 6, legendsep + 3*r, g->board[r][6].v); - mvwaddch(w, 2*r, legendsep + 3*r, g->board[r][7].v); + mvwaddch(w, 6*r, legendsep, g->board[r][0]->v); + mvwaddch(w, 6*r, legendsep + 9, g->board[r][1]->v); + mvwaddch(w, 6*r, legendsep + 18, g->board[r][2]->v); } wrefresh(w); } -WINDOW -*create_msgbox(void) +/* + * Redraw points for Nine and Twelve Man Morris + */ +void +update_9board(WINDOW *w, const game *g) +{ + int r = 0; + for (r = 0; r < 3; r++) { + mvwaddch(w, 2*r, legendsep + 9, g->board[r][0]->v); + mvwaddch(w, 2*r, legendsep + 18 - 3*r, g->board[r][1]->v); + mvwaddch(w, 6, legendsep + 18 - 3*r, g->board[r][2]->v); + mvwaddch(w, 12-2*r, legendsep + 18 - 3*r, g->board[r][3]->v); + mvwaddch(w, 12-2*r, legendsep + 9, g->board[r][4]->v); + mvwaddch(w, 12-2*r, legendsep + 3*r, g->board[r][5]->v); + mvwaddch(w, 6, legendsep + 3*r, g->board[r][6]->v); + mvwaddch(w, 2*r, legendsep + 3*r, g->board[r][7]->v); + } + wrefresh(w); +} + +/* + * Create window for displaying messages + */ +WINDOW * +create_msgbox(void) { return newwin(2, 80-msgcol, msgrow, msgcol); } +/* + * Change message displayed in window + */ void update_msgbox(WINDOW *w, const char *msg) { @@ -360,8 +675,72 @@ update_msgbox(WINDOW *w, const char *msg) wrefresh(w); } +char * +getgname(const game *g) +{ + switch (g->type) + { + case TMM: + return "tmm"; + + case TWMM: + return "twmm"; + + case NMM: + default: + return "nmm"; + } +} + +/* + * Completely redraw *everything* + */ +void +full_redraw(scrgame *sg) +{ + static char vers[25]; + static size_t vers_len; + static const char *helpstr = "? : help"; + static const size_t helplen = 8; + snprintf(vers, 25, "%s version %s", getgname(sg->game), VERSION); + vers_len = strnlen(vers, 25); + delwin(sg->board_w); + delwin(sg->score_w); + delwin(sg->msg_w); + clear(); + /* We want the ends of the version string and of + * helpstr to line up. */ + mvprintw(versrow, helpcol + helplen - vers_len, vers); + mvprintw(helprow, helpcol, helpstr); + refresh(); + sg->board_w = create_board(sg->game); + sg->score_w = create_scorebox(sg->game); + sg->msg_w = create_msgbox(); +} + +/* + * Print instructions to screen and prompt to continue + */ +void +printinstrs(scrgame *sg) +{ + /* TODO: Factor out the refresh-everything part */ + const char *const *instr; + clear(); + for (instr = instructions; *instr; instr++) { + printw("%s", *instr); + } + refresh(); + getch(); + full_redraw(sg); +} + +/* + * Announce winner, prompt user to play again, non-zero if user wants + * to. + */ int -gameend(struct scrgame *sg) +gameend(const scrgame *sg) { char c; if (sg->game->pieces[WHITE] < sg->game->pieces[BLACK]) { @@ -379,64 +758,67 @@ gameend(struct scrgame *sg) } /* - * Functions concerning game logic + * Properly end curses and exit */ +__dead void +quit(void) +{ + refresh(); + endwin(); + exit(0); +} +/* ******************************** + * Functions concerning game logic + * ******************************** */ + +/* + * Is the point p in a mill? + */ int -inmill(const struct point *p) +inmill(const point *p) { if (p->v == BLACKC || p->v == WHITEC) { - int steps = 0; - int hc = 1; - int vc = 1; /* horizontal count, vertical count */ - struct point *np = p->n[NORTH]; - struct point *sp = p->n[SOUTH]; - struct point *ep = p->n[EAST]; - struct point *wp = p->n[WEST]; - - /* We make up to three steps in each direction to account for when - we're on the edge */ - for (steps = 0; steps < 3; steps++) { - if (np && np->v == p->v) { - vc++; - np = np->n[NORTH]; - } - if (sp && sp->v == p->v) { - vc++; - sp = sp->n[SOUTH]; - } - if (ep && ep->v == p->v) { - hc++; - ep = ep->n[EAST]; + point *neighbours[8]; + int counts[4] = {1, 1, 1, 1}; + int i, dir = MINDIR; + for (dir = MINDIR; dir <= MAXDIR; dir++) { + neighbours[dir] = p->n[dir]; + } + + /* We should walk at most 2 steps in any direction */ + for (i = 0; i < 2; i++) { + for (dir = MINDIR; dir <= MAXDIR; dir++) { + if (neighbours[dir] && neighbours[dir]->v == p->v) { + /* Makes use of the fact that directions along the same axis + * are 1 apart (e.g. SOUTH = NORTH + 1 = 1), and that + * division truncates toward zero */ + counts[dir / 2]++; + neighbours[dir] = neighbours[dir]->n[dir]; + } } - if (wp && wp->v == p->v) { - hc++; - wp = wp->n[WEST]; + } + /* If we have three in a row along any axis, we're in a mill */ + for (i = 0; i < 4; i++) { + if (counts[i] == 3) { + return 1; } } - return (hc == 3 || vc == 3); } /* If we're EMPTY, we're clearly not in a mill */ return 0; } -int -adjacent(const struct point *p1, const struct point *p2) -{ - return (p2 == p1->n[EAST] || p2 == p1->n[WEST] || - p2 == p1->n[NORTH] || p2 == p1->n[SOUTH]); -} - -char -statechar(const struct game *g) -{ - return g->state == WHITE ? WHITEC : BLACKC; -} - -struct point* -mill_handler(struct scrgame *sg, char *move, int count) +/* + * Handle a mill by dealing with the removal of an opponent's piece. + * Prompts the user for a piece to remove, checks its legality, and + * removes it. Return the removed piece, recursing until a piece is + * removed. + */ +point * +mill_handler(scrgame *sg, char *move, int count) { - struct point *p = NULL; + point *p = NULL; if (count == 0) { update_msgbox(sg->msg_w, @@ -462,7 +844,7 @@ mill_handler(struct scrgame *sg, char *move, int count) } for (r = 0; r < 3; r++) { for (c = 0; c < 7; c++) { - if (sg->game->board[r][c].v == opp && !inmill(&sg->game->board[r][c])) { + if (sg->game->board[r][c]->v == opp && !inmill(sg->game->board[r][c])) { update_msgbox(sg->msg_w, "It is possible to remove a piece not in a mill; do so."); return mill_handler(sg, move, 1); @@ -492,134 +874,239 @@ mill_handler(struct scrgame *sg, char *move, int count) } } -struct point* -placepiece(struct scrgame *sg, const char *coords) +/* + * Return the char corresponding to the current state + */ +char +statechar(const game *g) { - struct point *p = NULL; - if ((p = getpoint(sg->game, coords))) { - if (p->v == EMPTY) { - p->v = statechar(sg->game); - return p; - } else { - update_msgbox(sg->msg_w, "That location is already occupied, please try again."); - return NULL; - } - } else { - update_msgbox(sg->msg_w, "Invalid coordinates. Please try again."); - return NULL; - } + return g->state == WHITE ? WHITEC : BLACKC; } +/* + * Checks the validity of coordinates for a game + * Assumes coords are lower case and of length 2 + */ int -checkdir(const char *dir) +validcoords(const game *g, const char *coords) { - if (isalpha(dir[0])) { - char d; - d = tolower(dir[0]); - return (d == NORTHC || d == SOUTHC || d == EASTC || d == WESTC); + switch (g->type) + { + case TMM: + return valid3coords(coords); + + case NMM: + case TWMM: + default: + return valid9coords(coords); } - return 0; } +/* + * Checks the validity of coordinates for Three Man Morris + * Assumes coords are lower case and of length 2 + */ int -validcoords(const char *coords) +valid3coords(const char *coords) +{ + return ('a' <= coords[0] && coords[0] <= 'c' && + '1' <= coords[1] && coords[1] <= '3'); +} + +/* + * Checks the validity of coordinates for Nine, Twelve Man Morris + * Assumes coords are lower case and of length 2 + */ +int +valid9coords(const char *coords) { - /* TODO: Why doesn't this work? */ - if ((sizeof(coords) / sizeof(*coords)) < 2) { - return 0; - } if (coords[0] < 'a' || 'g' < coords[0] || coords[1] < '0' || '7' < coords[1]) { return 0; } switch (coords[0]) - { - case 'a': case 'g': - return (coords[1] == '1' || coords[1] == '4' || coords[1] == '7'); + { + case 'a': case 'g': + return (coords[1] == '1' || coords[1] == '4' || coords[1] == '7'); - case 'b': case 'f': - return (coords[1] == '2' || coords[1] == '4' || coords[1] == '6'); + case 'b': case 'f': + return (coords[1] == '2' || coords[1] == '4' || coords[1] == '6'); - case 'c': case 'e': - return ('3' <= coords[1] && coords[1] <= '5'); + case 'c': case 'e': + return ('3' <= coords[1] && coords[1] <= '5'); - case 'd': - return (coords[1] != '4'); + case 'd': + return (coords[1] != '4'); - default: - /* UNREACHABLE */ - return 0; - } + default: + /* UNREACHABLE */ + return 0; + } } -char -*getmove(struct scrgame *sg, char *move, int l) +/* + * Checks the validity of a direction + */ +int +checkdir(const char *dir) { - int length = 0; - int ch = 0; - int c = 0; - if (l < 3 || 5 < l) { - if (sg->game->phase == 1) { - /* Of form d3 */ - length = 3; - } else if (sg->game->phase == 2) { - /* Of form d3s */ - length = 4; - } else if (sg->game->phase == 3) { - if (sg->game->pieces[(int) sg->game->state] == 3) { - /* Of form d3a1 */ - length = 5; - } else { - /* Of form d3s */ - length = 4; - } + if (isalpha(dir[0])) { + char d[3]; + int i; + for (i = 0; i < 2; i++) { + d[i] = tolower(dir[0]); } - } else { - length = l; + d[2] = '\0'; + i = dirtoindex(d); + return (MINDIR <= i && i <= MAXDIR); } + return 0; +} + +/* + * Prompt user for input, handling backspace, return. + * q twice at start of line: quit() + * ? at start of line: printinstrs() + * Otherwise, read up to length characters from sg->scorer_w into inp + * Assume than inp always has room for at least '\0' + * Recurse until no errors, return pointer to inp. + */ +char * +getinput(scrgame *sg, char *inp, const int length) +{ + int l, ch, i, quitc; + quitc = 0; /* Clear the prompt area */ mvwaddstr(sg->score_w, promptrow, promptcol, " |"); - for (c = 0; c < length - 1; c++) { - /* Store into int to make sure we don't miss out on special - ncurses keys, such as KEY_BACKSPACE */ - ch = mvwgetch(sg->score_w, promptrow, promptcol + c); - if (ch == '\b' || ch == KEY_BACKSPACE) { - /* We don't want users erasing the whole screen */ - if (c > 0) { - mvwaddch(sg->score_w, promptrow, promptcol + c, ' '); - c--; + wrefresh(sg->score_w); + for (l = 0; l < length - 1; l++) { + ch = mvwgetch(sg->score_w, promptrow, promptcol + l); + if (ch == 12) { + /* We were given a ^L */ + full_redraw(sg); + update_msgbox(sg->msg_w, ""); + for (i = 0; i < l; i++) { + mvwaddch(sg->score_w, promptrow, promptcol + i, inp[i]); + } + wrefresh(sg->score_w); + l--; + continue; + } + if (ch == '\b' || ch == KEY_BACKSPACE || + ch == KEY_DC || ch == 127) { + /* We need to retake the current character; l++ will increase + * it, so counteract that with an l-- to remove the BACKSPACE. */ + l--; + if (l >= 0) { + /* Erase the input char from the screen */ + mvwaddch(sg->score_w, promptrow, promptcol + l, ' '); + /* And another l-- so that we overwrite the input char from + * inp on the next iteration of this loop. */ + l--; } else { - mvwaddch(sg->score_w, promptrow, promptcol, ' '); + /* We're at the start of the line, so there was no input char + * to erase */ } + wrefresh(sg->score_w); + update_msgbox(sg->msg_w, ""); continue; - } else if (isascii(ch)) { - if (ch == '\n') { - move[c] = ch; - c++; - break; + } else if (isalnum(ch)) { + if (ch == 'q' && l == 0) { + if (quitc) { + quit(); + } else { + quitc = 1; + update_msgbox(sg->msg_w, "Enter 'q' again to quit"); + mvwaddch(sg->score_w, promptrow, promptcol, ' '); + l--; + } + } else if (ch == '?' && l == 0) { + printinstrs(sg); + mvwaddch(sg->score_w, promptrow, promptcol, ' '); + l--; } else { - move[c] = ch; - mvwaddch(sg->score_w, promptrow, promptcol + c, ch); + inp[l] = (char) ch; + mvwaddch(sg->score_w, promptrow, promptcol + l, ch); } + wrefresh(sg->score_w); + } else if (ch == '\n') { + inp[l] = '\0'; + break; } else { update_msgbox(sg->msg_w, "Unexpected non-ASCII input"); - return getmove(sg, move, length); + return getinput(sg, inp, length); } } - move[0] = tolower(move[0]); - move[2] = tolower(move[2]); - if (!validcoords(move) || - (length == 5 && !validcoords(&move[2]))) { + /* l <= length-1 */ + inp[l] = '\0'; + return inp; +} + +/* + * Prompts the user for a move with getinput, sanitizes it, and makes + * the move. It stores the move in *move, you can specify the maximum + * length l of the move to take, this should be at most the length of + * move. If mill is less than three or more than five, move should + * hold at least 5 characters. Returns pointer to the move. + */ +char * +getmove(scrgame *sg, char *move, int l) +{ + int length, pieces, index, twmm = 0; + /* Do we only have 3 pieces, i.e. are we in skipping mode? */ + pieces = sg->game->pieces[(int) sg->game->state]; + if (l < 3 || 5 < l) { + if (sg->game->type == TWMM) { + /* We need to add one to our length to make up for the + * possibility of ne/nw/se/sw over n/e/s/w */ + twmm = 1; + } + switch (sg->game->phase) + { + case 1: + /* In piece placing mode */ + length = 3; + break; + + case 2: + /* Of form d3s or d3sw */ + length = 4 + twmm; + break; + + case 3: + if (pieces == 3) { + /* Of form d3a1 */ + length = 5; + } else { + /* Of form d3s or d3sw */ + length = 4 + twmm; + } + break; + + default: + length = 0; + /* NOTREACHED */ + } + } else { + length = l; + } + move = getinput(sg, move, length); + move = lower(move); + if (!validcoords(sg->game, move) || + /* Check the length to make sure we're not in mill mode */ + (pieces == 3 && length != 3 && !validcoords(sg->game, &move[2]))) { update_msgbox(sg->msg_w, "Invalid coordinates"); return getmove(sg, move, length); } - if (length == 4) { - if (!(move[2] == NORTHC || move[2] == SOUTHC || - move[2] == EASTC || move[2] == WESTC)) { + /* If we'e in piece sliding stage. The length check makes sure we're + not in mill mode. */ + if (sg->game->phase != 1 && pieces != 3 && length != 3) { + index = dirtoindex(&move[2]); + if (index == -1) { update_msgbox(sg->msg_w, "Invalid direction"); return getmove(sg, move, length); } else if (!(getpoint(sg->game, /* We already checked that move[2] is safe */ - move)->n[dirtoindex(move[2])])) { + move)->n[index])) { update_msgbox(sg->msg_w, "Impossible to move in that direction"); return getmove(sg, move, length); } @@ -628,38 +1115,80 @@ char return move; } -struct point -*getpoint(struct game *g, const char *coords) +/* + * Given a coordinate array, retrieve the corresponding point by + * calling the appropriate function. + */ +point * +getpoint(const game *g, const char *coords) +{ + switch (g->type) + { + case TMM: + return get3point(g, coords); + + case NMM: + case TWMM: + default: + return get9point(g, coords); + } +} + +/* + * Given a coordinate array, retrieve the corresponding point + */ +point * +get3point(const game *g, const char *coords) +{ + int r; + if (validcoords(g, coords)) { + r = 2 + '1' - coords[1]; + /* = 2 - (coords[0] - '1'); */ + return g->board[r][coords[0] - 'a']; + } + return NULL; +} + + +/* + * Given a coordinate array, retrieve the corresponding point + */ +point * +get9point(const game *g, const char *coords) { int r, c = 0; - if (validcoords(coords)) { + if (validcoords(g, coords)) { switch (coords[1]) - { - case '4': - if (coords[0] < 'd') { - r = coords[0] - 'a'; - c = 6; - } else { - r = 'g' - coords[0]; - c = 2; - } - break; + { + case '4': + if (coords[0] < 'd') { + r = coords[0] - 'a'; + c = 6; + } else { + r = 'g' - coords[0]; + c = 2; + } + break; - case '1': case '7': - r = 0; - break; + case '1': case '7': + r = 0; + break; - case '2': case '6': - r = 1; - break; + case '2': case '6': + r = 1; + break; - case '3': case '5': - r = 2; - break; + case '3': case '5': + r = 2; + break; - default: - errx(1, "Reached an unreachable branch in getpoint"); - } + default: + /* NOTREACHED */ + endwin(); + errx(1, "Reached an unreachable branch in getpoint"); + r = 0; /* Shut up gcc complaining that r may be + used unitialized */ + } if (coords[1] != '4') { if (coords[0] < 'd') { c = coords[1] < '4' ? 5 : 7; @@ -669,18 +1198,100 @@ struct point c = coords[1] < '4' ? 3 : 1; } } - return &(g->board[r][c]); + return g->board[r][c]; + } + return NULL; +} + +/* + * Place a piece corresponding to the current player at the coordinates coords. + */ +point * +placepiece(const scrgame *sg, const char *coords) +{ + point *p = NULL; + if ((p = getpoint(sg->game, coords))) { + if (p->v == EMPTY) { + p->v = statechar(sg->game); + return p; + } else { + update_msgbox(sg->msg_w, "That location is already occupied, please try again."); + return NULL; + } + } else { + update_msgbox(sg->msg_w, "Invalid coordinates. Please try again."); + return NULL; + } +} + + +/* + * Move a piece from p in direction dir + */ +point * +movepiece(point *p, const char *dir) +{ + int index; + index = dirtoindex(dir); + if (p->n[index] && p->n[index]->v == EMPTY) { + p->n[index]->v = p->v; + p->v = EMPTY; + return p->n[index]; } return NULL; } +/* + * Move point p to the given position + */ +point * +jumppiece(point *p, game *g, const char *position) +{ + point *ptr = getpoint(g, position); + if (ptr && ptr->v == EMPTY) { + ptr->v = p->v; + p->v = EMPTY; + return ptr; + } + return NULL; +} -struct point* -phaseone(struct scrgame *sg) +/* + * Remove a point by setting it to empty and decrementing the piece + * counts + */ +point * +removepiece(game *g, point *p) { - struct point *p = NULL; + if (p && g) { + /* We let stupid people remove their own pieces */ + if (p->v == BLACKC) { + g->pieces[BLACK]--; + } else if (p->v == WHITE) { + g->pieces[WHITE]--; + } else { + errno = ENOTSUP; + endwin(); + errx(ENOTSUP, "Tried removing EMPTY. This should NEVER happen."); + } + p->v = EMPTY; + return p; + } + return NULL; +} + +/* + * Phase one of the game + * Returns NULL if one player is guaranteed to have less than 3 pieces + * at the end of the phase, thus loosing. + */ +point * +phaseone(scrgame *sg) +{ + point *p = NULL; char coords[4]; - for (; sg->game->totalpieces < PIECECOUNT; sg->game->totalpieces++) { + int maxrem = 0; + for (; sg->game->totalpieces < 2 * sg->game->type; sg->game->totalpieces++) { getmove(sg, coords, 0); if (!(p = placepiece(sg, coords))) { sg->game->totalpieces--; @@ -699,16 +1310,30 @@ phaseone(struct scrgame *sg) update_scorebox(sg->score_w, sg->game); update_board(sg->board_w, sg->game); update_msgbox(sg->msg_w, ""); + maxrem = (2 * sg->game->type - sg->game->totalpieces) / 2; + if (sg->game->pieces[0] + maxrem < 3 || + sg->game->pieces[1] + maxrem < 3) { + /* It's impossible for a player to place more than 3 pieces, abort */ + return NULL; + } + } + if (sg->game->type == TMM) { + /* Three Man Morris doesn't have a phase 2 */ + sg->game->phase = 3; + } else { + sg->game->phase = 2; } - sg->game->phase = 2; update_scorebox(sg->score_w, sg->game); return p; } -struct point* -phasetwo(struct scrgame *sg) +/* + * Phases two and three of the game + */ +point * +phasetwothree(scrgame *sg) { - struct point *p = NULL; + point *p = NULL; char coords[6]; while (sg->game->pieces[WHITE] >= 3 && sg->game->pieces[BLACK] >= 3) { getmove(sg, coords, 0); @@ -726,7 +1351,7 @@ phasetwo(struct scrgame *sg) "That location is already occupied. Please try again"); continue; } - } else if (!(p = movepiece(p, coords[2]))) { + } else if (!(p = movepiece(p, &coords[2]))) { update_msgbox(sg->msg_w, "That location is already occupied. Please try again"); continue; @@ -750,115 +1375,86 @@ phasetwo(struct scrgame *sg) return p; } -int -dirtoindex(const char dir) -{ - switch (dir) - { - case NORTHC: - return NORTH; - - case SOUTHC: - return SOUTH; - - case EASTC: - return EAST; - - case WESTC: - return WEST; - - default: - return -1; - } -} - -struct point* -movepiece(struct point *p, const char dir) +/* + * Applies tolower() to every element of a null-terminated string + * Will do bad things(TM) if the string is not null-terminated + */ +char* +lower(char *s) { - int index; - index = dirtoindex(dir); - if (p->n[index] && p->n[index]->v == EMPTY) { - p->n[index]->v = p->v; - p->v = EMPTY; - return p->n[index]; + char *c = s; + for (c = s; *c; c++) { + *c = tolower(*c); } - return NULL; + return s; } -struct point* -jumppiece(struct point *p, struct game *g, const char *position) +/* + * The Big Cheese + */ +int +main(int argc, char *argv[]) { - struct point *ptr = getpoint(g, position); - if (ptr && ptr->v == EMPTY) { - ptr->v = p->v; - p->v = EMPTY; - return ptr; + scrgame *sg; + int c; + char *bn = basename(argv[0]); + if (!bn || errno) { + /* basename can return a NULL pointer, causing a segfault on + strncmp below */ + errx(errno, "Something went wrong in determining the %s", + "filename by which nmm was called."); } - return NULL; -} - -struct point* -removepiece(struct game *g, struct point *p) -{ - if (p && g) { - /* We let stupid people remove their own pieces */ - if (p->v == BLACKC) { - g->pieces[BLACK]--; - } else if (p->v == WHITE) { - g->pieces[WHITE]--; + if (argc != 1) { + errx(EINVAL, "%s doesn't take any arguments.", bn); + } + if ((sg = malloc(sizeof(*sg)))) { + if ((sg->game = malloc(sizeof(*sg->game)))) { + point *board; + if ((board = calloc(ROWS * COLS, sizeof(*board)))) { + int r; + for (r = 0; r < ROWS; r++) { + for (c = 0; c < COLS; c++) { + sg->game->board[r][c] = board++; + } + } + } else { + errx(errno, "Unable to allocate memory for the board"); + } } else { - errno = ENOTSUP; - errx(ENOTSUP, "Tried removing EMPTY. This should NEVER happen."); + errx(errno, "Unable to allocate memory for the game"); } - p->v = EMPTY; - return p; + } else { + errx(errno, "Unable to allocate memory"); } - return NULL; -} - -int -main(void) -{ - struct scrgame *sg; - const char *const *instr; - int c; initscr(); + cbreak(); keypad(stdscr, TRUE); clear(); - refresh(); printw("Display instructions? (y/n) "); + refresh(); c = getch(); c = tolower(c); if (c == 'y') { - clear(); - for (instr = instructions; *instr; instr++) { - printw("%s", *instr); - } - refresh(); - getch(); + printinstrs(sg); } clear(); - refresh(); noecho(); - sg = malloc(sizeof(struct scrgame)); - if (sg) { - sg->game = malloc(sizeof(struct game)); - if (sg->game) { - mvprintw(25, 57, "nmm version %s", VERSION); - for (;;) { - initall(sg); - refresh(); - phaseone(sg); - phasetwo(sg); - if (!gameend(sg)) { - break; - } - } - } + refresh(); + if (strncmp("tmm", bn, 3) == 0) { + c = TMM; + } else if (strncmp("twmm", bn, 4) == 0) { + c = TWMM; } else { + c = NMM; + } + for (;;) { + initall(sg, c); refresh(); - endwin(); - errx(ENOMEM, "Unable to allocate enough memory."); + phaseone(sg); + phasetwothree(sg); + if (!gameend(sg)) { + break; + } } refresh(); endwin(); |