aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRyan Kavanagh <rak@debian.org>2013-03-07 16:41:02 -0500
committerRyan Kavanagh <rak@debian.org>2013-03-07 17:54:42 -0500
commitb1d02164f2fc3993004b28c17ca6f1fb79998431 (patch)
treee52214a0d5d3ee8e8c094975ae77cf17f3a6ddda
parentAdd install targets to Makefile (diff)
Release nmm version 1.0~beta11.0_beta1
-rw-r--r--Makefile6
-rw-r--r--README.md39
-rw-r--r--nmm.634
-rw-r--r--nmm.c1366
4 files changed, 1048 insertions, 397 deletions
diff --git a/Makefile b/Makefile
index f1a4077..9f65da2 100644
--- a/Makefile
+++ b/Makefile
@@ -1,4 +1,5 @@
-CFLAGS:=-Wall -Wextra -pedantic -Werror=format-security -fstack-protector-all $(CFLAGS)
+CFLAGS:= -Wall -Wextra -ansi -pedantic -Werror=format-security \
+ -fstack-protector-all $(CFLAGS)
CFLAGS:=-g $(CFLAGS)
PREFIX=/usr/local
@@ -23,6 +24,7 @@ install: nmm tmm twmm installman
install -m 555 twmm $(PREFIX)/games/
installman: nmm.6 tmm.6 twmm.6
+ -mkdir -p $(MANPATH)/man6/
install -m 444 nmm.6 $(MANPATH)/man6/
install -m 444 tmm.6 $(MANPATH)/man6/tmm.6
install -m 444 twmm.6 $(MANPATH)/man6/twmm.6
@@ -42,6 +44,6 @@ uninstall:
$(MANPATH)/man6/twmm.6
clean:
- rm nmm
+ -rm -f tmm nmm twmm tmm.6 twmm.6
.PHONY: clean install installman
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..0fedaef
--- /dev/null
+++ b/README.md
@@ -0,0 +1,39 @@
+NMM, TMM, and TWMM
+==================
+
+`NMM`, `TMM`, and `TWMM` are a C and curses implementation of the
+popular games Nine Man Morris, Three Man Morris, and Twelve Man
+Morris.
+
+Compiling
+---------
+
+To compile, you will need a C compiler, and a copy of the ncurses
+header files available. Then a simple
+
+ make
+
+will cause `nmm` and company to be compiled (in fact, `tmm` and `twmm`
+are no more than symlinks to `nmm`, displaying the correct game based
+on the basename). To install `nmm`, go
+
+ make install
+
+which will install under the prefix of `/usr/local` (see `Makefile`
+for details). If this isn't the desired location, you can override
+this setting with
+
+ make install -ePREFIX=/your/prefix install
+
+Man pages for `nmm` and company will be installed under
+`$(PREFIX)/man6/` and the `whatis` database will be updated with a
+call to `/usr/libexec/makewhatisdb`. If you wish to change this,
+please override the `MANPATH` and `MAKEWHATIS` variables when you call
+the install target.
+
+LICENSE
+-------
+
+`nmm` and company are distributed Copyright (C) 2013 Ryan Kavanagh
+<rak@debian.org> and are distributed under a 3-clause revised-BSD
+license. Please see the file `LICENSE` for details.
diff --git a/nmm.6 b/nmm.6
index be37f97..4d40b68 100644
--- a/nmm.6
+++ b/nmm.6
@@ -22,16 +22,20 @@
.\" 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
+.Dd March 07 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
+.Os nmm 1.0~beta1
.Sh NAME
-.Nm nmm
-.Nd Nine Men's Morris
+.Nm nmm ,
+.Nm tmm ,
+.Nm twmm
+.Nd Nine, Three, and Twelve Men's Morris
.Sh SYNOPSIS
.Nm nmm
+.Nm tmm
+.Nm twmm
.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
@@ -44,7 +48,8 @@ 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,
+placed 3 (tmm), 9 (nmm), or 12 (twmm) pieces. The location is selected
+by entering its coordinates,
e.g\&.
.Sq a1
or
@@ -56,13 +61,16 @@ entering its coordinates.
.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,
+coordinates and its direction is chosen from n s e w ne nw se sw, for
+North, South, East, West, North East, North West, South East, South
+West (ne\(emsw are only available in twmm). Note that to move in a
+direction, there must be a dashed line emanating from the piece in
+that direction. 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
+.Sq g7sw
+moves the piece at g7 south west. 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
@@ -71,6 +79,12 @@ location, e.g.
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.
+.Ss During game play
+Press
+.Sq \?
+at any time to display instructions. Press
+.Sq q
+to quit.
.Sh EXIT STATUS
.Ex
.Sh AUTHORS
diff --git a/nmm.c b/nmm.c
index 104e2fa..cac1f9b 100644
--- a/nmm.c
+++ b/nmm.c
@@ -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();