]> git.cworth.org Git - dvonn/blob - dvonn.c
43de664e9e362d9aa56d57ef9847d470a7f9e963
[dvonn] / dvonn.c
1 /*
2  * Copyright (C) 2008 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 _loa_game loa_game_t;
38
39 typedef struct {
40     loa_game_t *game;
41     GtkWidget *window;
42     layout_t layout;
43 } view_t;
44
45 struct _loa_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 loa_game_update_windows (loa_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 static gboolean
84 on_button_press_event (GtkWidget        *widget,
85                        GdkEventButton   *event,
86                        gpointer          user_data)
87 {
88     view_t *view = user_data;
89     layout_t *layout = &view->layout;
90     loa_game_t *game = view->game;
91     int x, y;
92     char *error;
93
94     x = (event->x - layout->x_offset) / layout->cell_size;
95     y = (event->y - layout->y_offset) / layout->cell_size;
96
97     if (! game->has_selected) {
98         if (game->board.cells[x][y].type == game->board.player) {
99             game->has_selected = TRUE;
100             game->selected_x = x;
101             game->selected_y = y;
102             loa_game_update_windows (game);
103         }
104         return TRUE;
105     }
106
107     /* Do nothing for out-of-bounds clicks */
108     if (x < 0 || x >= DVONN_BOARD_X_SIZE ||
109         y < 0 || y >= DVONN_BOARD_Y_SIZE)
110     {
111         return TRUE;
112     }
113
114     if (x == game->selected_x &&
115         y == game->selected_y)
116     {
117         game->has_selected = FALSE;
118         loa_game_update_windows (game);
119         return TRUE;
120     }
121         
122     if (dvonn_board_move (&game->board,
123                           game->selected_x, game->selected_y,
124                           x, y, &error))
125     {
126         game->has_selected = FALSE;
127         loa_game_update_windows (game);
128         return TRUE;
129     } else {
130         printf ("Illegal move %c%d%c%d: %s\n",
131                 'a' + game->selected_x,
132                 DVONN_BOARD_Y_SIZE - game->selected_y,
133                 'a' + x,
134                 DVONN_BOARD_Y_SIZE - y,
135                 error);
136     }
137
138     return TRUE;
139 }
140
141 /* Add a unit-sized DVONN-ring path to cr, from (0,0) to (1,1). */
142 static void
143 ring_path (cairo_t *cr)
144 {
145     cairo_arc (cr, 0.5, 0.5, 0.4, 0, 2 * M_PI);
146     cairo_arc_negative (cr, 0.5, 0.5, 0.2, 2 * M_PI, 0);
147 }
148
149 static gboolean
150 on_expose_event_draw (GtkWidget         *widget,
151                       GdkEventExpose    *event,
152                       gpointer           user_data)
153 {
154     view_t *view = user_data;
155     layout_t *layout = &view->layout;
156     loa_game_t *game = view->game;
157     cairo_t *cr;
158     int x, y;
159
160     if (layout->width != widget->allocation.width ||
161         layout->height != widget->allocation.height)
162     {
163         int x_size, y_size;
164
165         layout->width = widget->allocation.width;
166         layout->height = widget->allocation.height;
167
168         x_size = layout->width;
169         if (x_size > layout->height * BOARD_X_SIZE / (1 + M_SQRT1_2 * (BOARD_Y_SIZE-1)))
170             x_size = layout->height * BOARD_X_SIZE / (1 + M_SQRT1_2 * (BOARD_Y_SIZE-1));
171
172         /* Size must be a multiple of the integer cell_size */
173         layout->cell_size = x_size / BOARD_X_SIZE;
174         x_size = layout->cell_size * BOARD_X_SIZE;
175         y_size = layout->cell_size * (1 + M_SQRT1_2 * (BOARD_Y_SIZE-1));
176
177         layout->x_offset = (layout->width - x_size) / 2;
178         layout->y_offset = (layout->height - y_size) / 2;
179     }
180
181     cr = gdk_cairo_create (widget->window);
182     
183     cairo_set_source_rgb (cr, BACKGROUND_COLOR);
184     cairo_paint (cr);
185
186     cairo_translate (cr, layout->x_offset, layout->y_offset);
187     cairo_scale (cr, layout->cell_size, layout->cell_size);
188
189     for (y = 0; y < BOARD_Y_SIZE; y++) {
190         for (x = 0; x < BOARD_X_SIZE; x++) {
191             dvonn_cell_t cell;
192
193             cell = game->board.cells[x][y];
194             if (cell.type == DVONN_CELL_INVALID)
195                 continue;
196
197             cairo_save (cr);
198             cairo_translate(cr,
199                             x + (y - DVONN_BOARD_Y_SIZE/2) / 2.0,
200                             M_SQRT1_2 * y);
201             ring_path (cr);
202             cairo_set_source_rgba (cr, 0.0, 0.0, 0.2, 0.1);
203             cairo_fill (cr);
204
205             cairo_restore (cr);
206         }
207     }
208
209     cairo_destroy (cr);
210
211     return TRUE;
212 }
213
214 static void
215 loa_game_init (loa_game_t *game)
216 {
217     game->views = NULL;
218     game->num_views = 0;
219
220     game->has_selected = FALSE;
221
222     dvonn_board_init (&game->board);
223 }
224
225 static void
226 view_init (view_t *view, loa_game_t *game, GtkWidget *window)
227 {
228     view->game = game;
229     view->window = window;
230
231     view->layout.width = 0;
232     view->layout.height = 0;
233 }
234
235 static void
236 loa_game_create_view (loa_game_t *game)
237 {
238     view_t *view;
239     GtkWidget *window;
240     GtkWidget *drawing_area;
241
242     window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
243
244     view = malloc (sizeof (view_t));
245     if (view == NULL) {
246         fprintf (stderr, "Out of memory.\n");
247         exit (1);
248     }
249
250     game->num_views++;
251     game->views = realloc (game->views,
252                            game->num_views * sizeof (view_t *));
253     if (game->views == NULL) {
254         fprintf (stderr, "Out of memory.\n");
255         exit (1);
256     }
257     game->views[game->num_views - 1] = view;
258
259     view_init (view, game, window);
260
261     gtk_window_set_default_size (GTK_WINDOW (window), 780, 251);
262
263     g_signal_connect (window, "delete-event",
264                       G_CALLBACK (on_delete_event_quit), NULL);
265
266     drawing_area = gtk_drawing_area_new ();
267
268     gtk_container_add (GTK_CONTAINER (window), drawing_area);
269
270     g_signal_connect (drawing_area, "expose-event",  
271                       G_CALLBACK (on_expose_event_draw), view);
272
273     gtk_widget_add_events (drawing_area, GDK_BUTTON_PRESS_MASK);
274     g_signal_connect (drawing_area, "button-press-event",
275                       G_CALLBACK (on_button_press_event), view);
276
277     gtk_widget_show_all (window);
278 }
279
280 int
281 main (int argc, char *argv[])
282 {
283     loa_game_t game;
284
285     loa_game_init (&game);
286
287     gtk_init (&argc, &argv);
288
289     /* Create two views of the game (one for each player) */
290     loa_game_create_view (&game);
291     loa_game_create_view (&game);
292     
293     gtk_main ();
294
295     return 0;
296 }