#include <time.h>
#include <gtk/gtk.h>
#include <gdk-pixbuf/gdk-pixbuf.h>
#include <gnome.h>
#include "minefield.h"
#include "flag.xpm"
#include "mine.xpm"

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 int num_colors[9][3] = {
	{ 0  , 0  , 0   }, /* Black, not used */
	{ 0  , 0  , 255 }, /* Blue  */
	{ 0  , 160, 0   }, /* Green */
	{ 255, 0  , 0   }, /* Red   */
	{ 0  , 0  , 127 }, /* DarkBlue */
	{ 160, 0  , 0   }, /* DarkRed   */
	{ 0  , 255, 255 }, /* Cyan */
	{ 160, 0  , 160 }, /* DarkViolet */
	{ 0  , 0  , 0   }  /* Black */
};


time_t secs = 0;

enum {
	MARKS_CHANGED_SIGNAL,
	EXPLODE_SIGNAL,
	LOOK_SIGNAL,
	UNLOOK_SIGNAL,
	WIN_SIGNAL,
	LAST_SIGNAL
};

static gint minefield_signals[LAST_SIGNAL] = { 0 };

static GtkWidgetClass *parent_class;

static inline gint cell_idx (MineField *data, guint x, guint y)
{
	if (x >= 0 && x < data->xsize && y >= 0 && y < data->ysize)
		return x + y * data->xsize;
	return -1;
}

static void _setup_sign (sign *signp, char **data, guint minesize)
{
	GdkPixbuf *image;

        image = gdk_pixbuf_new_from_xpm_data ((const char **)data);
	gdk_pixbuf_render_pixmap_and_mask (image, &(signp->pixmap),
					   &(signp->mask), 128);

	gdk_pixbuf_unref (image);
        gdk_window_get_size(signp->pixmap, &(signp->width), &(signp->height));
}

static void gtk_minefield_setup_signs(GtkWidget *widget)
{
        GtkMineFieldView *mfield;
  
        mfield = GTK_MINEFIELD(widget);
  
        _setup_sign(&mfield->flag, flag_xpm, mfield->minesize);
        _setup_sign(&mfield->mine, mine_xpm, mfield->minesize);
}

static void gtk_minefield_realize(GtkWidget *widget)
{
        GtkMineFieldView *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);

        gtk_minefield_setup_signs(widget);

        mfield->cc = gdk_color_context_new (gtk_widget_get_visual (widget),
					    gtk_widget_get_colormap (widget));
}

static void gtk_minefield_unrealize (GtkWidget *widget)
{
	GtkMineFieldView *mfield;

	g_return_if_fail (widget != NULL);
	g_return_if_fail (GTK_IS_MINEFIELD (widget));

	mfield = GTK_MINEFIELD (widget);

	gdk_color_context_free (mfield->cc);
	mfield->cc = NULL;

	if (GTK_WIDGET_CLASS (parent_class)->unrealize)
		(* GTK_WIDGET_CLASS (parent_class)->unrealize) (widget);
}

static void gtk_minefield_size_allocate(GtkWidget *widget,
					GtkAllocation *allocation)
{
        g_return_if_fail(widget != NULL);
        g_return_if_fail(GTK_IS_MINEFIELD (widget));
        g_return_if_fail(allocation != NULL);

        widget->allocation = *allocation;
        
	if (GTK_WIDGET_REALIZED(widget)) {
		gdk_window_move_resize(widget->window,
				       allocation->x, allocation->y,
				       GTK_MINEFIELD(widget)->data->xsize *
				       GTK_MINEFIELD(widget)->minesize,
				       GTK_MINEFIELD(widget)->data->ysize *
				       GTK_MINEFIELD(widget)->minesize);
	}
}

static void gtk_minefield_size_request(GtkWidget *widget,
				       GtkRequisition *requisition)
{
        requisition->width  = GTK_MINEFIELD(widget)->data->xsize *
		              GTK_MINEFIELD(widget)->minesize;
	requisition->height = GTK_MINEFIELD(widget)->data->ysize * 
		              GTK_MINEFIELD(widget)->minesize;
}

