]> git.notmuchmail.org Git - dvonn/commitdiff
Initial import of DVONN game
authorCarl Worth <cworth@cworth.org>
Wed, 4 Mar 2009 23:35:48 +0000 (15:35 -0800)
committerCarl Worth <cworth@cworth.org>
Wed, 4 Mar 2009 23:39:27 +0000 (15:39 -0800)
Not yet playable, but at least we can draw something like the
correct board.

.gitignore [new file with mode: 0644]
Makefile [new file with mode: 0644]
README [new file with mode: 0644]
dvonn-board.c [new file with mode: 0644]
dvonn-board.h [new file with mode: 0644]
dvonn.c [new file with mode: 0644]

diff --git a/.gitignore b/.gitignore
new file mode 100644 (file)
index 0000000..72030dc
--- /dev/null
@@ -0,0 +1,4 @@
+*~
+*.o
+dvonn
+Makefile.dep
diff --git a/Makefile b/Makefile
new file mode 100644 (file)
index 0000000..4a98b8b
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,19 @@
+ALL=dvonn
+MYCFLAGS=-Wall `pkg-config --cflags loudmouth-1.0 gtk+-2.0`
+MYLDFLAGS=`pkg-config --libs loudmouth-1.0 gtk+-2.0`
+
+all: $(ALL)
+
+%.o: %.c
+       $(CC) $(CFLAGS) $(MYCFLAGS) -c -o $@ -c $<
+
+dvonn: dvonn.o dvonn-board.o
+       $(CC) $(LDLAGS) $(MYLDFLAGS) -o $@ $^
+
+Makefile.dep: *.c
+       $(CC) -M $(CPPFLAGS) $(MYCFLAGS) $^ > $@
+-include Makefile.dep
+
+.PHONY: clean
+clean:
+       rm -f $(ALL) *.o Makefile.dep
diff --git a/README b/README
new file mode 100644 (file)
index 0000000..ea4a9f5
--- /dev/null
+++ b/README
@@ -0,0 +1,19 @@
+This is an implementation of the game DVONN as invented by Kris Burm.
+
+Building dvonn
+--------------
+It depends on GTK+. If you're using a Debian system, this libraries
+can be installed with the following command:
+
+       sudo apt-get install libgtk2.0-dev
+
+After that, compiling the game is as simple as:
+
+       make
+
+And running it is as simple as:
+
+       ./dvonn
+
+For rules on playing DVONN, please consider purchasing a copy and
+supporting a great game designer.
diff --git a/dvonn-board.c b/dvonn-board.c
new file mode 100644 (file)
index 0000000..35789e7
--- /dev/null
@@ -0,0 +1,140 @@
+/*
+ * Copyright (C) 2009 Carl Worth
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see http://www.gnu.org/licenses/ .
+ *
+ * Author: Carl Worth <cworth@cworth.org>
+ */
+
+#include <stdint.h>
+#include <stdlib.h>
+#include <assert.h>
+
+#include <glib.h>
+
+#include "dvonn-board.h"
+
+/* Just a diagram to help start thinking about data structures:
+ *
+ * Played/drawn as:
+ *
+ *   1 ○ ○ ○ ○ ○ ○ ○ ○ ○
+ *  2 ○ ● ● ○ ○ ○ ○ ○ ○ ○
+ * 3 ○ ● ○ ● ○ ○ ○ ○ ○ ○ ○
+ *  4 ○ ● ● ○ ○ ○ ○ ○ ○ ○ K
+ *   5 ○ ○ ○ ○ ○ ○ ○ ○ ○ J
+ *      A B C D E F G H I
+ *
+ * Stored as:
+ *
+ * 1 x x ○ ○ ○ ○ ○ ○ ○ ○ ○
+ * 2 x ○ ● ● ○ ○ ○ ○ ○ ○ ○
+ * 3 ○ ● ○ ● ○ ○ ○ ○ ○ ○ ○
+ * 4 ○ ● ● ○ ○ ○ ○ ○ ○ ○ x
+ * 5 ○ ○ ○ ○ ○ ○ ○ ○ ○ x x
+ *   A B C D E F G H I J K
+ *
+ * With connections as:
+ *
+ *         C2  D2
+ *          | /
+ *  B3 <-> C3 <-> D3
+ *        / |
+ *      B2 C4
+ */
+
+void
+dvonn_board_init (dvonn_board_t *board)
+{
+    int x, y;
+
+    for (x = 0; x < DVONN_BOARD_X_SIZE; x++) {
+       for (y = 0; y < DVONN_BOARD_Y_SIZE; y++) {
+           board->cells[x][y].type = DVONN_CELL_EMPTY;
+           board->cells[x][y].height = 0;
+       }
+    }
+
+    /* Cut out the unplayable "corners" of the board. */
+    for (x = 0; x < DVONN_BOARD_Y_SIZE / 2; x++) {
+       for (y = 0; y < (DVONN_BOARD_Y_SIZE / 2) - x; y++) {
+           board->cells[x][y].type = DVONN_CELL_INVALID;
+           board->cells
+               [DVONN_BOARD_X_SIZE-1-x]
+               [DVONN_BOARD_Y_SIZE-1-y].type = DVONN_CELL_INVALID;
+       }
+    }
+
+    board->player = DVONN_PLAYER_WHITE;
+}
+
+static dvonn_bool_t
+dvonn_board_move_legal (dvonn_board_t *board,
+                       int x1, int y1,
+                       int x2, int y2,
+                       char **error)
+{
+    if (x1 < 0 || x1 >= DVONN_BOARD_X_SIZE ||
+       y1 < 0 || y1 >= DVONN_BOARD_Y_SIZE ||
+       x2 < 0 || x2 >= DVONN_BOARD_X_SIZE ||
+       y2 < 0 || y2 >= DVONN_BOARD_Y_SIZE)
+    {
+       *error = "Invalid coordinates (not on board)";
+       return FALSE;
+    }
+
+    if (board->cells[x1][y1].type == DVONN_CELL_INVALID) {
+       *error = "Not a valid board space";
+       return FALSE;
+    }
+
+    if (board->cells[x1][y1].type == DVONN_CELL_EMPTY) {
+       *error = "There is no piece there to move";
+       return FALSE;
+    }
+
+    if (board->cells[x1][y1].type != board->player) {
+       *error = "You cannot move your opponent's piece";
+       return FALSE;
+    }
+
+    /* XXX: Need to code up DVONN-legal move calculation here. */
+
+    return TRUE;
+}
+
+static void
+dvonn_board_next_player (dvonn_board_t *board)
+{
+    if (board->player == DVONN_PLAYER_BLACK)
+       board->player = DVONN_PLAYER_WHITE;
+    else
+       board->player = DVONN_PLAYER_BLACK;
+}
+
+int
+dvonn_board_move (dvonn_board_t *board,
+                 int x1, int y1,
+                 int x2, int y2,
+                 char **error)
+{
+    if (! dvonn_board_move_legal (board, x1, y1, x2, y2, error))
+       return FALSE;
+
+    /* XXX: Need to execute the move here. */
+
+    dvonn_board_next_player (board);
+
+    return TRUE;
+}
diff --git a/dvonn-board.h b/dvonn-board.h
new file mode 100644 (file)
index 0000000..c1c8270
--- /dev/null
@@ -0,0 +1,73 @@
+/*
+ * Copyright (C) 2008 Carl Worth
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see http://www.gnu.org/licenses/ .
+ *
+ * Author: Carl Worth <cworth@cworth.org>
+ */
+
+#ifndef DVONN_BOARD_H
+#define DVONN_BOARD_H
+
+typedef int dvonn_bool_t;
+
+#ifndef FALSE
+#define FALSE 0
+#endif
+#ifndef TRUE
+#define TRUE  1
+#endif
+
+typedef enum {
+    DVONN_PLAYER_BLACK,
+    DVONN_PLAYER_WHITE
+} dvonn_player_t;
+
+typedef enum {
+    DVONN_CELL_BLACK = DVONN_PLAYER_BLACK,
+    DVONN_CELL_WHITE = DVONN_PLAYER_WHITE,
+    DVONN_CELL_EMPTY,
+    DVONN_CELL_INVALID
+} dvonn_cell_type_t;
+
+typedef struct {
+    dvonn_cell_type_t type;
+    int height;
+} dvonn_cell_t;
+
+#define DVONN_BOARD_X_SIZE 11
+#define DVONN_BOARD_Y_SIZE 5
+
+typedef struct {
+    dvonn_cell_t cells[DVONN_BOARD_X_SIZE][DVONN_BOARD_Y_SIZE];
+
+    dvonn_player_t player;
+} dvonn_board_t;
+
+/* Initialize a board for a new game of DVONN. */
+void
+dvonn_board_init (dvonn_board_t *board);
+
+/* Move a piece from (x1,y1) to (x2,y2) where (0,0) is at the
+ * upper-left corner of the board. Returns TRUE if the move is legal
+ * and is performed. If the move is not legal this function returns
+ * FALSE, no change will be performed on the board, and *error will be
+ * set to a string describing why the move is illegal.*/
+int
+dvonn_board_move (dvonn_board_t *board,
+               int x1, int y1,
+               int x2, int y2,
+               char **error);
+
+#endif
diff --git a/dvonn.c b/dvonn.c
new file mode 100644 (file)
index 0000000..1744770
--- /dev/null
+++ b/dvonn.c
@@ -0,0 +1,295 @@
+/*
+ * Copyright (C) 2008 Carl Worth
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see http://www.gnu.org/licenses/ .
+ *
+ * Author: Carl Worth <cworth@cworth.org>
+ */
+
+#include <stdlib.h>
+#include <gtk/gtk.h>
+#include <math.h>
+
+#include "dvonn-board.h"
+
+#define BOARD_X_SIZE 11
+#define BOARD_Y_SIZE 5
+
+typedef struct {
+    int x_offset;
+    int y_offset;
+    int width;
+    int height;
+    int cell_size;
+} layout_t;
+
+typedef struct _loa_game loa_game_t;
+
+typedef struct {
+    loa_game_t *game;
+    GtkWidget *window;
+    layout_t layout;
+} view_t;
+
+struct _loa_game {
+    view_t **views;
+    int num_views;
+
+    dvonn_board_t board;
+    dvonn_bool_t has_selected;
+    int selected_x;
+    int selected_y;
+};
+
+static gboolean
+on_delete_event_quit (GtkWidget  *widget,
+                     GdkEvent   *event,
+                     gpointer    user_data)
+{
+    gtk_main_quit ();
+
+    /* Returning FALSE allows the default handler for delete-event
+     * to proceed to cleanup the widget. */
+    return FALSE;
+}
+
+/* Something like buff */
+#define BACKGROUND_COLOR 0.89, 0.70, 0.40
+#define LIGHT_SQUARE_COLOR 0.89, 0.70, 0.40
+/* Something like mahogany */
+#define DARK_SQUARE_COLOR  0.26, 0.02, 0.01
+
+/* XXX: This really should have an interest rectangle. */
+static void
+loa_game_update_windows (loa_game_t *game)
+{
+    int i;
+
+    for (i = 0; i < game->num_views; i++)
+       gtk_widget_queue_draw (game->views[i]->window);
+}
+
+static gboolean
+on_button_press_event (GtkWidget       *widget,
+                      GdkEventButton   *event,
+                      gpointer          user_data)
+{
+    view_t *view = user_data;
+    layout_t *layout = &view->layout;
+    loa_game_t *game = view->game;
+    int x, y;
+    char *error;
+
+    x = (event->x - layout->x_offset) / layout->cell_size;
+    y = (event->y - layout->y_offset) / layout->cell_size;
+
+    if (! game->has_selected) {
+       if (game->board.cells[x][y].type == game->board.player) {
+           game->has_selected = TRUE;
+           game->selected_x = x;
+           game->selected_y = y;
+           loa_game_update_windows (game);
+       }
+       return TRUE;
+    }
+
+    /* Do nothing for out-of-bounds clicks */
+    if (x < 0 || x >= DVONN_BOARD_X_SIZE ||
+       y < 0 || y >= DVONN_BOARD_Y_SIZE)
+    {
+       return TRUE;
+    }
+
+    if (x == game->selected_x &&
+       y == game->selected_y)
+    {
+       game->has_selected = FALSE;
+       loa_game_update_windows (game);
+       return TRUE;
+    }
+       
+    if (dvonn_board_move (&game->board,
+                         game->selected_x, game->selected_y,
+                         x, y, &error))
+    {
+       game->has_selected = FALSE;
+       loa_game_update_windows (game);
+       return TRUE;
+    } else {
+       printf ("Illegal move %c%d%c%d: %s\n",
+               'a' + game->selected_x,
+               DVONN_BOARD_Y_SIZE - game->selected_y,
+               'a' + x,
+               DVONN_BOARD_Y_SIZE - y,
+               error);
+    }
+
+    return TRUE;
+}
+
+/* Add a unit-sized DVONN-ring path to cr, from (0,0) to (1,1). */
+static void
+ring_path (cairo_t *cr)
+{
+    cairo_arc (cr, 0.5, 0.5, 0.4, 0, 2 * M_PI);
+    cairo_arc_negative (cr, 0.5, 0.5, 0.2, 2 * M_PI, 0);
+}
+
+static gboolean
+on_expose_event_draw (GtkWidget                *widget,
+                     GdkEventExpose    *event,
+                     gpointer           user_data)
+{
+    view_t *view = user_data;
+    layout_t *layout = &view->layout;
+    loa_game_t *game = view->game;
+    cairo_t *cr;
+    int x, y;
+
+    if (layout->width != widget->allocation.width ||
+       layout->height != widget->allocation.height)
+    {
+       int x_size, y_size;
+
+       layout->width = widget->allocation.width;
+       layout->height = widget->allocation.height;
+
+       x_size = layout->width;
+       if (x_size > layout->height * BOARD_X_SIZE / BOARD_Y_SIZE)
+           x_size = layout->height * BOARD_X_SIZE / BOARD_Y_SIZE;
+
+       /* Size must be a multiple of the integer cell_size */
+       layout->cell_size = x_size / BOARD_X_SIZE;
+       x_size = layout->cell_size * BOARD_X_SIZE;
+       y_size = layout->cell_size * BOARD_Y_SIZE;
+
+       layout->x_offset = (layout->width - x_size) / 2;
+       layout->y_offset = (layout->height - y_size) / 2;
+    }
+
+    cr = gdk_cairo_create (widget->window);
+    
+    cairo_set_source_rgb (cr, BACKGROUND_COLOR);
+    cairo_paint (cr);
+
+    cairo_translate (cr, layout->x_offset, layout->y_offset);
+    cairo_scale (cr, layout->cell_size, layout->cell_size);
+
+    for (y = 0; y < BOARD_Y_SIZE; y++) {
+       for (x = 0; x < BOARD_X_SIZE; x++) {
+           dvonn_cell_t cell;
+
+           cell = game->board.cells[x][y];
+           if (cell.type == DVONN_CELL_INVALID)
+               continue;
+
+           cairo_save (cr);
+           cairo_translate(cr,
+                           x + (y - DVONN_BOARD_Y_SIZE/2) / 2.0, y);
+           ring_path (cr);
+           cairo_set_source_rgba (cr, 0.0, 0.0, 0.2, 0.1);
+           cairo_fill (cr);
+
+           cairo_restore (cr);
+       }
+    }
+
+    cairo_destroy (cr);
+
+    return TRUE;
+}
+
+static void
+loa_game_init (loa_game_t *game)
+{
+    game->views = NULL;
+    game->num_views = 0;
+
+    game->has_selected = FALSE;
+
+    dvonn_board_init (&game->board);
+}
+
+static void
+view_init (view_t *view, loa_game_t *game, GtkWidget *window)
+{
+    view->game = game;
+    view->window = window;
+
+    view->layout.width = 0;
+    view->layout.height = 0;
+}
+
+static void
+loa_game_create_view (loa_game_t *game)
+{
+    view_t *view;
+    GtkWidget *window;
+    GtkWidget *drawing_area;
+
+    window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
+
+    view = malloc (sizeof (view_t));
+    if (view == NULL) {
+       fprintf (stderr, "Out of memory.\n");
+       exit (1);
+    }
+
+    game->num_views++;
+    game->views = realloc (game->views,
+                          game->num_views * sizeof (view_t *));
+    if (game->views == NULL) {
+       fprintf (stderr, "Out of memory.\n");
+       exit (1);
+    }
+    game->views[game->num_views - 1] = view;
+
+    view_init (view, game, window);
+
+    gtk_window_set_default_size (GTK_WINDOW (window), 561, 255);
+
+    g_signal_connect (window, "delete-event",
+                     G_CALLBACK (on_delete_event_quit), NULL);
+
+    drawing_area = gtk_drawing_area_new ();
+
+    gtk_container_add (GTK_CONTAINER (window), drawing_area);
+
+    g_signal_connect (drawing_area, "expose-event",  
+                     G_CALLBACK (on_expose_event_draw), view);
+
+    gtk_widget_add_events (drawing_area, GDK_BUTTON_PRESS_MASK);
+    g_signal_connect (drawing_area, "button-press-event",
+                     G_CALLBACK (on_button_press_event), view);
+
+    gtk_widget_show_all (window);
+}
+
+int
+main (int argc, char *argv[])
+{
+    loa_game_t game;
+
+    loa_game_init (&game);
+
+    gtk_init (&argc, &argv);
+
+    /* Create two views of the game (one for each player) */
+    loa_game_create_view (&game);
+    loa_game_create_view (&game);
+    
+    gtk_main ();
+
+    return 0;
+}