/*
 * Mines
 *
 * Copyright (C) Evan Harris, 1994, 1995.
 *
 * Permission is granted to freely redistribute and modify this code,
 * providing the author(s) get credit for having written it.
 */

#include <stdlib.h>
#include <stdio.h>
#include <time.h>
#include <unistd.h>

#include "mines.h"


#define MINWIDTH 8
#define MAXWIDTH 30
#define MINHEIGHT 8
#define MAXHEIGHT 30
#define MINDENSITY 0.05
#define MAXDENSITY 0.5

#define MINERATIO 4

#define MINE 9
#define PROTECTFLAG (1 << 4)
#define PROTECTQFLAG (1 << 5)
#define CLEARFLAG (1 << 6)
#define QUERYFLAG (1 << 7)
#define NOFLAGS 0x0f


struct minesdata {
    char *b;
    int w, h;
    double d;
    int minesleft;
    int protected;
    int cleared;
};


void init_board(struct minesdata *md);
void free_board(struct minesdata *md);
void new_board(struct minesdata *md);
void protect(struct minesdata *md, int x, int y);
void display_bad_flags(struct minesdata *md, int no_x, int no_y);
int detonate(struct minesdata *md, int x, int y);
void clear_around(struct minesdata *md, int x, int y);
int check_around(struct minesdata *md, int x, int y, int flag);


int
main(int argc, char **argv)
{
    struct minesdata md;
    int c, err = 0;
    int width = 20, height = 20;
    double density = 0.25;
    int packedcmd, cmd, x, y, end = 0;
    int gameover;
    
    while ((c = getopt(argc, argv, "d:h:w:")) != -1) {
	switch (c) {
	  case 'd':
	    density = atof(optarg);
	    if (density < MINDENSITY || density > MAXDENSITY) {
		err++;
	    }
	    break;
	  case 'h':
	    height = atoi(optarg);
	    if (height < MINHEIGHT || height > MAXHEIGHT) {
		err++;
	    }
	    break;
	  case 'w':
	    width = atoi(optarg);
	    if (width < MINWIDTH || height > MAXWIDTH) {
		err++;
	    }
	    break;
	  case '?':
	    err++;
	    break;
	}
    }
    if (err) {
	fprintf(stderr, "Usage: %s [-d F] [-h N] [-w N]\n",
		argv[0]);
	fprintf(stderr, "\t-d F  set mine density to F (%.2f-%.2f)\n",
		MINDENSITY, MAXDENSITY);
	fprintf(stderr, "\t-h N  set board height to N (%d-%d)\n",
		MINHEIGHT, MAXHEIGHT);
	fprintf(stderr, "\t-w N  set board width to N (%d-%d)\n",
		MINWIDTH, MAXWIDTH);
	exit(EXIT_FAILURE);
    }

    md.w = width;
    md.h = height;
    md.d = density;

    init_board(&md);
    if (md.b == NULL) {
	fprintf(stderr, "Cannot get memory for board\n");
	exit(1);
    }
    InitDisplay(width, height);

    new_board(&md);
    gameover = CONT;
    NewGame(width, height);
    MinesLeft(md.minesleft);

    while (!end) {
	packedcmd = GetMove();
	cmd = packedcmd_cmd(packedcmd);
	x = packedcmd_x(packedcmd);
	y = packedcmd_y(packedcmd);
	switch (cmd) {
	  case NEWGAME:
	    new_board(&md);
	    gameover = CONT;
	    NewGame(width, height);
	    MinesLeft(md.minesleft);
	    break;
	  case QUIT:
	    end = 1;
	    break;
	  case PROTECT:
	    if (!gameover) {
		protect(&md, x, y);
		MinesLeft(md.minesleft);
		if (md.protected + md.cleared == md.w * md.h) {
		    gameover = WIN;
		}
		if (gameover) {
		    GameOver(gameover);
		}
	    }
	    break;
	  case DETONATE:
	    if (!gameover) {
		gameover = detonate(&md, x, y);
		if (md.protected + md.cleared == md.w * md.h) {
		    gameover = WIN;
		}
		if (gameover) {
		    GameOver(gameover);
		}
	    }
	    break;
	  case CHECK_AROUND:
	    if (!gameover) {
		gameover = check_around(&md, x, y, 1);
		if (md.protected + md.cleared == md.w * md.h) {
		    gameover = WIN;
		}
		if (gameover) {
		    GameOver(gameover);
		}
	    }
	    break;
	  case UNCHECK_AROUND:
	    if (!gameover) {
		gameover = check_around(&md, x, y, 0);
		if (md.protected + md.cleared == md.w * md.h) {
		    gameover = WIN;
		}
		if (gameover) {
		    GameOver(gameover);
		}
	    }
	    break;
	  default:
	    fprintf(stderr, "Unknown command\n");
	    end = 1;
	    break;
	}
    }
    
    free_board(&md);
    EndDisplay();

    return EXIT_SUCCESS;
}