static void gtk_mine_draw(GtkMineFieldView *mfield, guint x, guint y)
{
        int c = cell_idx (mfield->data, x, y);
	int noshadow;
	int n;
	guint minesize;

        GtkWidget *widget = GTK_WIDGET(mfield);

	minesize = mfield->minesize;

	if (mfield->data->lose || mfield->data->win) {
		noshadow = mfield->data->mines[c].shown;
	} else {
		noshadow = mfield->data->mines[c].down || mfield->data->mines[c].shown;
	}
	
	gdk_window_clear_area(widget->window,
			      x*minesize, y*minesize,
			      minesize,
			      minesize);

        if (!noshadow)
        {
                gtk_draw_shadow(widget->style, widget->window,
                                GTK_WIDGET_STATE (widget), GTK_SHADOW_OUT,
                                x*minesize, y*minesize,
                                minesize,
                                minesize);
        } else {
                gdk_draw_line(widget->window,
                              widget->style->black_gc,
                              x*minesize+minesize-1,
                              y*minesize,
                              x*minesize+minesize-1,
                              y*minesize+minesize-1);
                gdk_draw_line(widget->window,
                              widget->style->black_gc,
                              x*minesize,
                              y*minesize+minesize-1,
                              x*minesize+minesize-1,
                              y*minesize+minesize-1);
        }

	if (mfield->data->mines[c].shown && !mfield->data->mines[c].mined) {
		if ((n = mfield->data->mines[c].neighbours) != 0) {
			gdk_draw_string(widget->window,
					mfield->font,
					mfield->numstr[n].gc,
					x*minesize+mfield->numstr[n].dx,
					y*minesize+mfield->numstr[n].dy,
					mfield->numstr[n].text);
		}
	} else if (mfield->data->mines[c].marked == 1) {
		gdk_gc_set_clip_mask(widget->style->black_gc,
					     mfield->flag.mask);
		gdk_gc_set_clip_origin(widget->style->black_gc,
					       x * minesize + (minesize - mfield->flag.width) / 2, 
					       y * minesize + (minesize - mfield->flag.height) / 2);
		gdk_draw_pixmap (widget->window,
				 widget->style->black_gc,
				 mfield->flag.pixmap,
				 0, 0, x * minesize + (minesize - mfield->flag.width) / 2, 
				       y * minesize + (minesize - mfield->flag.height) / 2, -1, -1);
	        gdk_gc_set_clip_mask(widget->style->black_gc, NULL);
	  
		if (mfield->data->lose && mfield->data->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->data->lose && mfield->data->mines[c].mined) {
		if (mfield->mine.mask) {
			gdk_gc_set_clip_mask(widget->style->black_gc,
					     mfield->mine.mask);
			gdk_gc_set_clip_origin(widget->style->black_gc,
					       x*minesize + (minesize - mfield->mine.width) / 2, 
					       y*minesize + (minesize - mfield->mine.height) / 2);
		}

	        gdk_draw_pixmap (widget->window,
				 widget->style->black_gc,
				 mfield->mine.pixmap,
				 0, 0, x * minesize + (minesize - mfield->mine.width) / 2,
				       y * minesize + (minesize - mfield->mine.height) / 2, -1, -1);
		
		if (mfield->flag.mask) {
			gdk_gc_set_clip_mask(widget->style->black_gc, NULL);
		}
	}
}

static void gtk_minefield_draw(GtkMineFieldView *mfield, GdkRectangle *area)
{
	guint x1, y1, x2, y2, x, y, minesize;

	minesize = mfield->minesize;

	if (area) {
		x1 = area->x/minesize;
		y1 = area->y/minesize;
		x2 = (area->x + area->width - 1) / minesize;
		y2 = (area->y + area->height - 1) / minesize;
	} else {
		x1 = 0; y1 = 0;
		x2 = mfield->data->xsize;
		y2 = mfield->data->ysize;
	}
	
	for (x = x1; x <= x2; x++)
		for (y = y1; y <= y2; y++)
			gtk_mine_draw(mfield, x, y);
}

static gint gtk_minefield_expose(GtkWidget *widget,
				 GdkEventExpose *event)
{
        GtkMineFieldView *mfield;
	GdkColor color;
	gint n;
	guint minesize;
        int i;

	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))
                return FALSE;

        mfield = GTK_MINEFIELD(widget);
	
	minesize = mfield->minesize;

	if (mfield->numstr[0].gc == 0) {
	        int pxlsz;
	        char fontname[50];
	  
	        gtk_minefield_setup_signs(widget);

	        pxlsz = minesize - 2;
	        if (pxlsz > 999) pxlsz = 999;
                if (pxlsz < 2)  pxlsz = 2;
  
                sprintf(fontname, "-bitstream-courier-bold-r-*-*-%d-*-*-*-*-*-*-*", pxlsz);
	
		mfield->font = gdk_font_load(fontname);
	  
	            /* The font used to be "-misc-fixed-bold-r-normal--13-*-*-*-*-*-*" */
	  
                if (!mfield->font) mfield->font = widget->style->font;
		for (i=0; i<9; i++) {
			mfield->numstr[i].text[0] = i+'0';
			mfield->numstr[i].text[1] = '\0';
			mfield->numstr[i].dx =
				(minesize-gdk_string_width(mfield->font,
							   mfield->numstr[i].text))/2;
			mfield->numstr[i].dy = (minesize + 5 * pxlsz / 8) / 2;
			mfield->numstr[i].gc = gdk_gc_new(GTK_WIDGET(mfield)->window);

			color.red   = num_colors[i][0] | (num_colors[i][0] << 8);
			color.green = num_colors[i][1] | (num_colors[i][1] << 8);
			color.blue  = num_colors[i][2] | (num_colors[i][2] << 8);
			color.pixel = 0; /* required! */

			n = 0;
			gdk_color_context_get_pixels (mfield->cc,
						      &color.red, &color.green, &color.blue,
						      1,
						      &color.pixel,
						      &n);

			gdk_gc_set_foreground(mfield->numstr[i].gc, &color);
		}
	}

	gtk_minefield_draw(GTK_MINEFIELD(widget), &event->area);

	return FALSE;
}

