X-Git-Url: https://git.cworth.org/git?p=dvonn;a=blobdiff_plain;f=dvonn.c;h=c1762ed19be159f67832ce71fe032e85e0331ee0;hp=616ebf35e7ee6f69a3dabb5c4a5fc421481a78a7;hb=HEAD;hpb=52fe7cdf6d86687cca4662a937b8e212cba94f6f diff --git a/dvonn.c b/dvonn.c index 616ebf3..c1762ed 100644 --- a/dvonn.c +++ b/dvonn.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2008 Carl Worth + * 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 @@ -17,7 +17,10 @@ * Author: Carl Worth */ +#define _GNU_SOURCE /* for vasprintf */ +#include #include +#include #include #include @@ -34,15 +37,15 @@ typedef struct { int cell_size; } layout_t; -typedef struct _loa_game loa_game_t; +typedef struct _dvonn_game dvonn_game_t; typedef struct { - loa_game_t *game; + dvonn_game_t *game; GtkWidget *window; layout_t layout; } view_t; -struct _loa_game { +struct _dvonn_game { view_t **views; int num_views; @@ -50,6 +53,12 @@ struct _loa_game { dvonn_bool_t has_selected; int selected_x; int selected_y; + + PangoFontDescription *font; + PangoFontDescription *ring_font; + + dvonn_bool_t dual_window_mode; + GtkWidget *windows[2]; }; static gboolean @@ -66,13 +75,20 @@ on_delete_event_quit (GtkWidget *widget, /* 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 + +#define RED_RING_COLOR 0.8, 0.2, 0.2 + +#define DVONN_FONT "sans" +#define DVONN_FONT_SIZE 12 + +/* Relative to a unit square. */ +#define RING_OUTER_RADIUS 0.4 +#define RING_INNER_RADIUS 0.2 +#define RING_FONT_SIZE (RING_INNER_RADIUS * 1.5) /* XXX: This really should have an interest rectangle. */ static void -loa_game_update_windows (loa_game_t *game) +dvonn_game_update_windows (dvonn_game_t *game) { int i; @@ -144,10 +160,23 @@ on_button_press_event (GtkWidget *widget, { view_t *view = user_data; layout_t *layout = &view->layout; - loa_game_t *game = view->game; + dvonn_game_t *game = view->game; int x, y; char *error; + /* Ignore events from the non-player. (Obviously when we add more + * interaction abilities, we will want to allow those even for the + * non-player---things like quit, etc.). */ + if (game->dual_window_mode && + widget->parent != game->windows[game->board.player]) + { + return TRUE; + } + + /* Ignore double and triple clicks. */ + if (event->type >= GDK_2BUTTON_PRESS) + return TRUE; + x = event->x; y = event->y; layout_device_to_board (layout, &x, &y); @@ -163,46 +192,50 @@ on_button_press_event (GtkWidget *widget, if (game->board.cells[x][y].type == DVONN_CELL_INVALID) return TRUE; - if (game->board.cells[x][y].type == DVONN_CELL_EMPTY) - game->board.cells[x][y].type = DVONN_CELL_WHITE; - else if (game->board.cells[x][y].type == DVONN_CELL_WHITE) - game->board.cells[x][y].type = DVONN_CELL_EMPTY; + if (game->board.phase == DVONN_PHASE_PLACEMENT) { + if (dvonn_board_place (&game->board, x, y, &error)) + dvonn_game_update_windows (game); + else + printf ("Illegal placement %c%d: %s\n", + 'a' + x, + y + 1, + error); - loa_game_update_windows (game); - - return TRUE; + return TRUE; + } 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); - } + if (dvonn_board_cell_owned_by (&game->board, x, y, game->board.player) && + ! dvonn_board_cell_surrounded (&game->board, x, y)) + { + game->has_selected = TRUE; + game->selected_x = x; + game->selected_y = y; + dvonn_game_update_windows (game); + } return TRUE; } - if (x == game->selected_x && - y == game->selected_y) + if (x == game->selected_x && y == game->selected_y) { game->has_selected = FALSE; - loa_game_update_windows (game); + dvonn_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); + dvonn_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, + game->selected_y + 1, 'a' + x, - DVONN_BOARD_Y_SIZE - y, + y + 1, error); } @@ -213,8 +246,73 @@ on_button_press_event (GtkWidget *widget, 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); + cairo_new_sub_path (cr); + cairo_arc (cr, 0.5, 0.5, RING_OUTER_RADIUS, 0, 2 * M_PI); + cairo_arc_negative (cr, 0.5, 0.5, RING_INNER_RADIUS, 2 * M_PI, 0); +} + +/* Some helper functions for using pango. */ +static PangoLayout * +_create_layout (cairo_t *cr, PangoFontDescription *font, const char *text) +{ + PangoLayout *layout; + + layout = pango_cairo_create_layout (cr); + pango_layout_set_font_description (layout, font); + pango_layout_set_text (layout, text, -1); + pango_layout_set_alignment (layout, PANGO_ALIGN_CENTER); + + return layout; +} + +#define PRINTF_FORMAT(fmt_index, va_index) __attribute__ ((__format__(__printf__, fmt_index, va_index))) + +static PangoLayout * +_create_layout_vprintf (cairo_t *cr, PangoFontDescription *font, const char *fmt, va_list ap) +{ + PangoLayout *layout; + char *text; + + vasprintf (&text, fmt, ap); + + layout = _create_layout (cr, font, text); + + free (text); + + return layout; +} + +static PangoLayout * +_create_layout_printf (cairo_t *cr, PangoFontDescription *font, const char *fmt, ...) + PRINTF_FORMAT (3, 4); + +static PangoLayout * +_create_layout_printf (cairo_t *cr, PangoFontDescription *font, const char *fmt, ...) +{ + va_list ap; + PangoLayout *layout; + + va_start (ap, fmt); + + layout = _create_layout_vprintf (cr, font, fmt, ap); + + va_end (ap); + + return layout; +} + +static void +_destroy_layout (PangoLayout *layout) +{ + g_object_unref (layout); +} + +static void +_show_layout (cairo_t *cr, PangoLayout *layout) +{ + pango_cairo_show_layout (cr, layout); + + _destroy_layout (layout); } static gboolean @@ -224,9 +322,10 @@ on_expose_event_draw (GtkWidget *widget, { view_t *view = user_data; layout_t *layout = &view->layout; - loa_game_t *game = view->game; + dvonn_game_t *game = view->game; cairo_t *cr; int x, y; + PangoLayout *to_move; if (layout->width != widget->allocation.width || layout->height != widget->allocation.height) @@ -247,13 +346,66 @@ on_expose_event_draw (GtkWidget *widget, layout->x_offset = (layout->width - x_size) / 2; layout->y_offset = (layout->height - y_size) / 2; + + if (game->ring_font == NULL) { + game->ring_font = pango_font_description_new (); + pango_font_description_set_family (game->ring_font, + DVONN_FONT); + } + pango_font_description_set_absolute_size (game->ring_font, + RING_FONT_SIZE * PANGO_SCALE); } cr = gdk_cairo_create (widget->window); - + cairo_set_source_rgb (cr, BACKGROUND_COLOR); cairo_paint (cr); + if (game->font == NULL) { + game->font = pango_font_description_new (); + pango_font_description_set_family (game->font, DVONN_FONT); + pango_font_description_set_absolute_size (game->font, DVONN_FONT_SIZE * PANGO_SCALE); + } + if (game->board.phase == DVONN_PHASE_GAME_OVER) { + if (game->board.score[DVONN_PLAYER_WHITE] > + game->board.score[DVONN_PLAYER_BLACK]) + { + to_move = _create_layout_printf (cr, game->font, + "White wins (%d to %d)\n", + game->board.score[DVONN_PLAYER_WHITE], + game->board.score[DVONN_PLAYER_BLACK]); + } + else if (game->board.score[DVONN_PLAYER_BLACK] > + game->board.score[DVONN_PLAYER_WHITE]) + { + to_move = _create_layout_printf (cr, game->font, + "Black wins (%d to %d)\n", + game->board.score[DVONN_PLAYER_BLACK], + game->board.score[DVONN_PLAYER_WHITE]); + } + else + { + to_move = _create_layout_printf (cr, game->font, + "Tie game (%d to %d)\n", + game->board.score[DVONN_PLAYER_WHITE], + game->board.score[DVONN_PLAYER_BLACK]); + + } + } else { + to_move = _create_layout_printf (cr, game->font, + "%s to %s.", + game->board.player == DVONN_PLAYER_WHITE ? + "White" : "Black", + game->board.phase == DVONN_PHASE_PLACEMENT ? + "place" : "move"); + } + cairo_move_to (cr, 2, 2); + if (game->board.player == DVONN_PLAYER_WHITE) + cairo_set_source_rgb (cr, 1.0, 1.0, 1.0); + else + cairo_set_source_rgb (cr, 0.0, 0.0, 0.0); + _show_layout (cr, to_move); + cairo_translate (cr, layout->x_offset, layout->y_offset); cairo_scale (cr, layout->cell_size, layout->cell_size); @@ -269,13 +421,49 @@ on_expose_event_draw (GtkWidget *widget, cairo_translate(cr, x + (y - DVONN_BOARD_Y_SIZE/2) / 2.0, M_SQRT1_2 * y); + if (cell.contains_red && cell.type != DVONN_CELL_RED) { + cairo_new_sub_path (cr); + cairo_arc (cr, 0.5, 0.5, + (RING_INNER_RADIUS + RING_OUTER_RADIUS)/2.0, + 0, 2 * M_PI); + cairo_set_source_rgb (cr, RED_RING_COLOR); + cairo_fill (cr); + } ring_path (cr); - if (cell.type == DVONN_CELL_WHITE) + switch (cell.type) { + case DVONN_CELL_WHITE: cairo_set_source_rgb (cr, 1.0, 1.0, 1.0); /* white */ - else + break; + case DVONN_CELL_BLACK: + cairo_set_source_rgb (cr, 0.0, 0.0, 0.0); /* black */ + break; + case DVONN_CELL_RED: + cairo_set_source_rgb (cr, RED_RING_COLOR); + break; + case DVONN_CELL_EMPTY: + default: cairo_set_source_rgba (cr, 0.0, 0.0, 0.2, 0.1); + break; + } + if (game->has_selected && + x == game->selected_x && + y == game->selected_y) + { + cairo_fill_preserve (cr); + cairo_set_source_rgba (cr, 0.2, 0.2, 1.0, 0.4); + } cairo_fill (cr); + if (game->board.cells[x][y].height > 1) { + PangoLayout *height; + cairo_move_to (cr, + 0.5 - 0.7 * RING_INNER_RADIUS * cos (M_PI_4), + 0.5 - 1.2 * RING_INNER_RADIUS * sin (M_PI_4)); + height = _create_layout_printf (cr, game->ring_font, "%d", + game->board.cells[x][y].height); + _show_layout (cr, height); + } + cairo_restore (cr); } } @@ -286,7 +474,7 @@ on_expose_event_draw (GtkWidget *widget, } static void -loa_game_init (loa_game_t *game) +dvonn_game_init (dvonn_game_t *game) { game->views = NULL; game->num_views = 0; @@ -294,10 +482,17 @@ loa_game_init (loa_game_t *game) game->has_selected = FALSE; dvonn_board_init (&game->board); + + game->font = NULL; + game->ring_font = NULL; + + game->dual_window_mode = 0; + game->windows[0] = NULL; + game->windows[1] = NULL; } static void -view_init (view_t *view, loa_game_t *game, GtkWidget *window) +view_init (view_t *view, dvonn_game_t *game, GtkWidget *window) { view->game = game; view->window = window; @@ -306,8 +501,8 @@ view_init (view_t *view, loa_game_t *game, GtkWidget *window) view->layout.height = 0; } -static void -loa_game_create_view (loa_game_t *game) +static GtkWidget* +dvonn_game_create_view (dvonn_game_t *game) { view_t *view; GtkWidget *window; @@ -349,20 +544,50 @@ loa_game_create_view (loa_game_t *game) G_CALLBACK (on_button_press_event), view); gtk_widget_show_all (window); + + return window; } int main (int argc, char *argv[]) { - loa_game_t game; + GtkWidget *window0, *window1; + GdkDisplay *display; + GdkScreen *screen; + dvonn_game_t game; - loa_game_init (&game); + dvonn_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); + /* Create a view for player 1. */ + window0 = dvonn_game_create_view (&game); + + /* Ugly little hack to get Xauthority data from keithp. Obviously + * won't work for any other user. + * + setenv ("XAUTHORITY", "/home/keithp/.Xauthority", 1); + */ + + /* Also ugly that localhost:10.0 is hard-coded, but this will work + * for many situations--both for when keithp attaches to my + * machine, and for when I connect to anyone's machine and then + * connect back, (assuming I haven't made other X forwardings + * first). + * + * Clearly we'll want some actual UI to select the right thing + * here. + */ + display = gdk_display_open ("localhost:10.0"); + if (display) { + screen = gdk_display_get_default_screen (display); + window1 = dvonn_game_create_view (&game); + gtk_window_set_screen (GTK_WINDOW (window1), screen); + + game.dual_window_mode = 1; + game.windows[0] = window0; + game.windows[1] = window1; + } gtk_main ();