void
init_board(struct minesdata *md)
{
    srand48(time(NULL));
    
    md->b = malloc(md->w * md->h * sizeof(char));
}


void
free_board(struct minesdata *md)
{
    free(md->b);
}


void
new_board(struct minesdata *md)
{
    int i, j;
    int mines;

    mines = (int)(md->w * md->h * md->d);
    
    for (i = 0; i < md->w * md->h; i++) {
	md->b[i] = 0;
    }
    for (i = 0; i < mines; i++) {
	do {
	    j = lrand48() % (md->w * md->h);
	} while (md->b[j] != 0);
	md->b[j] = MINE;
    }
    for (i = 0; i < md->w * md->h; i++) {
	if (md->b[i] != MINE) {
	    j = 0;
	    if (i / md->w > 0 && i % md->w > 0 && md->b[i - md->w - 1] == MINE)
		j++;
	    if (i / md->w > 0 && md->b[i - md->w] == MINE)
		j++;
	    if (i / md->w > 0 && i % md->w < md->w - 1
		&& md->b[i - md->w + 1] == MINE)
		j++;
	    if (i % md->w > 0 && md->b[i - 1] == MINE)
		j++;
	    if (i % md->w < md->w - 1 && md->b[i + 1] == MINE)
		j++;
	    if (i / md->w < md->h - 1 && i % md->w > 0
		&& md->b[i + md->w - 1] == MINE)
		j++;
	    if (i / md->w < md->h - 1 && md->b[i + md->w] == MINE)
		j++;
	    if (i / md->w < md->h - 1 && i % md->w < md->w - 1
		&& md->b[i + md->w + 1] == MINE)
		j++;
	    md->b[i] = j;
	}
    }

    md->minesleft = mines;
    md->protected = 0;
    md->cleared = 0;
}


void
protect(struct minesdata *md, int x, int y)
{
    int i;

    i = y * md->w + x;
    if (md->b[i] & CLEARFLAG) {
	return;
    }
    if (md->b[i] & PROTECTFLAG) {
	md->b[i] = (md->b[i] & NOFLAGS) | PROTECTQFLAG;
	ShowSquare(x, y, SQ_Q);
	md->minesleft++;
	md->protected--;
	return;
    }
    if (md->b[i] & PROTECTQFLAG) {
	md->b[i] &= NOFLAGS;
	ShowSquare(x, y, SQ_COVERED);
	return;
    }
    md->b[i] = (md->b[i] & NOFLAGS) | PROTECTFLAG;
    ShowSquare(x, y, SQ_X);
    md->minesleft--;
    md->protected++;
}


void
clear_it(struct minesdata *md, int x, int y)
{
    int i = y * md->w + x;

    if (!(md->b[i] & (PROTECTFLAG | PROTECTQFLAG | CLEARFLAG))) {
	md->b[i] = (md->b[i] & NOFLAGS) | CLEARFLAG;
	md->cleared++;
	ShowSquare(x, y, md->b[i] & NOFLAGS);
	if ((md->b[i] & NOFLAGS) == 0) {
	    clear_around(md, x, y);
	}
    }
}


void
clear_around(struct minesdata *md, int x, int y)
{
    if (y > 0 && x > 0) {
	clear_it(md, x - 1, y - 1);
    }
    if (y > 0) {
	clear_it(md, x, y - 1);
    }
    if (y > 0 && x < md->w - 1) {
	clear_it(md, x + 1, y - 1);
    }
    if (x > 0) {
	clear_it(md, x - 1, y);
    }
    if (x < md->w - 1) {
	clear_it(md, x + 1, y);
    }
    if (y < md->h - 1 && x > 0) {
	clear_it(md, x - 1, y + 1);
    }
    if (y < md->h - 1) {
	clear_it(md, x, y + 1);
    }
    if (y < md->h - 1 && x < md->w - 1) {
	clear_it(md, x + 1, y + 1);
    }
}


int
protected_around(struct minesdata *md, int x, int y)
{
    int i = 0;

    if (y > 0 && x > 0 && (md->b[(y - 1) * md->w + (x - 1)] & PROTECTFLAG))
	i++;
    if (y > 0 && (md->b[(y - 1) * md->w + x] & PROTECTFLAG))
	i++;
    if (y > 0 && x < md->w - 1
	&& (md->b[(y - 1) * md->w + (x + 1)] & PROTECTFLAG))
	i++;
    if (x > 0 && (md->b[y * md->w + (x - 1)] & PROTECTFLAG))
	i++;
    if (x < md->w - 1 && (md->b[y * md->w + (x + 1)] & PROTECTFLAG))
	i++;
    if (y < md->h - 1 && x > 0
	&& (md->b[(y + 1) * md->w + (x - 1)] & PROTECTFLAG))
	i++;
    if (y < md->h - 1 && (md->b[(y + 1) * md->w + x] & PROTECTFLAG))
	i++;
    if (y < md->h - 1 && x < md->w - 1
	&& (md->b[(y + 1) * md->w + (x + 1)] & PROTECTFLAG))
	i++;
    return i;
}