static inline int gtk_minefield_check_cell(GtkMineFieldView *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->data, nx, ny)) != -1) {
                        if (mfield->data->mines[c].shown == 0 &&
                            mfield->data->mines[c].marked == 0) {
				mfield->data->mines[c].shown = 1;
                                mfield->data->shown++;
				gtk_mine_draw(mfield, nx, ny);
				changed = 1;
			}
                }
        }
        return changed;
}


static void gtk_minefield_check_field(GtkMineFieldView *mfield, gint x, gint y)
{
        guint c;
        guint changed;

        gint x1, y1, x2, y2;
	gint cx1,cy1,cx2,cy2;
	
	cx1 = x-2;
	cy1 = y-2;
	cx2 = x+2;
	cy2 = y+2;

        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->data->xsize) x2 = mfield->data->xsize-1;
		if (y2 >= mfield->data->ysize) y2 = mfield->data->ysize-1;
		
		changed = 0;
                for (x=x1; x<=x2; x++) {
                        for (y=y1; y<=y2; y++) {
                                c = cell_idx (mfield->data, x, y);
                                if (mfield->data->mines[c].neighbours == 0 &&
                                    mfield->data->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_loose(GtkMineFieldView *mfield)
{
        gtk_signal_emit(GTK_OBJECT(mfield),
                        minefield_signals[EXPLODE_SIGNAL]);
        mfield->data->lose = 1;
        gtk_minefield_draw(mfield, NULL);
}

static void gtk_minefield_win(GtkMineFieldView *mfield)
{
        guint x, y, c;

	for (x = 0; x < mfield->data->xsize; x++)
		for (y = 0; y < mfield->data->ysize; y++)
                {
                        c = x+y*mfield->data->xsize;
                        if (mfield->data->mines[c].shown == 0 && mfield->data->mines[c].marked == 0)
                        {
                                mfield->data->mines[c].marked = 1;
                                gtk_mine_draw(mfield, x, y);
                        }
                }

        gtk_signal_emit(GTK_OBJECT(mfield),
                        minefield_signals[WIN_SIGNAL]);
        mfield->data->win = 1;
}

static void gtk_minefield_show(GtkMineFieldView *mfield, guint x, guint y)
{
	int c = x+mfield->data->xsize*y;
	
        if (mfield->data->mines[c].marked != 1 && mfield->data->mines[c].shown != 1) {
		mfield->data->mines[c].shown = 1;
		mfield->data->shown++;
                gtk_mine_draw(mfield, mfield->cdownx, mfield->cdowny);
                if(mfield->data->mines[c].mined == 1) {
                        gtk_minefield_loose(mfield);
                } else {
			gtk_minefield_check_field(mfield, x, y);
			if (mfield->data->shown == mfield->data->xsize*mfield->data->ysize-mfield->data->mcount) {
                                gtk_minefield_win(mfield);
			}
		}
        }
}

static void gtk_minefield_toggle_mark(GtkMineFieldView *mfield, guint x, guint y)
{
        int c = cell_idx (mfield->data, x, y);
        if (mfield->data->mines[c].shown == 0) {
		if ((mfield->data->mines[c].marked = 1-mfield->data->mines[c].marked) == 1) {
			mfield->data->flags++;
		} else {
			mfield->data->flags--;
		}
		gtk_signal_emit(GTK_OBJECT(mfield),
				minefield_signals[MARKS_CHANGED_SIGNAL]);
                if (mfield->data->shown == mfield->data->xsize*mfield->data->ysize-mfield->data->mcount) {
                        gtk_minefield_win(mfield);
		}
        }
}

static inline void gtk_minefield_multi_press(GtkMineFieldView *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->data, nx, ny)) == -1)
                        continue;
                if (!mfield->data->mines[c2].marked && !mfield->data->mines[c2].shown) {
                        mfield->data->mines[c2].down = 1;
                        gtk_mine_draw(mfield, nx, ny);
                }
        }
        mfield->multi_mode = 1;
}

