From: Carl Worth Date: Wed, 4 Mar 2009 23:35:48 +0000 (-0800) Subject: Initial import of DVONN game X-Git-Url: https://git.notmuchmail.org/git?p=dvonn;a=commitdiff_plain;h=c63c776c52cb865bc510b1feb9d17abe47086e5c Initial import of DVONN game Not yet playable, but at least we can draw something like the correct board. --- c63c776c52cb865bc510b1feb9d17abe47086e5c diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..72030dc --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +*~ +*.o +dvonn +Makefile.dep diff --git a/Makefile b/Makefile new file mode 100644 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 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 index 0000000..35789e7 --- /dev/null +++ b/dvonn-board.c @@ -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 + */ + +#include +#include +#include + +#include + +#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 index 0000000..c1c8270 --- /dev/null +++ b/dvonn-board.h @@ -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 + */ + +#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 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 + */ + +#include +#include +#include + +#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; +}