diff options
| author | Ryan Kavanagh <rak@debian.org> | 2013-03-01 16:56:47 -0500 | 
|---|---|---|
| committer | Ryan Kavanagh <rak@debian.org> | 2013-03-02 17:25:09 -0500 | 
| commit | d3528d9d1c0b85f481f6ff5d4faf00efb6a5745c (patch) | |
| tree | fea3eb916012b23f8761e933f8bc5af67f8d88a2 | |
Initial release0.1
Diffstat (limited to '')
| -rw-r--r-- | LICENSE | 24 | ||||
| -rw-r--r-- | Makefile | 11 | ||||
| -rw-r--r-- | nmm.6 | 83 | ||||
| -rw-r--r-- | nmm.c | 866 | 
4 files changed, 984 insertions, 0 deletions
| @@ -0,0 +1,24 @@ +Copyright (C) 2013 Ryan Kavanagh <rak@debian.org> +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + 1. Redistributions of source code must retain the above copyright +    notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright +    notice, this list of conditions and the following disclaimer in the +    documentation and/or other materials provided with the distribution. + 3. The name of the author may not be used to endorse or promote products +    derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, +INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY +AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL +THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; +OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR +OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF +ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
\ No newline at end of file diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..1a86f43 --- /dev/null +++ b/Makefile @@ -0,0 +1,11 @@ +CFLAGS:=-Wall -Wextra -pedantic -Werror=format-security -fstack-protector-all $(CFLAGS) + +all: nmm + +nmm: nmm.c +	$(CC) $(CFLAGS) -lcurses -o $@ $< + +clean: +	rm nmm + +.PHONY: clean nmm @@ -0,0 +1,83 @@ +.\" Copyright (C) 2013 Ryan Kavanagh <rak@debian.org> +.\" All rights reserved. +.\" +.\" Redistribution and use in source and binary forms, with or without +.\" modification, are permitted provided that the following conditions +.\" are met: +.\" 1. Redistributions of source code must retain the above copyright +.\"    notice, this list of conditions and the following disclaimer. +.\" 2. Redistributions in binary form must reproduce the above copyright +.\"    notice, this list of conditions and the following disclaimer in the +.\"    documentation and/or other materials provided with the distribution. +.\" 3. The name of the author may not be used to endorse or promote products +.\"    derived from this software without specific prior written permission. +.\" +.\" THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, +.\" INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY +.\" AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL +.\" THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +.\" EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +.\" PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; +.\" OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +.\" WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR +.\" OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF +.\" ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +.Dd February 18 2013 +.Dt NMM 6 +.\" Bad bad bad, I know, the .Os field is supposed to specify the OS +.\" and its version, not the command and its version. +.Os nmm 0.1 +.Sh NAME +.Nm nmm +.Nd Nine Men's Morris +.Sh SYNOPSIS +.Nm nmm +.Sh DESCRIPTION +Nine Men's Morris is an ancient board game, alleged to have been +played by the Romans. Gameplay is similar to Tic-Tac-Toe, with users +placing pieces in attempt to form three in a row, except that players +can additionally move their pieces and remove their opponents pieces. +.Pp +The board consists of positions connected with dashed lines, initially +marked empty (E), but then filled with black (B) and white (W) pieces. +Players place, move, and remove pieces, and the winner is the first to +reduce the opponent to 2 pieces. Gameplay is split into three phases. +.Ss Phase 1 +Users alternatingly place a piece on the board, until each player has +placed 7 pieces. The location is selected by entering its coordinates, +e.g\&. +.Sq a1 +or +.Sq g7  . +A mill can be formed by aligning three pieces along a dashed line, at +which point an opponent's piece is removed. This piece is selected by +entering its coordinates. +.Pp +.Ss Phase 2 +Users alternatingly slide one of their pieces along dashed line to an +adjacent empty position. The piece is selected by entering its +coordinates and its direction is chosen from n s e w, for North, +South, East, West. For example, +.Sq a1n +moves the piece at a1 north, +.Sq g7s +moves the piece at g7 south. Mills are formed and pieces removed as in +phase 1.  Phase 2 continues until one player only has 3 pieces +remaining. +.Ss Phase 3 +The player with three pieces may move a piece to any empty +location, e.g. +.Sq a1g7 +moves the piece at a1 to location g7. The player with more than three +pieces moves as in Phase 2. The first player to reduce the opponent +to two pieces wins. +.Sh EXIT STATUS +.Ex +.Sh AUTHORS +.Sy nmm +is Copyright \(co 2013 +.An Ryan Kavanagh +.Aq Mt rak@debian.org +and is distributed under a BSD-style license. +.Sh BUGS +None known. @@ -0,0 +1,866 @@ +/* + * Copyright (C) 2013 Ryan Kavanagh <rak@debian.org> + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + *    notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + *    notice, this list of conditions and the following disclaimer in the + *    documentation and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + *    derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY + * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL + * THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include <ctype.h> +#include <curses.h> +#include <err.h> +#include <errno.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#define VERSION "0.1" + +#define EMPTY 'E' +#define WHITEC 'W' +#define BLACKC 'B' +#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 sbrow 3         /* score box */ +#define sbcol 37 +#define brdrow 2        /* board     */ +#define brdcol 4 +#define legendsep 3     /* distance board<->legend */ +#define msgrow 20       /* message box */ +#define msgcol 7 +#define promptcol 16 +#define promptrow 7 + +/* Board & game construction */ +struct point { +  char v; /* Value: what's currently here */ +  struct point* n[4]; /* Our neighbours, indexed by NORTH, SOUTH, EAST, WEST */ +}; + +typedef struct point nboard[3][8]; + +struct game { +  nboard board; +  int state; +  int pieces[2]; +  int totalpieces; +  int phase; +}; + +struct scrgame { +  struct game *game; +  WINDOW *score_w; +  WINDOW *board_w; +  WINDOW *msg_w; +}; + +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); + +const char *const instructions[] = { +  "The board consists of positions connected with dashed lines, initially\n", +  "marked empty (E), but then filled with black (B) and white (W) pieces.\n", +  "Players place, move, and remove pieces, and the winner is the first to\n", +  "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", +  "  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", +  "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 any key to continue...", +  NULL +}; + +/* + * Board and game creation + */ + +void +joinns(/*@null@*/ struct point *n, /*@null@*/ struct point *s) +{ +  if (n) { +    n->n[SOUTH] = s; +    freepoint(n); +  } +  if (s) { +    s->n[NORTH] = n; +    freepoint(s); +  } +} + +void +joinwe(/*@null@*/ struct point *w, /*@null@*/ struct point *e) +{ +  if (e) { +    e->n[WEST] = w; +    freepoint(e); +  } +  if (w) { +    w->n[EAST] = e; +    freepoint(w); +  } +} + +void freepoint(struct point *p) +{ +  if (!p->n[NORTH] && !p->n[SOUTH] && !p->n[EAST] && !p->n[WEST]) { +    /* free(p); */ +  } +} + +struct point +makepoint(const char v, /*@null@*/ struct point *n, /*@null@*/ struct point *s, +	  /*@null@*/ struct point *e, /*@null@*/ struct point *w) +{ +  struct point temp; +  updatepoint(&temp, v, n, s, e, w); +  return temp; +} + +void +updatepoint(struct point *p, const char v, struct point *n, struct point *s, +	    struct point *e, struct point *w) +{ +  p->v = v; +  joinns(p, s); +  joinns(n, p); +  joinwe(w, p); +  joinwe(p, e); +} + +void +initboard(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 */ +  } +  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  */ +  } +} + +void +initgame(struct game *g) +{ +  initboard(g->board); +  /* We'll assume that, as in chess, black moves first */ +  g->state = BLACK; +  g->phase = 1; +  g->totalpieces = 0; +  g->pieces[WHITE] = 0; +  g->pieces[BLACK] = 0; +} + +void +initall(struct scrgame *sg) +{ +  initgame(sg->game); +  sg->score_w = create_scorebox(sg->game); +  sg->board_w = create_board(sg->game); +  sg->msg_w = create_msgbox(); +} + +/* + * Drawing functions + */ + +WINDOW +*create_scorebox(const struct game *g) +{ +  WINDOW *local_win; + +  /* WINDOW *newwin(int nlines, int ncols, int begin_y, +     int begin_x); */ +  local_win = newwin(10, 27, sbrow, sbcol); +  wborder(local_win, '|', '|', '-', '-', '+', '+', '+', '+'); +  /* 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"); +  /* update will call a refresh for us */ +  update_scorebox(local_win, g); +  return local_win; +} + +void +update_scorebox(WINDOW *w, const struct game *g) +{ +  mvwprintw(w, 3, 2, "Phase %d", g->phase); +  mvwprintw(w, 4, 2, "White pieces: %d", g->pieces[WHITE]); +  mvwprintw(w, 5, 2, "Black pieces: %d", g->pieces[BLACK]); +  if (g->state == BLACK) { +    mvwprintw(w, 7, 2, "Black's move: "); +  } else { +    mvwprintw(w, 7, 2, "White's move: "); +  } +  wrefresh(w); +} + +WINDOW +*create_board(const struct 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 < 7; r++) { +    mvwprintw(local_win, 2*r, 0, "%d", 7-r); +  } +  c = 'a'; +  for (r = 0; r < 21; r += 3) { +    mvwaddch(local_win, 14, legendsep + r, c); +    c++; +  } + +  /* Draw the board lines */ +  for (r = 0; r < 3; r++) { +    /* Top line */ +    mvwhline(local_win, 2*r, legendsep + 3*r, '-', 18 - 6*r); +    /* Left line */ +    mvwvline(local_win, 2*r, legendsep + 3*r, '|', 13 - 4*r); +    /* Bottom line */ +    mvwhline(local_win, 12 - 2*r, legendsep + 3*r, '-', 18 - 6*r); +    /* Right line */ +    mvwvline(local_win, 2*r, 18 + legendsep - 3*r, '|', 13 - 4*r); +  } +  /* TV crossline */ +  mvwvline(local_win, 1, legendsep + 9, '|', 3); +  /* LH crossline */ +  mvwhline(local_win, 6, legendsep + 1, '-', 5); +  /* BV crossline */ +  mvwvline(local_win, 9, legendsep + 9, '|', 3); +  /* RH crossline */ +  mvwhline(local_win, 6, legendsep + 13, '-', 5); + +  update_board(local_win, g); +  return local_win; +} + +void +update_board(WINDOW *w, const struct 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); +} + +WINDOW +*create_msgbox(void) +{ +  return newwin(2, 80-msgcol, msgrow, msgcol); +} + +void +update_msgbox(WINDOW *w, const char *msg) +{ +  werase(w); +  mvwaddstr(w, 0, 0, msg); +  wrefresh(w); +} + +int +gameend(struct scrgame *sg) +{ +  char c; +  if (sg->game->pieces[WHITE] < sg->game->pieces[BLACK]) { +    update_msgbox(sg->msg_w, +		  "Black wins! Play again?"); +  } else { +    update_msgbox(sg->msg_w, +		  "White wins! Play again?"); +  } +  mvwprintw(sg->score_w, promptrow, 2, "Play again?:     "); +  wrefresh(sg->score_w); +  c = mvwgetch(sg->score_w, promptrow, promptcol); +  c = tolower(c); +  return (c == 'y'); +} + +/* + * Functions concerning game logic + */ + +int +inmill(const struct 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]; +      } +      if (wp && wp->v == p->v) { +	hc++; +	wp = wp->n[WEST]; +      } +    } +    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) +{ +  struct point *p = NULL; + +  if (count == 0) { +    update_msgbox(sg->msg_w, +		  "You've formed a mill, enter opponent piece to remove."); +  } +  getmove(sg, move, 3); +  if ((p = getpoint(sg->game, move))) { +    if (p->v != statechar(sg->game) && p->v != EMPTY) { +      if (inmill(p)) { +	/* We can only break an opponent's mill if there are no other +	   pieces to remove. We could keep a list of each player's +	   pieces, but this implies the headache of always updating +	   it. Alternatively, we can just iterate through at most 21 +	   points, until we find an opponent piece not in a mill, or +	   nothing. */ +	int r = 0; +	int c = 0; +	int opp = 0; +	if (sg->game->state == WHITE) { +	  opp = BLACKC; +	} else { +	  opp = WHITEC; +	} +	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])) { +	      update_msgbox(sg->msg_w, +			    "It is possible to remove a piece not in a mill; do so."); +	      return mill_handler(sg, move, 1); +	    } +	  } +	} +	/* By this point, we've iterated through all of the opponent's +	   pieces without encountering one not in a mill. Allow the +	   desired piece to be removed. */ +      } +      p->v = EMPTY; +      /* Decrement the opponent's pieces */ +      sg->game->pieces[sg->game->state ^ BLACK]--; +      return p; +    } else if (p->v == EMPTY) { +      update_msgbox(sg->msg_w, +		    "You tried clearing an empty position. Please try again."); +      return mill_handler(sg, move, 1); +    } else { +      update_msgbox(sg->msg_w, +		    "You tried removing your own piece. Please try again."); +      return mill_handler(sg, move, 1); +    } +  } else { +    update_msgbox(sg->msg_w, "Invalid coordinates. Please try again."); +    return mill_handler(sg, move, 1); +  } +} + +struct point* +placepiece(struct scrgame *sg, const char *coords) +{ +  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; +  } +} + +int +checkdir(const char *dir) +{ +  if (isalpha(dir[0])) { +    char d; +    d = tolower(dir[0]); +    return (d == NORTHC || d == SOUTHC || d == EASTC || d == WESTC); +  } +  return 0; +} + +int +validcoords(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 '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 'd': +      return (coords[1] != '4'); + +    default: +      /* UNREACHABLE */ +      return 0; +    } +} + +char +*getmove(struct scrgame *sg, char *move, int l) +{ +  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; +      } +    } +  } else { +    length = l; +  } +  /* 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--; +      } else { +	mvwaddch(sg->score_w, promptrow, promptcol, ' '); +      } +      continue; +    } else if (isascii(ch)) { +      if (ch == '\n') { +	move[c] = ch; +	c++; +	break; +      } else { +	move[c] = ch; +	mvwaddch(sg->score_w, promptrow, promptcol + c, ch); +      } +    } else { +      update_msgbox(sg->msg_w, "Unexpected non-ASCII input"); +      return getmove(sg, move, length); +    } +  } +  move[0] = tolower(move[0]); +  move[2] = tolower(move[2]); +  if (!validcoords(move) || +      (length == 5 && !validcoords(&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)) { +      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])])) { +      update_msgbox(sg->msg_w, "Impossible to move in that direction"); +      return getmove(sg, move, length); +    } +  } +  move[length - 1] = '\0'; +  return move; +} + +struct point +*getpoint(struct game *g, const char *coords) +{ +  int r, c = 0; +  if (validcoords(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 '1': case '7': +	r = 0; +	break; + +      case '2': case '6': +	r = 1; +	break; + +      case '3': case '5': +	r = 2; +	break; + +      default: +	errx(1, "Reached an unreachable branch in getpoint"); +      } +    if (coords[1] != '4') { +      if (coords[0] < 'd') { +	c = coords[1] < '4' ? 5 : 7; +      } else if (coords[0] == 'd') { +	c = coords[1] < '4' ? 4 : 0; +      } else { +	c = coords[1] < '4' ? 3 : 1; +      } +    } +    return &(g->board[r][c]); +  } +  return NULL; +} + + +struct point* +phaseone(struct scrgame *sg) +{ +  struct point *p = NULL; +  char coords[4]; +  for (; sg->game->totalpieces < PIECECOUNT; sg->game->totalpieces++) { +    getmove(sg, coords, 0); +    if (!(p = placepiece(sg, coords))) { +      sg->game->totalpieces--; +      continue; +    } +    sg->game->pieces[sg->game->state]++; +    if (inmill(p)) { +      /* We should redraw the board now so that the player can see the +	 piece he just played */ +      update_scorebox(sg->score_w, sg->game); +      update_board(sg->board_w, sg->game); +      update_msgbox(sg->msg_w, ""); +      mill_handler(sg, coords, 0); +    } +    sg->game->state ^= BLACK; +    update_scorebox(sg->score_w, sg->game); +    update_board(sg->board_w, sg->game); +    update_msgbox(sg->msg_w, ""); +  } +  sg->game->phase = 2; +  update_scorebox(sg->score_w, sg->game); +  return p; +} + +struct point* +phasetwo(struct scrgame *sg) +{ +  struct point *p = NULL; +  char coords[6]; +  while (sg->game->pieces[WHITE] >= 3 && sg->game->pieces[BLACK] >= 3) { +    getmove(sg, coords, 0); +    if (!(p = getpoint(sg->game, coords))) { +      update_msgbox(sg->msg_w, "Something went wrong..."); +      continue; +    } +    if (p->v != statechar(sg->game)) { +      update_msgbox(sg->msg_w, "Please move your own piece."); +      continue; +    } +    if (sg->game->pieces[sg->game->state] == 3) { +      if (!(p = jumppiece(p, sg->game, &coords[2]))) { +	update_msgbox(sg->msg_w, +		      "That location is already occupied. Please try again"); +	continue; +      } +    } else if (!(p = movepiece(p, coords[2]))) { +      update_msgbox(sg->msg_w, +		    "That location is already occupied. Please try again"); +      continue; +    } +    if (inmill(p)) { +      /* We should redraw the board now so that the player can see the +	 piece he just played */ +      update_scorebox(sg->score_w, sg->game); +      update_board(sg->board_w, sg->game); +      update_msgbox(sg->msg_w, ""); +      mill_handler(sg, coords, 0); +    } +    sg->game->state ^= BLACK; +    if (sg->game->pieces[BLACK] == 3 || sg->game->pieces[WHITE] == 3) { +      sg->game->phase = 3; +    } +    update_scorebox(sg->score_w, sg->game); +    update_board(sg->board_w, sg->game); +    update_msgbox(sg->msg_w, ""); +  } +  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) +{ +  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; +} + +struct point* +jumppiece(struct point *p, struct game *g, const char *position) +{ +  struct point *ptr = getpoint(g, position); +  if (ptr && ptr->v == EMPTY) { +    ptr->v = p->v; +    p->v = EMPTY; +    return ptr; +  } +  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]--; +    } else { +      errno = ENOTSUP; +      errx(ENOTSUP, "Tried removing EMPTY. This should NEVER happen."); +    } +    p->v = EMPTY; +    return p; +  } +  return NULL; +} + +int +main(void) +{ +  struct scrgame *sg; +  const char *const *instr; +  int c; +  initscr(); +  keypad(stdscr, TRUE); +  clear(); +  refresh(); +  printw("Display instructions? (y/n) "); +  c = getch(); +  c = tolower(c); +  if (c == 'y') { +    clear();  +    for (instr = instructions; *instr; instr++) { +      printw("%s", *instr); +    } +    refresh(); +    getch(); +  } +  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; +	} +      } +    } +  } else { +    refresh(); +    endwin(); +    errx(ENOMEM, "Unable to allocate enough memory."); +  } +  refresh(); +  endwin(); +  return 0; +} | 