static void gtk_minefield_multi_release (GtkMineFieldView *mfield, guint x, guint y, guint c, guint really)
{
        gint n, nx, ny, i, c2;
        guint loose = 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->data, nx, ny)) == -1)
                        continue;
                if (mfield->data->mines[c2].marked) n++;
        }
        if (mfield->data->mines[c].neighbours != n ||
            mfield->data->mines[c].marked ||
            !mfield->data->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->data, nx, ny)) == -1)
                        continue;
                if (mfield->data->mines[c2].down) {
                        mfield->data->mines[c2].down = 0;
                        if (really) {
				mfield->data->mines[c2].shown = 1;
				mfield->data->shown++;
                                if (mfield->data->mines[c2].mined == 1) {
                                        loose = 1;
                                }
                        }
                        gtk_mine_draw(mfield, nx ,ny);
                }
        }
        if (loose) {
                gtk_minefield_loose(mfield);
        } else if (really) {
                gtk_minefield_check_field(mfield, x, y);
                if (mfield->data->shown == mfield->data->xsize*mfield->data->ysize-mfield->data->mcount) {
			gtk_minefield_win(mfield);
		}
        }
}

static gint gtk_minefield_motion_notify(GtkWidget *widget, GdkEventMotion *event)
{
        GtkMineFieldView *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->data->lose || mfield->data->win) return FALSE;

        if (mfield->bdown[0] || mfield->bdown[1]) {
                x = event->x/minesize;
                y = event->y/minesize;
                c = x+y*(mfield->data->xsize);

                if (c != mfield->cdown) {
                        mfield->data->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->data->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)
{
        GtkMineFieldView *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->data->lose || mfield->data->win) return FALSE;
	
        if (event->button <= 3 && !mfield->bdown[1]) {
                x = event->x/minesize;
                y = event->y/minesize;
                c = x+y*(mfield->data->xsize);
                if (!mfield->bdown[0] && !mfield->bdown[1] && !mfield->bdown[2]) {
                        mfield->cdownx = x;
                        mfield->cdowny = y;
                        mfield->cdown = c;
                        mfield->data->mines[c].down = 1;
                }
		mfield->bdown[event->button-1]++;
                gtk_mine_draw(mfield, x, y);
                if (event->button == 2 || (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) {
			gtk_signal_emit(GTK_OBJECT(mfield),
					minefield_signals[LOOK_SIGNAL]);
		}
        }
        return FALSE;
}

static gint gtk_minefield_button_release(GtkWidget *widget, GdkEventButton *event)
{
        GtkMineFieldView *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->data->lose || mfield->data->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: 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->data->lose && !mfield->data->win) {
			gtk_signal_emit(GTK_OBJECT(mfield),
					minefield_signals[UNLOOK_SIGNAL]);
		}
		mfield->data->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 (GtkMineFieldViewClass *class)
{
	GtkWidgetClass *widget_class;
	GtkObjectClass *object_class;
	
        widget_class = (GtkWidgetClass *)class;
	object_class = (GtkObjectClass *)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] =
		gtk_signal_new("marks_changed",
			       GTK_RUN_FIRST,
			       object_class->type,
			       GTK_SIGNAL_OFFSET(GtkMineFieldViewClass, marks_changed),
			       gtk_signal_default_marshaller,
			       GTK_TYPE_NONE,
			       0);
	minefield_signals[EXPLODE_SIGNAL] =
		gtk_signal_new("explode",
			       GTK_RUN_FIRST,
			       object_class->type,
			       GTK_SIGNAL_OFFSET(GtkMineFieldViewClass, explode),
			       gtk_signal_default_marshaller,
			       GTK_TYPE_NONE,
			       0);
	minefield_signals[LOOK_SIGNAL] =
		gtk_signal_new("look",
			       GTK_RUN_FIRST,
			       object_class->type,
			       GTK_SIGNAL_OFFSET(GtkMineFieldViewClass, look),
			       gtk_signal_default_marshaller,
			       GTK_TYPE_NONE,
			       0);
	minefield_signals[UNLOOK_SIGNAL] =
		gtk_signal_new("unlook",
			       GTK_RUN_FIRST,
			       object_class->type,
			       GTK_SIGNAL_OFFSET(GtkMineFieldViewClass, unlook),
			       gtk_signal_default_marshaller,
			       GTK_TYPE_NONE,
			       0);
	minefield_signals[WIN_SIGNAL] =
		gtk_signal_new("win",
			       GTK_RUN_FIRST,
			       object_class->type,
			       GTK_SIGNAL_OFFSET(GtkMineFieldViewClass, win),
			       gtk_signal_default_marshaller,
			       GTK_TYPE_NONE,
			       0);

	gtk_object_class_add_signals(object_class, minefield_signals, LAST_SIGNAL);
}

