2 * Copyright (C) 2008 Carl Worth
4 * This program is free software: you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation, either version 3 of the License, or
7 * (at your option) any later version.
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
14 * You should have received a copy of the GNU General Public License
15 * along with this program. If not, see http://www.gnu.org/licenses/ .
17 * Author: Carl Worth <cworth@cworth.org>
24 #include "dvonn-board.h"
26 #define BOARD_X_SIZE 11
27 #define BOARD_Y_SIZE 5
37 typedef struct _loa_game loa_game_t;
50 dvonn_bool_t has_selected;
56 on_delete_event_quit (GtkWidget *widget,
62 /* Returning FALSE allows the default handler for delete-event
63 * to proceed to cleanup the widget. */
67 /* Something like buff */
68 #define BACKGROUND_COLOR 0.89, 0.70, 0.40
69 #define LIGHT_SQUARE_COLOR 0.89, 0.70, 0.40
70 /* Something like mahogany */
71 #define DARK_SQUARE_COLOR 0.26, 0.02, 0.01
73 /* XXX: This really should have an interest rectangle. */
75 loa_game_update_windows (loa_game_t *game)
79 for (i = 0; i < game->num_views; i++)
80 gtk_widget_queue_draw (game->views[i]->window);
83 /* Convert from a board index to a device-pixel coordinate pair, (at
84 * the center of the ring). */
86 layout_board_to_device (layout_t *layout, double *x_ret, double *y_ret)
88 double x = *x_ret, y = *y_ret;
90 *x_ret = layout->x_offset + layout->cell_size *
91 (x + (y - DVONN_BOARD_Y_SIZE/2)/2.0 + 0.5);
92 *y_ret = layout->y_offset + layout->cell_size * (M_SQRT1_2 * y + 0.5);
95 /* Convert from a device-pixel coordinate pair to a board index. */
97 layout_device_to_board (layout_t *layout, int *x_ret, int *y_ret)
99 int x = *x_ret, y = *y_ret;
101 double x1_dev, y1_dev, x2_dev, y2_dev;
102 double dx, dy, d1, d2;
104 /* Because the vertical spacing between adjacent rows is less than
105 * layout->cell_size, the simple calculations here give us two
106 * candidate (x,y) pairs. We then choose the correct one based on
107 * a distance calculation.
109 y1 = (y - layout->y_offset) / (layout->cell_size * M_SQRT1_2);
110 x1 = (double) (x - layout->x_offset) / layout->cell_size - (y1 - DVONN_BOARD_Y_SIZE/2)/2.0;
113 x2 = (double) (x - layout->x_offset) / layout->cell_size - (y2 - DVONN_BOARD_Y_SIZE/2)/2.0;
117 layout_board_to_device (layout, &x1_dev, &y1_dev);
121 layout_board_to_device (layout, &x2_dev, &y2_dev);
125 d1 = sqrt (dx*dx + dy*dy);
129 d2 = sqrt (dx*dx + dy*dy);
141 on_button_press_event (GtkWidget *widget,
142 GdkEventButton *event,
145 view_t *view = user_data;
146 layout_t *layout = &view->layout;
147 loa_game_t *game = view->game;
153 layout_device_to_board (layout, &x, &y);
155 /* Do nothing for out-of-bounds clicks */
156 if (x < 0 || x >= DVONN_BOARD_X_SIZE ||
157 y < 0 || y >= DVONN_BOARD_Y_SIZE)
162 /* Nor for cells which have array entries that are invalid. */
163 if (game->board.cells[x][y].type == DVONN_CELL_INVALID)
166 if (game->board.cells[x][y].type == DVONN_CELL_EMPTY)
167 game->board.cells[x][y].type = DVONN_CELL_WHITE;
168 else if (game->board.cells[x][y].type == DVONN_CELL_WHITE)
169 game->board.cells[x][y].type = DVONN_CELL_EMPTY;
171 loa_game_update_windows (game);
175 if (! game->has_selected) {
176 if (game->board.cells[x][y].type == game->board.player) {
177 game->has_selected = TRUE;
178 game->selected_x = x;
179 game->selected_y = y;
180 loa_game_update_windows (game);
185 if (x == game->selected_x &&
186 y == game->selected_y)
188 game->has_selected = FALSE;
189 loa_game_update_windows (game);
193 if (dvonn_board_move (&game->board,
194 game->selected_x, game->selected_y,
197 game->has_selected = FALSE;
198 loa_game_update_windows (game);
201 printf ("Illegal move %c%d%c%d: %s\n",
202 'a' + game->selected_x,
203 DVONN_BOARD_Y_SIZE - game->selected_y,
205 DVONN_BOARD_Y_SIZE - y,
212 /* Add a unit-sized DVONN-ring path to cr, from (0,0) to (1,1). */
214 ring_path (cairo_t *cr)
216 cairo_arc (cr, 0.5, 0.5, 0.4, 0, 2 * M_PI);
217 cairo_arc_negative (cr, 0.5, 0.5, 0.2, 2 * M_PI, 0);
221 on_expose_event_draw (GtkWidget *widget,
222 GdkEventExpose *event,
225 view_t *view = user_data;
226 layout_t *layout = &view->layout;
227 loa_game_t *game = view->game;
231 if (layout->width != widget->allocation.width ||
232 layout->height != widget->allocation.height)
236 layout->width = widget->allocation.width;
237 layout->height = widget->allocation.height;
239 x_size = layout->width;
240 if (x_size > layout->height * BOARD_X_SIZE / (1 + M_SQRT1_2 * (BOARD_Y_SIZE-1)))
241 x_size = layout->height * BOARD_X_SIZE / (1 + M_SQRT1_2 * (BOARD_Y_SIZE-1));
243 /* Size must be a multiple of the integer cell_size */
244 layout->cell_size = x_size / BOARD_X_SIZE;
245 x_size = layout->cell_size * BOARD_X_SIZE;
246 y_size = layout->cell_size * (1 + M_SQRT1_2 * (BOARD_Y_SIZE-1));
248 layout->x_offset = (layout->width - x_size) / 2;
249 layout->y_offset = (layout->height - y_size) / 2;
252 cr = gdk_cairo_create (widget->window);
254 cairo_set_source_rgb (cr, BACKGROUND_COLOR);
257 cairo_translate (cr, layout->x_offset, layout->y_offset);
258 cairo_scale (cr, layout->cell_size, layout->cell_size);
260 for (y = 0; y < BOARD_Y_SIZE; y++) {
261 for (x = 0; x < BOARD_X_SIZE; x++) {
264 cell = game->board.cells[x][y];
265 if (cell.type == DVONN_CELL_INVALID)
270 x + (y - DVONN_BOARD_Y_SIZE/2) / 2.0,
273 if (cell.type == DVONN_CELL_WHITE)
274 cairo_set_source_rgb (cr, 1.0, 1.0, 1.0); /* white */
276 cairo_set_source_rgba (cr, 0.0, 0.0, 0.2, 0.1);
289 loa_game_init (loa_game_t *game)
294 game->has_selected = FALSE;
296 dvonn_board_init (&game->board);
300 view_init (view_t *view, loa_game_t *game, GtkWidget *window)
303 view->window = window;
305 view->layout.width = 0;
306 view->layout.height = 0;
310 loa_game_create_view (loa_game_t *game)
314 GtkWidget *drawing_area;
316 window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
318 view = malloc (sizeof (view_t));
320 fprintf (stderr, "Out of memory.\n");
325 game->views = realloc (game->views,
326 game->num_views * sizeof (view_t *));
327 if (game->views == NULL) {
328 fprintf (stderr, "Out of memory.\n");
331 game->views[game->num_views - 1] = view;
333 view_init (view, game, window);
335 gtk_window_set_default_size (GTK_WINDOW (window), 780, 251);
337 g_signal_connect (window, "delete-event",
338 G_CALLBACK (on_delete_event_quit), NULL);
340 drawing_area = gtk_drawing_area_new ();
342 gtk_container_add (GTK_CONTAINER (window), drawing_area);
344 g_signal_connect (drawing_area, "expose-event",
345 G_CALLBACK (on_expose_event_draw), view);
347 gtk_widget_add_events (drawing_area, GDK_BUTTON_PRESS_MASK);
348 g_signal_connect (drawing_area, "button-press-event",
349 G_CALLBACK (on_button_press_event), view);
351 gtk_widget_show_all (window);
355 main (int argc, char *argv[])
359 loa_game_init (&game);
361 gtk_init (&argc, &argv);
363 /* Create two views of the game (one for each player) */
364 loa_game_create_view (&game);
365 loa_game_create_view (&game);