/* -*- mode:C; tab-width:8; c-basic-offset:8; indent-tabs-mode:true -*- */ /* * Mines for GNOME * Author: Pista * * Score support: horape@compendium.com.ar * Mine Resizing: djb@redhat.com * * This game 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 2, 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, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 * USA */ #include #include #include #include #include "minefield.h" static struct { gint x; gint y; } neighbour_map[8] = { { -1, 1 }, { 0 , 1 }, { 1 , 1 }, { 1 , 0 }, { 1 , -1 }, { 0 , -1 }, { -1, -1 }, { -1, 0 } }; static guint16 num_colors[9][3] = { { 0x0000, 0x0000, 0x0000 }, /* Black, not used */ { 0x0000, 0x0000, 0xffff }, /* Blue */ { 0x0000, 0xa0a0, 0x0000 }, /* Green */ { 0xffff, 0x0000, 0x0000 }, /* Red */ { 0x0000, 0x0000, 0x7fff }, /* DarkBlue */ { 0xa0a0, 0x0000, 0x0000 }, /* DarkRed */ { 0x0000, 0xffff, 0xffff }, /* Cyan */ { 0xa0a0, 0x0000, 0xa0a0 }, /* DarkViolet */ { 0x0000, 0x0000, 0x0000 } /* Black */ }; time_t secs = 0; enum { MARKS_CHANGED_SIGNAL, EXPLODE_SIGNAL, LOOK_SIGNAL, UNLOOK_SIGNAL, WIN_SIGNAL, HINT_SIGNAL, LAST_SIGNAL }; static gint minefield_signals[LAST_SIGNAL] = { 0 }; static GtkWidgetClass *parent_class; /* Prototypes */ static inline gint cell_idx(GtkMineField *mfield, guint x, guint y); static void gtk_mine_draw(GtkMineField *mfield, guint x, guint y); static gint gtk_minefield_button_press(GtkWidget *widget, GdkEventButton *event); static gint gtk_minefield_button_release(GtkWidget *widget, GdkEventButton *event); static void gtk_minefield_check_field(GtkMineField *mfield, gint x, gint y); static void gtk_minefield_class_init (GtkMineFieldClass *class); static gint gtk_minefield_expose(GtkWidget *widget, GdkEventExpose *event); static void gtk_minefield_init (GtkMineField *mfield); static void gtk_minefield_lose(GtkMineField *mfield); static gint gtk_minefield_motion_notify(GtkWidget *widget, GdkEventMotion *event); static void gtk_minefield_multi_release (GtkMineField *mfield, guint x, guint y, guint c, guint really); static void gtk_minefield_randomize(GtkMineField *mfield, int curloc); static void gtk_minefield_realize(GtkWidget *widget); static void gtk_minefield_setup_signs(GtkMineField *mfield); static void gtk_minefield_show(GtkMineField *mfield, guint x, guint y); static void gtk_minefield_size_allocate(GtkWidget *widget, GtkAllocation *allocation); static void gtk_minefield_size_request(GtkWidget *widget, GtkRequisition *requisition); static void gtk_minefield_toggle_mark(GtkMineField *mfield, guint x, guint y); static void gtk_minefield_unrealize (GtkWidget *widget); static void gtk_minefield_win(GtkMineField *mfield); static inline int gtk_minefield_check_cell(GtkMineField *mfield, guint x, guint y); static void _setup_sign (sign *signp, const char *file, guint minesize); static inline void gtk_minefield_multi_press(GtkMineField *mfield, guint x, guint y, gint c); /* end prototypes */ static inline gint cell_idx(GtkMineField *mfield, guint x, guint y) { if (x>=0 && xxsize && y>=0 && yysize) return x+y*mfield->xsize; return -1; } static void _setup_sign (sign *signp, const char *file, guint minesize) { if (signp->pixbuf == NULL) { GError *error = NULL; signp->pixbuf = gdk_pixbuf_new_from_file (file, &error); if (signp->pixbuf == NULL) g_error (error->message); } else { g_object_unref (signp->scaledpixbuf); } signp->scaledpixbuf = gdk_pixbuf_scale_simple (signp->pixbuf, minesize - 2, minesize - 2, GDK_INTERP_BILINEAR); signp->width = gdk_pixbuf_get_width (signp->scaledpixbuf); signp->height = gdk_pixbuf_get_height (signp->scaledpixbuf); } static void gtk_minefield_setup_signs(GtkMineField *mfield) { _setup_sign(&mfield->flag, DATADIR"/pixmaps/gnomine/flag.svg", mfield->minesize); _setup_sign(&mfield->mine, DATADIR"/pixmaps/gnomine/mine.svg", mfield->minesize); _setup_sign(&mfield->question, DATADIR"/pixmaps/gnomine/flag-question.svg", mfield->minesize); } static void gtk_minefield_setup_numbers (GtkMineField *mfield) { int minesize, pixel_sz, i; static guint last_size = 0; minesize = mfield->minesize; pixel_sz = minesize - 2; if (pixel_sz > 999) pixel_sz = 999; if (last_size == pixel_sz) return; last_size = pixel_sz; for (i=0; i<9; i++) { gchar text[2]; PangoLayout *layout; PangoAttrList *alist; PangoAttribute *attr; PangoFontDescription *font_desc; PangoRectangle extent; guint64 font_size; /* free an existing layout ... */ if (mfield->numstr[i].layout) g_object_unref(mfield->numstr[i].layout); text[0] = '0' + i; text[1] = '\0'; layout = gtk_widget_create_pango_layout(GTK_WIDGET(mfield), text); /* set attributes for the layout */ alist = pango_attr_list_new(); /* colour */ attr = pango_attr_foreground_new(num_colors[i][0], num_colors[i][1], num_colors[i][2]); attr->start_index = 0; attr->end_index = G_MAXUINT; pango_attr_list_insert(alist, attr); /* do the font */ font_desc = pango_font_description_new(); pango_font_description_set_family(font_desc, "Mono 8"); /* Guess at the correct font size. */ font_size = pixel_sz * PANGO_SCALE; pango_font_description_set_size(font_desc, font_size); pango_font_description_set_weight(font_desc,PANGO_WEIGHT_BOLD); attr = pango_attr_font_desc_new(font_desc); attr->start_index = 0; attr->end_index = G_MAXUINT; pango_attr_list_insert(alist, attr); pango_layout_set_attributes(layout, alist); pango_layout_set_alignment(layout, PANGO_ALIGN_CENTER); /* Now correct the size to fit. */ pango_layout_get_extents (layout, &extent, NULL); font_size = font_size*0.7*pixel_sz*PANGO_SCALE/extent.height; pango_font_description_set_size(font_desc, font_size); attr = pango_attr_font_desc_new(font_desc); attr->start_index = 0; attr->end_index = G_MAXUINT; pango_attr_list_insert(alist, attr); pango_layout_set_attributes(layout, alist); pango_font_description_free(font_desc); pango_attr_list_unref(alist); mfield->numstr[i].layout = layout; pango_layout_get_extents (layout, NULL, &extent); /* The +1 is necessary since these coordinates are * with respect to minesize, not pixel_sz (the * difference is 2). */ mfield->numstr[i].dx = (pixel_sz - extent.width/PANGO_SCALE)/2 + 1; mfield->numstr[i].dy = (pixel_sz - extent.height/PANGO_SCALE)/2 + 1; } } static void gtk_minefield_realize(GtkWidget *widget) { GtkMineField *mfield; GdkWindowAttr attributes; gint attributes_mask; g_return_if_fail(widget != NULL); g_return_if_fail(GTK_IS_MINEFIELD (widget)); mfield = GTK_MINEFIELD(widget); GTK_WIDGET_SET_FLAGS(widget, GTK_REALIZED); attributes.window_type = GDK_WINDOW_CHILD; attributes.x = widget->allocation.x; attributes.y = widget->allocation.y; attributes.width = widget->allocation.width; attributes.height = widget->allocation.height; attributes.wclass = GDK_INPUT_OUTPUT; attributes.visual = gtk_widget_get_visual(widget); attributes.colormap = gtk_widget_get_colormap(widget); attributes.event_mask = gtk_widget_get_events(widget); attributes.event_mask |= GDK_EXPOSURE_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK | GDK_POINTER_MOTION_MASK; attributes_mask = GDK_WA_X | GDK_WA_Y | GDK_WA_VISUAL | GDK_WA_COLORMAP; widget->window = gdk_window_new(widget->parent->window, &attributes, attributes_mask); gdk_window_set_user_data(widget->window, mfield); widget->style = gtk_style_attach(widget->style, widget->window); gtk_style_set_background(widget->style, widget->window, GTK_STATE_ACTIVE); } static void gtk_minefield_unrealize (GtkWidget *widget) { g_return_if_fail (widget != NULL); g_return_if_fail (GTK_IS_MINEFIELD (widget)); if (GTK_WIDGET_CLASS (parent_class)->unrealize) (* GTK_WIDGET_CLASS (parent_class)->unrealize) (widget); } /* The frame makes sure that the minefield is allocated the correct size */ /* This is the standard allocate routine - it could be removed and the parents routine inherited */ static void gtk_minefield_size_allocate (GtkWidget *widget, GtkAllocation *allocation) { guint minesize, width, height; guint xofs, yofs; GtkMineField * mfield; widget->allocation = *allocation; mfield = GTK_MINEFIELD (widget); if (GTK_WIDGET_REALIZED (widget)) { minesize = MIN (allocation->width / mfield->xsize, allocation->height / mfield->ysize); mfield->minesize = minesize; width = mfield->xsize * minesize; height = mfield->ysize * minesize; xofs = allocation->x + (allocation->width - width)/2; yofs = allocation->y + (allocation->height - height)/2; gdk_window_move_resize (widget->window, xofs, yofs, width, height); } } static void gtk_minefield_size_request (GtkWidget *widget, GtkRequisition *requisition) { GtkMineField *mf = GTK_MINEFIELD (widget); /* request the minimum size - to allow the widget window to be resized */ requisition->width = mf->xsize * MINESIZE_MIN; requisition->height = mf->ysize * MINESIZE_MIN; } static void gtk_mine_draw(GtkMineField *mfield, guint x, guint y) { int c = cell_idx(mfield, x, y); int noshadow; gboolean clicked; int n; guint minesize; static GdkGC *dots; static char stipple_data[] = { 0x03, 0x03, 0x0c, 0x0c }; static GdkPixmap *stipple = NULL; GtkWidget *widget = GTK_WIDGET(mfield); GdkRectangle rect; g_return_if_fail (c != -1); /* This gives us a dotted line to increase the contrast between * buttons and the "sea". */ if (stipple == NULL) { stipple = gdk_bitmap_create_from_data (NULL, stipple_data, 4, 4); dots = gdk_gc_new (widget->window); gdk_gc_copy (dots, widget->style->dark_gc[2]); gdk_gc_set_stipple (dots, stipple); g_object_unref (stipple); gdk_gc_set_fill (dots, GDK_STIPPLED); } minesize = mfield->minesize; noshadow = mfield->mines[c].shown; clicked = mfield->mines[c].down; /* gtk_paint_box needs a clipping rectangle. */ rect.x = x*minesize; rect.y = y*minesize; rect.width = minesize; rect.height = minesize; if (noshadow) { /* draw grid on ocean floor */ gtk_paint_box (widget->style, widget->window, clicked ? GTK_STATE_ACTIVE : GTK_STATE_NORMAL, GTK_SHADOW_IN, &rect, widget, "button", x*minesize, y*minesize, minesize, minesize); if (y == 0) { /* top row only */ gdk_draw_line(widget->window, /* top */ dots, x*minesize, 0, x*minesize+minesize-1, 0); } if (x == 0) { /* left column only */ gdk_draw_line(widget->window, /* left */ dots, 0, y*minesize, 0, y*minesize+minesize-1); } gdk_draw_line(widget->window, /* right */ dots, x*minesize+minesize-1, y*minesize, x*minesize+minesize-1, y*minesize+minesize-1); gdk_draw_line(widget->window, /* bottom */ dots, x*minesize, y*minesize+minesize-1, x*minesize+minesize-1, y*minesize+minesize-1); } else { /* draw shadow around possible mine location */ gtk_paint_box (widget->style, widget->window, clicked ? GTK_STATE_ACTIVE : GTK_STATE_SELECTED, clicked ? GTK_SHADOW_IN : GTK_SHADOW_OUT, &rect, widget, "button", x*minesize, y*minesize, minesize, minesize); } if (mfield->mines[c].shown && !mfield->mines[c].mined) { if ((n = mfield->mines[c].neighbours) != 0) { g_assert (n >= 0 && n <= 9); gdk_draw_layout(widget->window, widget->style->black_gc, x*minesize + mfield->numstr[n].dx, y*minesize + mfield->numstr[n].dy+1, PANGO_LAYOUT(mfield->numstr[n].layout)); } } else if (mfield->mines[c].marked == MINE_QUESTION) { gdk_draw_pixbuf (widget->window, NULL, mfield->question.scaledpixbuf, 0, 0, x * minesize + (minesize - mfield->flag.width) / 2, y * minesize + (minesize - mfield->flag.height) / 2, mfield->flag.width, mfield->flag.height, GDK_RGB_DITHER_NORMAL, 0, 0); } else if (mfield->mines[c].marked == MINE_MARKED) { gdk_draw_pixbuf (widget->window, NULL, mfield->flag.scaledpixbuf, 0, 0, x * minesize + (minesize - mfield->flag.width) / 2, y * minesize + (minesize - mfield->flag.height) / 2, mfield->flag.width, mfield->flag.height, GDK_RGB_DITHER_NORMAL, 0, 0); if (mfield->lose && mfield->mines[c].mined != 1) { gdk_draw_line(widget->window, widget->style->black_gc, x*minesize+2, y*minesize+3, x*minesize+minesize-4, y*minesize+minesize-3); gdk_draw_line(widget->window, widget->style->black_gc, x*minesize+3, y*minesize+2, x*minesize+minesize-3, y*minesize+minesize-4); gdk_draw_line(widget->window, widget->style->black_gc, x*minesize+2, y*minesize+minesize-4, x*minesize+minesize-4, y*minesize+2); gdk_draw_line(widget->window, widget->style->black_gc, x*minesize+3, y*minesize+minesize-3, x*minesize+minesize-3, y*minesize+3); } } else if (mfield->lose && mfield->mines[c].mined) { gdk_draw_pixbuf (widget->window, NULL, mfield->mine.scaledpixbuf, 0, 0, x * minesize + (minesize - mfield->flag.width) / 2, y * minesize + (minesize - mfield->flag.height) / 2, mfield->flag.width, mfield->flag.height, GDK_RGB_DITHER_NORMAL, 0, 0); } } static gint gtk_minefield_expose (GtkWidget *widget, GdkEventExpose *event) { g_return_val_if_fail (widget != NULL, FALSE); g_return_val_if_fail (GTK_IS_MINEFIELD (widget), FALSE); g_return_val_if_fail (event != NULL, FALSE); if (GTK_WIDGET_DRAWABLE (widget)) { guint x1, y1, x2, y2, x, y; GtkMineField *mfield = GTK_MINEFIELD (widget); GdkRectangle *area = &event->area; /* mine square numbers must be resized to fit the mine size */ gtk_minefield_setup_signs (mfield); gtk_minefield_setup_numbers (mfield); if (area) { x1 = area->x/mfield->minesize; y1 = area->y/mfield->minesize; x2 = (area->x + area->width - 1) / mfield->minesize; y2 = (area->y + area->height - 1) / mfield->minesize; } else { x1 = 0; y1 = 0; x2 = mfield->xsize - 1; y2 = mfield->ysize - 1; } /* These are necessary because we get an expose call before a * resize at the old size, but after we have changed our data. */ if (x2 >= mfield->xsize) x2 = mfield->xsize - 1; if (y2 >= mfield->ysize) y2 = mfield->ysize - 1; for (x = x1; x <= x2; x++) for (y = y1; y <= y2; y++) gtk_mine_draw (mfield, x, y); } return FALSE; } static inline int gtk_minefield_check_cell(GtkMineField *mfield, guint x, guint y) { guint changed; gint c; guint i; gint nx, ny; changed = 0; for (i=0; i<8; i++) { nx = x+neighbour_map[i].x; ny = y+neighbour_map[i].y; if ((c = cell_idx(mfield, nx, ny)) != -1) { if (mfield->mines[c].shown == 0 && mfield->mines[c].marked == MINE_NOMARK) { mfield->mines[c].shown = 1; mfield->shown++; gtk_mine_draw(mfield, nx, ny); changed = 1; } } } return changed; } static void gtk_minefield_check_field(GtkMineField *mfield, gint x, gint y) { guint c; guint changed; gint x1, y1, x2, y2; gint cx1, cx2, cy1, cy2; cx1 = cx2 = x; cy1 = cy2 = y; do { x1 = cx1-1; y1 = cy1-1; x2 = cx2+1; y2 = cy2+1; if (x1 < 0) x1 = 0; if (y1 < 0) y1 = 0; if (x2 >= mfield->xsize) x2 = mfield->xsize-1; if (y2 >= mfield->ysize) y2 = mfield->ysize-1; changed = 0; for (x=x1; x<=x2; x++) { for (y=y1; y<=y2; y++) { c = cell_idx(mfield, x, y); if (mfield->mines[c].neighbours == 0 && mfield->mines[c].shown == 1) { changed |= gtk_minefield_check_cell(mfield, x, y); if (changed) { if (x < cx1) cx1 = x; if (x > cx2) cx2 = x; if (y < cy1) cy1 = y; if (y > cy2) cy2 = y; } } } } } while (changed); } static void gtk_minefield_lose (GtkMineField *mfield) { g_signal_emit (G_OBJECT (mfield), minefield_signals[EXPLODE_SIGNAL], 0, NULL ); mfield->lose = 1; gtk_widget_queue_draw (GTK_WIDGET (mfield)); } static void gtk_minefield_win(GtkMineField *mfield) { guint x, y, c; /* mark any unmarked mines and update displayed total */ for (x = 0; x < mfield->xsize; x++) { for (y = 0; y < mfield->ysize; y++) { c = x + y * mfield->xsize; if (mfield->mines[c].shown == 0 && /* not shown & not marked */ mfield->mines[c].marked != MINE_MARKED) { mfield->mines[c].marked = MINE_MARKED; /* mark it */ gtk_mine_draw(mfield, x, y); /* draw it */ mfield->flag_count++; /* up the count */ g_signal_emit(GTK_OBJECT(mfield), /* display the count */ minefield_signals[MARKS_CHANGED_SIGNAL], 0, NULL); } } } /* now stop the clock. (MARKS_CHANGED_SIGNAL starts it) */ g_signal_emit(GTK_OBJECT(mfield), minefield_signals[WIN_SIGNAL], 0, NULL); mfield->win = 1; } static void gtk_minefield_randomize (GtkMineField *mfield, int curloc) { guint i; guint x, y; guint n; guint cidx; /* randomly set the mines, but avoid the current location (why ?)*/ for (n = 0; n < mfield->mcount; ) { i = g_rand_int_range (mfield->grand, 0, mfield->xsize * mfield->ysize); if (!mfield->mines[i].mined && i != curloc) { mfield->mines[i].mined = 1; n++; } } /* load neighborhood numbers */ for (x=0; xxsize; x++) { for (y=0; yysize; y++) { n = 0; for (i=0; i<8; i++) { if (((cidx = cell_idx(mfield, x + neighbour_map[i].x, y+neighbour_map[i].y)) != -1) && mfield->mines[cidx].mined) { n++; } } mfield->mines[x+mfield->xsize * y].neighbours = n; } } } static void gtk_minefield_show(GtkMineField *mfield, guint x, guint y) { int c = cell_idx (mfield, x, y); g_return_if_fail (c != -1); /* make sure first click isn't on a mine */ if (!mfield->in_play) { mfield->in_play = 1; gtk_minefield_randomize(mfield, c); } if (mfield->mines[c].marked != MINE_MARKED && mfield->mines[c].shown != 1) { mfield->mines[c].shown = 1; mfield->shown++; gtk_mine_draw(mfield, mfield->cdownx, mfield->cdowny); if(mfield->mines[c].mined == 1) { gtk_minefield_lose(mfield); } else { gtk_minefield_check_field(mfield, x, y); if (mfield->shown == mfield->xsize*mfield->ysize-mfield->mcount) { gtk_minefield_win(mfield); } } } } static void gtk_minefield_toggle_mark(GtkMineField *mfield, guint x, guint y) { int c = cell_idx(mfield, x, y); g_return_if_fail (c != -1); if (mfield->mines[c].shown != 0) { /* nothing to toggle */ return; } switch (mfield->mines[c].marked) { case MINE_NOMARK: /* If we've used all the flags don't plant any more, * this should be an indication to the player that they * have made a mistake. */ if (mfield->flag_count == mfield->mcount) { if (mfield->use_question_marks) { mfield->mines[c].marked = MINE_QUESTION; } break; } mfield->mines[c].marked = MINE_MARKED; mfield->flag_count++; break; case MINE_MARKED: if (mfield->use_question_marks) { mfield->mines[c].marked = MINE_QUESTION; } else { mfield->mines[c].marked = MINE_NOMARK; } mfield->flag_count--; break; case MINE_QUESTION: mfield->mines[c].marked = MINE_NOMARK; break; default: /* better not get here! */ break; } g_signal_emit(GTK_OBJECT(mfield), minefield_signals[MARKS_CHANGED_SIGNAL], 0, NULL); } static inline void gtk_minefield_multi_press(GtkMineField *mfield, guint x, guint y, gint c) { guint i; gint nx, ny, c2; for (i=0; i<8; i++) { nx = x+neighbour_map[i].x; ny = y+neighbour_map[i].y; if ((c2 = cell_idx(mfield, nx, ny)) == -1) continue; if (mfield->mines[c2].marked != MINE_MARKED && !mfield->mines[c2].shown) { mfield->mines[c2].down = 1; gtk_mine_draw(mfield, nx, ny); } } mfield->multi_mode = 1; } static void gtk_minefield_multi_release (GtkMineField *mfield, guint x, guint y, guint c, guint really) { gint n, nx, ny, i, c2; guint lose = 0; mfield->multi_mode = 0; n = 0; for (i=0; i<8; i++) { nx = x+neighbour_map[i].x; ny = y+neighbour_map[i].y; if ((c2 = cell_idx(mfield, nx, ny)) == -1) continue; if (mfield->mines[c2].marked == MINE_MARKED) n++; } if (mfield->mines[c].neighbours != n || mfield->mines[c].marked == MINE_MARKED || !mfield->mines[c].shown) really = 0; for (i=0; i<8; i++) { nx = x+neighbour_map[i].x; ny = y+neighbour_map[i].y; if ((c2 = cell_idx(mfield, nx, ny)) == -1) continue; if (mfield->mines[c2].down) { mfield->mines[c2].down = 0; if (really && (mfield->mines[c2].shown == 0)) { mfield->mines[c2].shown = 1; mfield->shown++; if (mfield->mines[c2].mined == 1) { lose = 1; } } gtk_mine_draw(mfield, nx ,ny); } } if (lose) { gtk_minefield_lose(mfield); } else if (really) { gtk_minefield_check_field(mfield, x, y); if (mfield->shown == mfield->xsize*mfield->ysize-mfield->mcount) { gtk_minefield_win(mfield); } } } static gint gtk_minefield_motion_notify(GtkWidget *widget, GdkEventMotion *event) { GtkMineField *mfield; guint x, y; guint c; guint multi; guint minesize; g_return_val_if_fail(widget != NULL, 0); g_return_val_if_fail(GTK_IS_MINEFIELD(widget), 0); g_return_val_if_fail(event != NULL, 0); mfield = GTK_MINEFIELD(widget); minesize = mfield->minesize; if (mfield->lose || mfield->win) return FALSE; if (mfield->bdown[0] || mfield->bdown[1]) { x = event->x/minesize; y = event->y/minesize; if (x < 0 || y < 0 || x > mfield->xsize-1 || y > mfield->ysize-1) return 0; c = cell_idx (mfield, x, y); g_assert (c != -1); if (c != mfield->cdown) { mfield->mines[mfield->cdown].down = 0; gtk_mine_draw(mfield, mfield->cdownx, mfield->cdowny); multi = mfield->multi_mode; if (multi) gtk_minefield_multi_release(mfield, mfield->cdownx, mfield->cdowny, mfield->cdown, 0); mfield->cdownx = x; mfield->cdowny = y; mfield->cdown = c; mfield->mines[c].down = 1; gtk_mine_draw(mfield, x, y); if (multi) gtk_minefield_multi_press(mfield, x, y, c); } } return FALSE; } static gint gtk_minefield_button_press(GtkWidget *widget, GdkEventButton *event) { GtkMineField *mfield; guint x, y; guint c; guint minesize; g_return_val_if_fail(widget != NULL, 0); g_return_val_if_fail(GTK_IS_MINEFIELD(widget), 0); g_return_val_if_fail(event != NULL, 0); mfield = GTK_MINEFIELD(widget); minesize = mfield->minesize; if (mfield->lose || mfield->win) return FALSE; if (event->button <= 3 && !mfield->bdown[1]) { x = event->x/minesize; y = event->y/minesize; c = cell_idx (mfield, x, y); if (c == -1) return FALSE; if (!mfield->bdown[0] && !mfield->bdown[1] && !mfield->bdown[2]) { mfield->cdownx = x; mfield->cdowny = y; mfield->cdown = c; mfield->mines[c].down = 1; } mfield->bdown[event->button-1]++; gtk_mine_draw(mfield, x, y); if (((event->button == 2) || (event->button == 1 && event->state & GDK_SHIFT_MASK)) || (mfield->bdown[0] && mfield->bdown[2]) ) { /* multi show */ gtk_minefield_multi_press(mfield, x, y, c); } else if (event->button == 3 && mfield->bdown[2] == 1) { gtk_minefield_toggle_mark(mfield, x, y); gtk_mine_draw(mfield, x, y); } if (event->button == 1 || event->button == 2) { g_signal_emit(GTK_OBJECT(mfield), minefield_signals[LOOK_SIGNAL], 0, NULL); } } return FALSE; } static gint gtk_minefield_button_release(GtkWidget *widget, GdkEventButton *event) { GtkMineField *mfield; g_return_val_if_fail(widget != NULL, FALSE); g_return_val_if_fail(GTK_IS_MINEFIELD(widget), FALSE); g_return_val_if_fail(event != NULL, FALSE); mfield = GTK_MINEFIELD(widget); if (mfield->lose || mfield->win) return FALSE; if (event->button <= 3 && mfield->bdown[event->button-1]) { if (mfield->bdown[0] && mfield->bdown[2] && event->button != 2) { /* left+right click = multi show */ mfield->bdown[0] = 0; mfield->bdown[1] = 1; mfield->bdown[2] = 0; event->button = 2; } switch (event->button) { case 1: if (event->state & GDK_SHIFT_MASK) gtk_minefield_multi_release(mfield, mfield->cdownx, mfield->cdowny, mfield->cdown, 1); else gtk_minefield_show(mfield, mfield->cdownx, mfield->cdowny); break; case 2: if (mfield->multi_mode) gtk_minefield_multi_release(mfield, mfield->cdownx, mfield->cdowny, mfield->cdown, 1); break; } if (!mfield->lose && !mfield->win) { g_signal_emit(GTK_OBJECT(mfield), minefield_signals[UNLOOK_SIGNAL], 0, NULL); } mfield->mines[mfield->cdown].down = 0; mfield->cdown = -1; mfield->bdown[event->button-1] = 0; gtk_mine_draw(mfield, mfield->cdownx, mfield->cdowny); } return FALSE; } static void gtk_minefield_class_init (GtkMineFieldClass *class) { GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (class); GtkObjectClass *object_class = GTK_OBJECT_CLASS (class); parent_class = gtk_type_class (gtk_widget_get_type ()); widget_class->realize = gtk_minefield_realize; widget_class->unrealize = gtk_minefield_unrealize; widget_class->size_allocate = gtk_minefield_size_allocate; widget_class->size_request = gtk_minefield_size_request; widget_class->expose_event = gtk_minefield_expose; widget_class->button_press_event = gtk_minefield_button_press; widget_class->button_release_event = gtk_minefield_button_release; widget_class->motion_notify_event = gtk_minefield_motion_notify; class->marks_changed = NULL; class->explode = NULL; class->look = NULL; class->unlook = NULL; class->win = NULL; minefield_signals[MARKS_CHANGED_SIGNAL] = g_signal_new("marks_changed", G_OBJECT_CLASS_TYPE (object_class), G_SIGNAL_RUN_FIRST, G_STRUCT_OFFSET(GtkMineFieldClass, marks_changed), NULL, NULL, g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0); minefield_signals[EXPLODE_SIGNAL] = g_signal_new("explode", G_OBJECT_CLASS_TYPE (object_class), G_SIGNAL_RUN_FIRST, G_STRUCT_OFFSET(GtkMineFieldClass, explode), NULL, NULL, g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0); minefield_signals[LOOK_SIGNAL] = g_signal_new("look", G_OBJECT_CLASS_TYPE (object_class), G_SIGNAL_RUN_FIRST, G_STRUCT_OFFSET(GtkMineFieldClass, look), NULL, NULL, g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0); minefield_signals[UNLOOK_SIGNAL] = g_signal_new("unlook", G_OBJECT_CLASS_TYPE (object_class), G_SIGNAL_RUN_FIRST, G_STRUCT_OFFSET(GtkMineFieldClass, unlook), NULL, NULL, g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0); minefield_signals[WIN_SIGNAL] = g_signal_new("win", G_OBJECT_CLASS_TYPE (object_class), G_SIGNAL_RUN_FIRST, G_STRUCT_OFFSET(GtkMineFieldClass, win), NULL, NULL, g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0); minefield_signals[HINT_SIGNAL] = g_signal_new("hint-used", G_OBJECT_CLASS_TYPE (object_class), G_SIGNAL_RUN_FIRST, G_STRUCT_OFFSET(GtkMineFieldClass, hint_used), NULL, NULL, g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0); } static void gtk_minefield_init (GtkMineField *mfield) { mfield->xsize = 0; mfield->ysize = 0; mfield->mines = NULL; mfield->started = FALSE; mfield->cdown = -1; mfield->flag.pixbuf = NULL; mfield->mine.pixbuf = NULL; mfield->question.pixbuf = NULL; mfield->grand = g_rand_new (); } void gtk_minefield_set_size(GtkMineField *mfield, guint xsize, guint ysize) { g_return_if_fail(mfield != NULL); g_return_if_fail(GTK_IS_MINEFIELD(mfield)); if (mfield->xsize*mfield->ysize != xsize*ysize) { mfield->mines = g_realloc(mfield->mines, sizeof(mine)*xsize*ysize); } if (mfield->xsize != xsize || mfield->ysize != ysize) { mfield->xsize = xsize; mfield->ysize = ysize; if (GTK_WIDGET_VISIBLE(mfield)) { gtk_widget_queue_resize(GTK_WIDGET(mfield)); } } } GtkWidget* gtk_minefield_new (void) { return GTK_WIDGET (g_object_new (GTK_TYPE_MINEFIELD, NULL)); } GType gtk_minefield_get_type (void) { static GType minefield_type = 0; if (minefield_type == 0) { static const GTypeInfo minefield_info = { sizeof (GtkMineFieldClass), NULL, /* base_init */ NULL, /* base_finalize */ (GClassInitFunc) gtk_minefield_class_init, NULL, /* class_finalize */ NULL, /* class_data */ sizeof (GtkMineField), 0, /* n_preallocs */ (GInstanceInitFunc) gtk_minefield_init, }; minefield_type = g_type_register_static (GTK_TYPE_WIDGET, "GtkMineField", &minefield_info, 0); } return minefield_type; } void gtk_minefield_restart (GtkMineField *mfield) { guint i; g_return_if_fail (mfield != NULL); g_return_if_fail (GTK_IS_MINEFIELD (mfield)); mfield->flag_count = 0; mfield->shown = 0; mfield->lose = 0; mfield->win = 0; mfield->bdown[0] = 0; mfield->bdown[1] = 0; mfield->bdown[2] = 0; mfield->cdown = -1; mfield->multi_mode = 0; mfield->in_play = 0; for (i=0; i < mfield->xsize * mfield->ysize; i++) { mfield->mines[i].marked = MINE_NOMARK; mfield->mines[i].mined = 0; mfield->mines[i].shown = 0; mfield->mines[i].down = 0; } if (mfield->started == FALSE) mfield->started = TRUE; else gtk_widget_queue_draw (GTK_WIDGET (mfield)); } void gtk_minefield_set_use_question_marks(GtkMineField *mfield, gboolean use_question_marks) { g_return_if_fail(mfield != NULL); g_return_if_fail(GTK_IS_MINEFIELD(mfield)); mfield->use_question_marks = use_question_marks; } /* Hunt for a hint to give the player. Revealed squares are handled here, * everything else is passed back up. The comments below detail the * strategy for revealing squares. */ gint gtk_minefield_hint (GtkMineField *mfield) { gint i,x,y; gint a,c; mine * m; guint ncase1, ncase2, ncase3; guint *case1list, *case2list, *case3list; guint *case1ptr, *case2ptr, *case3ptr; gint size; g_return_val_if_fail (mfield != NULL, MINEFIELD_HINT_NO_GAME); g_return_val_if_fail (GTK_IS_MINEFIELD(mfield), MINEFIELD_HINT_NO_GAME); if (!mfield->in_play) return MINEFIELD_HINT_NO_GAME; /* We search for three cases: * * Case 1: we look for squares adjacent to both a mine and a revealed * square since these are most likely to help the player and resolve * ambiguous situations. * * Case 2: we look for squares that are adjacent to a mine * (this will only occur in the rare case that a square is completely * encircled by mines, but at that point this case is probably * useful). * * Case 3: we look for any unrevealed square without a mine (as a * consequence of the previous cases this won't be adjacent to a * mine). */ /* This code is pretty diabolical, * Yet it is perfectly logical, * Is it C ? * Is it me ? * Or is it just pathological ? */ size = mfield->xsize*mfield->ysize; case1ptr = case1list = g_malloc (size*sizeof(guint)); case2ptr = case2list = g_malloc (size*sizeof(guint)); case3ptr = case3list = g_malloc (size*sizeof(guint)); ncase1 = ncase2 = ncase3 = 0; g_return_val_if_fail (case1ptr && case2ptr && case3ptr, MINEFIELD_HINT_NO_GAME); for (i = 0; imines + i; if (!m->mined && !m->marked && !m->shown) { ncase3++; *case3ptr++ = i; if (m->neighbours > 0) { ncase2++; *case2ptr++ = i; for (a = 0; a < 8; a++) { x = i % mfield->xsize; y = i / mfield->ysize; c = cell_idx (mfield, x + neighbour_map[a].x, y + neighbour_map[a].y); if ((c != -1) && mfield->mines[c].shown) { ncase1++; *case1ptr++ = i; } } } } } if (ncase1 > 0) { a = g_rand_int_range (mfield->grand, 0, ncase1); i = case1list[a]; } else if (ncase2 > 0) { a = g_rand_int_range (mfield->grand, 0, ncase2); i = case2list[a]; } else if (ncase3 > 0) { a = g_rand_int_range (mfield->grand, 0, ncase3); i = case3list[a]; } else return MINEFIELD_HINT_ALL_MINES; x = i % mfield->xsize; y = i / mfield->xsize; /* Makes sure that the program knows about the successful * hint before a possible win. */ g_signal_emit(GTK_OBJECT(mfield), minefield_signals[HINT_SIGNAL], 0, NULL); gtk_minefield_show (mfield, x, y); gtk_mine_draw (mfield, x, y); return MINEFIELD_HINT_ACCEPTED; }