static void gtk_minefield_init (GtkMineFieldView *mfield)
{
#ifndef GTK_HAVE_FEATURES_1_1_4
        GTK_WIDGET_SET_FLAGS (mfield, GTK_BASIC);
#endif
	mfield->cc = NULL;

        GTK_WIDGET (mfield)->requisition.width = mfield->minesize;
        GTK_WIDGET (mfield)->requisition.height = mfield->minesize;
}

void minefield_set_size (MineField *data, guint xsize, guint ysize)
{
        g_return_if_fail (data != NULL);

	if (data->xsize * data->ysize != xsize * ysize) {
		data->mines = g_realloc (data->mines,
					 sizeof(mine) * xsize * ysize);
	}
	data->xsize = xsize;
	data->ysize = ysize;
}

MineField *minefield_new ()
{
	MineField *data = g_new (MineField, 1);

	data->views  =  0;
	data->xsize  = -1;
	data->ysize  = -1;
	data->mcount =  0;
	data->mines  = NULL;
	data->flags  =  0;
	data->shown  =  0;
	data->lose   =  0;
	data->win    =  0;

	return data;
}

void minefield_destroy (MineField *data)
{
	g_return_if_fail (data != NULL);

	while (data->views)
		data->views = g_list_remove (data->views, data->views->data);

	g_free (data);
}

GtkWidget* gtk_minefield_new_view (MineField *data)
{
        GtkMineFieldView *mfield;
        
	mfield = gtk_type_new (gtk_minefield_get_type ());
	mfield->data = data;
	data->views = g_list_append (data->views, mfield);

	mfield->cdown = -1;
	mfield->numstr[0].gc = 0; /* Force GC generation */
	mfield->minesize = 17;

	return GTK_WIDGET (mfield);
}

guint gtk_minefield_get_type ()
{
        static guint minefield_type = 0;
        
        if (!minefield_type) {
                GtkTypeInfo minefield_info =
                        {
                                "GtkMineFieldView",
                                sizeof (GtkMineFieldView),
                                sizeof (GtkMineFieldViewClass),
                                (GtkClassInitFunc) gtk_minefield_class_init,
                                (GtkObjectInitFunc) gtk_minefield_init,
                                (GtkArgSetFunc) NULL,
                                (GtkArgGetFunc) NULL,
                        };
                        
                        minefield_type = gtk_type_unique (gtk_widget_get_type (),
							  &minefield_info);
        }
        
        return minefield_type;
}