int
detonate_it(struct minesdata *md, int x, int y)
{
    int i = y * md->w + x;

    if (md->b[i] & PROTECTFLAG &&
	(md->b[i] & NOFLAGS) != MINE) {
	ShowSquare(x, y, SQ_XFLAG);
	md->cleared++;
	return CONT;
    }
    if (md->b[i] & (CLEARFLAG | PROTECTFLAG)) {
	return CONT;
    }
    md->b[i] = (md->b[i] & NOFLAGS) | CLEARFLAG;
    ShowSquare(x, y, md->b[i] & NOFLAGS);

    if ((md->b[i] & NOFLAGS) == 0) {
	clear_around(md, x, y);
    }

    if ((md->b[i] & NOFLAGS) == MINE) {
	return LOSE;
    } else {
	md->cleared++;
	return CONT;
    }
}


int
detonate_around(struct minesdata *md, int x, int y)
{
    int i = 0;

    if (y > 0 && x > 0) {
	i += detonate_it(md, x - 1, y - 1);
    }
    if (y > 0) {
	i += detonate_it(md, x, y - 1);
    }
    if (y > 0 && x < md->w - 1) {
	i += detonate_it(md, x + 1, y - 1);
    }
    if (x > 0) {
	i += detonate_it(md, x - 1, y);
    }
    if (x < md->w - 1) {
	i += detonate_it(md, x + 1, y);
    }
    if (y < md->h - 1 && x > 0) {
	i += detonate_it(md, x - 1, y + 1);
    }
    if (y < md->h - 1) {
	i += detonate_it(md, x, y + 1);
    }
    if (y < md->h - 1 && x < md->w - 1) {
	i += detonate_it(md, x + 1, y + 1);
    }
    return (i > 0) ? LOSE : CONT;
}


void
query_it(struct minesdata *md, int x, int y)
{
    int i = y * md->w + x;

    if (md->b[i] & (CLEARFLAG | PROTECTFLAG)) {
	return;
    }
    if (md->b[i] & QUERYFLAG) {
	md->b[i] = md->b[i] & (~QUERYFLAG);
	if (md->b[i] & PROTECTQFLAG) {
	    ShowSquare(x, y, SQ_Q);
	} else {
	    ShowSquare(x, y, SQ_COVERED);
	}
    } else {
	md->b[i] = md->b[i] | QUERYFLAG;
	ShowSquare(x, y, SQ_QUERY);
    }
}


void
query_around(struct minesdata *md, int x, int y)
{
    if (y > 0 && x > 0) {
	query_it(md, x - 1, y - 1);
    }
    if (y > 0) {
	query_it(md, x, y - 1);
    }
    if (y > 0 && x < md->w - 1) {
	query_it(md, x + 1, y - 1);
    }
    if (x > 0) {
	query_it(md, x - 1, y);
    }
    if (x < md->w - 1) {
	query_it(md, x + 1, y);
    }
    if (y < md->h - 1 && x > 0) {
	query_it(md, x - 1, y + 1);
    }
    if (y < md->h - 1) {
	query_it(md, x, y + 1);
    }
    if (y < md->h - 1 && x < md->w - 1) {
	query_it(md, x + 1, y + 1);
    }
}


void
display_bad_flags(struct minesdata *md, int no_x, int no_y)
{
    int x, y, i;
    
    for (y = 0; y < md->w; y++) {
	for (x = 0; x < md->h; x++) {
	    if (x != no_x || y != no_y) {
		i = y * md->w + x;
		if ((md->b[i] & PROTECTFLAG) &&
		    ((md->b[i] & NOFLAGS) != MINE)) {
		    ShowSquare(x, y, SQ_XFLAG);
		} else if (!(md->b[i] & PROTECTFLAG) &&
			   ((md->b[i] & NOFLAGS) == MINE)) {
		    ShowSquare(x, y, SQ_GMINE);
		}
	    }
	}
    }
}


int
detonate(struct minesdata *md, int x, int y)
{
    int i, ret;

    i = y * md->w + x;
    if (md->b[i] & PROTECTFLAG) {
	return CONT;
    }

    ret = detonate_it(md, x, y);
    if (ret == LOSE) {
	ShowSquare(x, y, SQ_XMINE);
	display_bad_flags(md, x, y);
    }

    return ret;
}


int
check_around(struct minesdata *md, int x, int y, int flag)
{
    int i, ret;

    i = y * md->w + x;
    if (md->b[i] & CLEARFLAG) {
	if (protected_around(md, x, y) >= (md->b[i] & NOFLAGS)) {
	    if (flag == 1) {
		ret = detonate_around(md, x, y);
		if (ret == LOSE) {
		    display_bad_flags(md, -1, -1);
		}
		return ret;
	    } 
	} else {
	    query_around(md, x, y);
	}
    }
    return CONT;
}
