--- /dev/null
+/*
+ * 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;
+}
--- /dev/null
+/*
+ * 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
--- /dev/null
+/*
+ * 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;
+}