]> git.cworth.org Git - dvonn/blob - dvonn.c
Implement placement phase of game
[dvonn] / dvonn.c
1 /*
2  * Copyright (C) 2009 Carl Worth
3  *
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.
8  *
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.
13  *
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/ .
16  *
17  * Author: Carl Worth <cworth@cworth.org>
18  */
19
20 #include <stdlib.h>
21 #include <gtk/gtk.h>
22 #include <math.h>
23
24 #include "dvonn-board.h"
25
26 #define BOARD_X_SIZE 11
27 #define BOARD_Y_SIZE 5
28
29 typedef struct {
30     int x_offset;
31     int y_offset;
32     int width;
33     int height;
34     int cell_size;
35 } layout_t;
36
37 typedef struct _dvonn_game dvonn_game_t;
38
39 typedef struct {
40     dvonn_game_t *game;
41     GtkWidget *window;
42     layout_t layout;
43 } view_t;
44
45 struct _dvonn_game {
46     view_t **views;
47     int num_views;
48
49     dvonn_board_t board;
50     dvonn_bool_t has_selected;
51     int selected_x;
52     int selected_y;
53 };
54
55 static gboolean
56 on_delete_event_quit (GtkWidget  *widget,
57                       GdkEvent   *event,
58                       gpointer    user_data)
59 {
60     gtk_main_quit ();
61
62     /* Returning FALSE allows the default handler for delete-event
63      * to proceed to cleanup the widget. */
64     return FALSE;
65 }
66
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
72
73 /* XXX: This really should have an interest rectangle. */
74 static void
75 dvonn_game_update_windows (dvonn_game_t *game)
76 {
77     int i;
78
79     for (i = 0; i < game->num_views; i++)
80         gtk_widget_queue_draw (game->views[i]->window);
81 }
82
83 /* Convert from a board index to a device-pixel coordinate pair, (at
84  * the center of the ring). */
85 static void
86 layout_board_to_device (layout_t *layout, double *x_ret, double *y_ret)
87 {
88     double x = *x_ret, y = *y_ret;
89
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);
93 }
94
95 /* Convert from a device-pixel coordinate pair to a board index. */
96 static void
97 layout_device_to_board (layout_t *layout, int *x_ret, int *y_ret)
98 {
99     int x = *x_ret, y = *y_ret;
100     int x1, y1, x2, y2;
101     double x1_dev, y1_dev, x2_dev, y2_dev;
102     double dx, dy, d1, d2;
103
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.
108      */
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;
111
112     y2 = y1 - 1;
113     x2 = (double) (x - layout->x_offset) / layout->cell_size - (y2 - DVONN_BOARD_Y_SIZE/2)/2.0;
114
115     x1_dev = x1;
116     y1_dev = y1;
117     layout_board_to_device (layout, &x1_dev, &y1_dev);
118
119     x2_dev = x2;
120     y2_dev = y2;
121     layout_board_to_device (layout, &x2_dev, &y2_dev);
122
123     dx = x - x1_dev;
124     dy = y - y1_dev;
125     d1 = sqrt (dx*dx + dy*dy);
126
127     dx = x - x2_dev;
128     dy = y - y2_dev;
129     d2 = sqrt (dx*dx + dy*dy);
130
131     if (d1 < d2) {
132         *x_ret = x1;
133         *y_ret = y1;
134     } else {
135         *x_ret = x2;
136         *y_ret = y2;
137     }
138 }
139
140 static gboolean
141 on_button_press_event (GtkWidget        *widget,
142                        GdkEventButton   *event,
143                        gpointer          user_data)
144 {
145     view_t *view = user_data;
146     layout_t *layout = &view->layout;
147     dvonn_game_t *game = view->game;
148     int x, y;
149     char *error;
150
151     x = event->x;
152     y = event->y;
153     layout_device_to_board (layout, &x, &y);
154
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)
158     {
159         return TRUE;
160     }
161
162     /* Nor for cells which have array entries that are invalid. */
163     if (game->board.cells[x][y].type == DVONN_CELL_INVALID)
164         return TRUE;
165
166     if (game->board.phase == DVONN_PHASE_PLACEMENT) {
167         if (dvonn_board_place (&game->board, x, y, &error))
168             dvonn_game_update_windows (game);
169         else
170             printf ("Illegal placement %c%d: %s\n",
171                     'a' + x,
172                     y + 1,
173                     error);
174
175         return TRUE;
176     }
177
178     if (! game->has_selected) {
179         if (game->board.cells[x][y].type == game->board.player) {
180             game->has_selected = TRUE;
181             game->selected_x = x;
182             game->selected_y = y;
183             dvonn_game_update_windows (game);
184         }
185         return TRUE;
186     }
187
188     if (x == game->selected_x && y == game->selected_y)
189     {
190         game->has_selected = FALSE;
191         dvonn_game_update_windows (game);
192         return TRUE;
193     }
194         
195     if (dvonn_board_move (&game->board,
196                           game->selected_x, game->selected_y,
197                           x, y, &error))
198     {
199         game->has_selected = FALSE;
200         dvonn_game_update_windows (game);
201         return TRUE;
202     } else {
203         printf ("Illegal move %c%d%c%d: %s\n",
204                 'a' + game->selected_x,
205                 game->selected_y + 1,
206                 'a' + x,
207                 y + 1,
208                 error);
209     }
210
211     return TRUE;
212 }
213
214 /* Add a unit-sized DVONN-ring path to cr, from (0,0) to (1,1). */
215 static void
216 ring_path (cairo_t *cr)
217 {
218     cairo_arc (cr, 0.5, 0.5, 0.4, 0, 2 * M_PI);
219     cairo_arc_negative (cr, 0.5, 0.5, 0.2, 2 * M_PI, 0);
220 }
221
222 static gboolean
223 on_expose_event_draw (GtkWidget         *widget,
224                       GdkEventExpose    *event,
225                       gpointer           user_data)
226 {
227     view_t *view = user_data;
228     layout_t *layout = &view->layout;
229     dvonn_game_t *game = view->game;
230     cairo_t *cr;
231     int x, y;
232
233     if (layout->width != widget->allocation.width ||
234         layout->height != widget->allocation.height)
235     {
236         int x_size, y_size;
237
238         layout->width = widget->allocation.width;
239         layout->height = widget->allocation.height;
240
241         x_size = layout->width;
242         if (x_size > layout->height * BOARD_X_SIZE / (1 + M_SQRT1_2 * (BOARD_Y_SIZE-1)))
243             x_size = layout->height * BOARD_X_SIZE / (1 + M_SQRT1_2 * (BOARD_Y_SIZE-1));
244
245         /* Size must be a multiple of the integer cell_size */
246         layout->cell_size = x_size / BOARD_X_SIZE;
247         x_size = layout->cell_size * BOARD_X_SIZE;
248         y_size = layout->cell_size * (1 + M_SQRT1_2 * (BOARD_Y_SIZE-1));
249
250         layout->x_offset = (layout->width - x_size) / 2;
251         layout->y_offset = (layout->height - y_size) / 2;
252     }
253
254     cr = gdk_cairo_create (widget->window);
255     
256     cairo_set_source_rgb (cr, BACKGROUND_COLOR);
257     cairo_paint (cr);
258
259     cairo_translate (cr, layout->x_offset, layout->y_offset);
260     cairo_scale (cr, layout->cell_size, layout->cell_size);
261
262     for (y = 0; y < BOARD_Y_SIZE; y++) {
263         for (x = 0; x < BOARD_X_SIZE; x++) {
264             dvonn_cell_t cell;
265
266             cell = game->board.cells[x][y];
267             if (cell.type == DVONN_CELL_INVALID)
268                 continue;
269
270             cairo_save (cr);
271             cairo_translate(cr,
272                             x + (y - DVONN_BOARD_Y_SIZE/2) / 2.0,
273                             M_SQRT1_2 * y);
274             ring_path (cr);
275             switch (cell.type) {
276             case DVONN_CELL_WHITE:
277                 cairo_set_source_rgb (cr, 1.0, 1.0, 1.0); /* white */
278                 break;
279             case DVONN_CELL_BLACK:
280                 cairo_set_source_rgb (cr, 0.0, 0.0, 0.0); /* black */
281                 break;
282             case DVONN_CELL_RED:
283                 cairo_set_source_rgb (cr, 0.8, 0.2, 0.2); /* red */
284                 break;
285             case DVONN_CELL_EMPTY:
286             default:
287                 cairo_set_source_rgba (cr, 0.0, 0.0, 0.2, 0.1);
288                 break;
289             }
290             cairo_fill (cr);
291
292             cairo_restore (cr);
293         }
294     }
295
296     cairo_destroy (cr);
297
298     return TRUE;
299 }
300
301 static void
302 dvonn_game_init (dvonn_game_t *game)
303 {
304     game->views = NULL;
305     game->num_views = 0;
306
307     game->has_selected = FALSE;
308
309     dvonn_board_init (&game->board);
310 }
311
312 static void
313 view_init (view_t *view, dvonn_game_t *game, GtkWidget *window)
314 {
315     view->game = game;
316     view->window = window;
317
318     view->layout.width = 0;
319     view->layout.height = 0;
320 }
321
322 static void
323 dvonn_game_create_view (dvonn_game_t *game)
324 {
325     view_t *view;
326     GtkWidget *window;
327     GtkWidget *drawing_area;
328
329     window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
330
331     view = malloc (sizeof (view_t));
332     if (view == NULL) {
333         fprintf (stderr, "Out of memory.\n");
334         exit (1);
335     }
336
337     game->num_views++;
338     game->views = realloc (game->views,
339                            game->num_views * sizeof (view_t *));
340     if (game->views == NULL) {
341         fprintf (stderr, "Out of memory.\n");
342         exit (1);
343     }
344     game->views[game->num_views - 1] = view;
345
346     view_init (view, game, window);
347
348     gtk_window_set_default_size (GTK_WINDOW (window), 780, 251);
349
350     g_signal_connect (window, "delete-event",
351                       G_CALLBACK (on_delete_event_quit), NULL);
352
353     drawing_area = gtk_drawing_area_new ();
354
355     gtk_container_add (GTK_CONTAINER (window), drawing_area);
356
357     g_signal_connect (drawing_area, "expose-event",  
358                       G_CALLBACK (on_expose_event_draw), view);
359
360     gtk_widget_add_events (drawing_area, GDK_BUTTON_PRESS_MASK);
361     g_signal_connect (drawing_area, "button-press-event",
362                       G_CALLBACK (on_button_press_event), view);
363
364     gtk_widget_show_all (window);
365 }
366
367 int
368 main (int argc, char *argv[])
369 {
370     dvonn_game_t game;
371
372     dvonn_game_init (&game);
373
374     gtk_init (&argc, &argv);
375
376     /* Create two views of the game (one for each player) */
377     dvonn_game_create_view (&game);
378     dvonn_game_create_view (&game);
379     
380     gtk_main ();
381
382     return 0;
383 }