void minefield_set_mines (MineField *data, guint mcount)
{
	GList *l;
        g_return_if_fail (data != NULL);
	
        data->mcount = mcount;
/*        data->numstr[0].gc = 0;*/

	l = data->views;
	while (l) {
		if (GTK_WIDGET_VISIBLE (l->data))
			gtk_widget_queue_resize (GTK_WIDGET (l->data));
		l = g_list_next (l);
	}
}

void gtk_minefield_set_minesize (GtkMineFieldView *mfield,
			     guint minesize)
{
	g_return_if_fail (mfield != NULL);

        mfield->minesize = minesize;
}

static gulong random_seed;

static void init_random(gulong seed)
{
        random_seed = seed;
}

static gulong get_random(gulong limit)
{
	do {
		random_seed = (random_seed*1139113+10921)>>2;
	} while (random_seed > ((gulong)(G_MAXLONG/limit))*limit);
	return random_seed % limit;
}

static void gtk_minefield_cleanup (GtkMineFieldView *mfield)
{
	mfield->bdown[0] = 0;
	mfield->bdown[1] = 0;
	mfield->bdown[2] = 0;
        mfield->cdown = -1;
        mfield->multi_mode = 0;
}

void minefield_set_at (MineField *data, int x, int y,
		       char num, char state)
{
	int idx;

	g_return_if_fail (data != NULL);

	idx = x + y * data->xsize;
	g_return_if_fail (idx < data->xsize * data->ysize);

	data->mines [idx].neighbours = num - '0';
	data->mines [idx].mined      = state == 'M';
	data->mines [idx].marked     = state == 'P';
	data->mines [idx].down       = state == 'v';
	data->mines [idx].shown      = state == ' ';
}

void minefield_get_at (MineField *data, int x, int y,
		       char *num, char *state)
{
	int idx;

	g_return_if_fail (data != NULL);

	idx = x + y * data->xsize;
	g_return_if_fail (idx < data->xsize * data->ysize);

	*num = data->mines [idx].neighbours + '0';

	if (data->mines [idx].mined)
		*state = 'M';
	else if (data->mines [idx].marked)
		*state = 'P';
	else if (data->mines [idx].down)
		*state = 'v';
	else if (data->mines [idx].shown)
		*state = ' ';
	else
		*state = '?';
}

void minefield_restart (MineField *data)
{
	guint i, j;
        guint x, y;
	guint tmp;
	guint n;
	guint cidx;
	GList *l;

        g_return_if_fail (data != NULL);

	data->flags = 0;
	data->shown = 0;
	data->lose  = 0;
	data->win   = 0;

	for (i=0; i<data->mcount; i++) {
		data->mines[i].mined = 1;
	}
	for (i = data->mcount; i < data->xsize * data->ysize; i++)
		data->mines[i].mined = 0;

	if (secs == 0) {
		time((time_t *)&secs);
		init_random(secs);
	}
	
	for (i = 0; i < data->xsize * data->ysize; i++) {
                data->mines[i].marked = 0;
                data->mines[i].shown  = 0;
		data->mines[i].down   = 0;
		j = (guint)get_random(data->xsize*data->ysize);
		tmp = data->mines[i].mined;
		data->mines[i].mined = data->mines[j].mined;
		data->mines[j].mined = tmp;
	}

	for (x = 0; x < data->xsize; x++) {
		for (y = 0; y < data->ysize; y++) {
			n = 0;
			for (i = 0; i < 8; i++) {
				if ((cidx = cell_idx (data, x + neighbour_map[i].x,
						      y + neighbour_map[i].y)) != -1) {
					if (data->mines[cidx].mined) n++;
				}
			}
                        data->mines[x + data->xsize * y].neighbours = n;
		}
	}

	l = data->views;
	while (l) {
		gtk_minefield_cleanup (l->data);
		l = g_list_next (l);
	}
}



syntax highlighted by Code2HTML, v. 0.9.1