/*****************************************************************************/
/*									     */
/*									     */
/*	X patience - rules.c						     */
/*									     */
/*	written by Heiko Eissfeldt and Michael Bischoff			     */
/*									     */
/*	24-Feb-1993: First release (0.1)				     */
/*									     */
/*									     */
/*****************************************************************************/
#include "xpat.h"
#include <time.h>
#include <limits.h>	/* for INT_MAX */

#ifdef DEBUG
static int type[200];

static void count(void)
{   int i;
    memset(type, 0, sizeof(type));
    for (i = 0; i < rules.numcards; ++i)
	++type[game.cards[i]];
    for (i = 0; i < 52; ++i)
	if (type[i] != rules.numdecks)
	    fprintf(stderr, "Card %d: Number = %d\n", i, type[i]);
}
#endif

void mem_alloc(int num)
{   game.numalloc = ((num - 1) | 0xf) + 1;	/* multiple of 16 */
    if (!(game.move = malloc(game.numalloc * sizeof(Move)))) {
	fprintf(stderr, "out of memory\n");
	exit(1);
    }
}

void store_move(Move m)
{   if (game.n_moves == game.numalloc) {
	game.numalloc += 256;
	if (!(game.move = realloc(game.move, game.numalloc * sizeof(Move)))) {
	    fprintf(stderr, "out of memory\n");
	    exit(1);	/* that's hard, I know. should at least store game */
	}
    }
    game.move[game.n_moves] = m;
    game.stored_moves = ++game.n_moves;
}

static void undo_give_cards(int num)	/* num = number of cards */
{   int i;
    /* printf("undo give %d cards\n", num); */
    if (rules.variant & DECK_SOURCE) {
	if (!num) {
	    /* undo flip */
	    while (!EMPTY(IDECK))
		do_move(INDEX_OF_LAST_CARD(IDECK), VDECK);
	    --game.flipnum;
	    draw_pileupdate(IDECK, 0);
	    /* draw_pileupdate(VDECK, 0); */
	} else {
	    for (i = 0; i < num; ++i)
		do_move(INDEX_OF_LAST_CARD(VDECK), IDECK);
	    /* if (EMPTY(VDECK))
		draw_pileupdate(VDECK, 0); */
	}
    } else {
	for (i = FIRST_SLOT + num; i != FIRST_SLOT; --i)
	    /* slot i-1 back to deck */
	    do_move(game.ind[i]-1, IDECK);
    }
    if (CARDS_ON_PILE(IDECK) == num) 		/* there are cards again */
	draw_pileupdate(IDECK, 0);	/* force deck to be redrawn */
}

int undo_move(void)
{   Move m;
    int retval = 1;	/* no cheat */

    if (!game.n_moves) {
	/* possibly undo restart game? */
	if (game.stored_moves) {
	    while (redo_move())		/* must replay the game */
		;
	    return 1;
	}
	return 0;
    }
    m = game.move[--game.n_moves];
    if ((m & NEW_CARDS_MOVE) == NEW_CARDS_MOVE) {
	undo_give_cards(DSTPILE(m));
	++game.cheat_count;
	return 2;
    }
    m >>= 16;	/* upper part = undo information */
    if (m & MOVE_TURNED) {
	++game.cheat_count;
	game.visible[game.ind[DSTPILE(m)+1]-1] = 0;	/* invisible again! */
	retval = 2;
    }
    (void)do_move(SRCIND(m), DSTPILE(m));
    if (retval == 2)
	draw_pileupdate(DSTPILE(m), 0);			/* hide card again! */
    return retval;
}

int redo_move(void)
{   Move m;
    int retval = 1;
    if (game.n_moves == game.stored_moves)
	return 0;	/* no redo possible */
    m = game.move[game.n_moves++];
    if (m & MOVE_TURNED) {
	--game.cheat_count;
	retval = 2;
	/* printf("uncheat move %08x\n", m); */
	if ((m & NEW_CARDS_MOVE) == NEW_CARDS_MOVE) {
	    (void)give_new_cards();
	    return retval;
	}
    }
    (void)do_move(SRCIND(m), DSTPILE(m));
    return retval;
}


static void shuffle(void)
{   int i, d, v, c;
    int tmp[MAXCARDS];

    i = 0;
    for (d = 0; d < rules.numdecks; ++d)
	for (v = 0; v < rules.cards_per_color; ++v)
	    for (c = 0; c < 4; ++c)
		tmp[i++] = c + (v << 2);
    assert(i <= rules.numcards);
    c = 0;
    while (i < rules.numcards)
	tmp[i++] = JOKER + c++;
    while (c < rules.numjokers)
	tmp[prand() % rules.numcards] = JOKER + c++;	/* subst old cards */

    for (i = rules.numcards; i; --i) {
	v = ((int)prand() & INT_MAX) % i;
	/* printf("rand() = %d\n", v); */
	/* look for the vth non-empty card in tmp */
	c = -1;
	do {
	    while (tmp[++c] == -1)
		;			/* skip card */
	} while (v--);
	game.cards[i-1] = tmp[c];
	/* printf("c = %d, card = %d\n", c, game.cards[i-1]); */
	tmp[c] = -1;
    }
}

Pileindex getpile(Cardindex ind)
{   int i;
    assert(ind < rules.numcards);
    i = 0;
    while (ind >= game.ind[i+1])
	++i;
    return i;
}

/* return index of first card of block max up to height y 
   belonging to cardindex cardi (cardi lies on slot) */

Cardindex getblock(Cardindex cardind, int y)
{
    int ind = getpile(cardind);
    while (cardind > game.ind[ind] && game.visible[cardind-1] &&
      (*rules.valid)(game.cards[cardind-1], game.cards[cardind])
         && y < graphic.cardy[cardind]) {
	--cardind;
    }
    return cardind;
}


/* if slot: return cardindex of maximum block up to height y in pile ind */
/* otherwise return the index of the topmost card */

Cardindex select_max(Pileindex ind, int y)
{
    if (!EMPTY(ind)) {	/* the pile may not be empty for this operation */
	switch (game.piletype[ind]) {
	case Stack:
	    if (!(rules.variant & STACK_SOURCE))
		break;		/* not allowed => break */
	    /* fall through: */
	case Tmp:
	    return INDEX_OF_LAST_CARD(ind);
	case Slot:
	    y -= graphic.pile[ind].y;
	    return getblock(INDEX_OF_LAST_CARD(ind), y);
	case FaceupDeck:
	    if (rules.variant & DECK_SOURCE)
		return INDEX_OF_LAST_CARD(ind);
	    break;	/* not allowed => fall through */
	case FacedownDeck:
	    break;	/* this is a special type of move and is */
	}		/* handled at some other place */
    }
    return -1;		/* this indicates that selection is not possible */
}

/* this is a central card moving routine				 */
/* every time anything is changed, this function is called at least once */
/* We can reset hint tables at this point				 */

Move do_move(Cardindex srcindex, Pileindex dstpile)
{   int srcpile, numcards;
    int tmp[MAXCARDS];
    Move m;

    hint(RESET_HINTS);
    game.srcind = -1;                   /* no source selected */
    game.arrow_srcind = -1;             /* no arrow displayed */
    game.arrow_dstpile = -1;
    graphic.zoomed_card = -1;
    if (srcindex == -1)
	return NO_MOVE;

    srcpile = getpile(srcindex);
    numcards = game.ind[srcpile+1] - srcindex;
    m = MOVE(srcindex, dstpile);
    if (srcindex > game.ind[srcpile] && !game.visible[srcindex-1]) {
	game.visible[srcindex-1] = 1;
	m |= MOVE_TURNED | (MOVE_TURNED << 16);
    }

    memcpy(tmp, game.cards+srcindex, numcards * sizeof(int));
    /* printf("move %d to %d, %d cards\n", srcindex, dstpile, numcards); */
    assert(srcpile != dstpile);
    if (srcpile < dstpile) {
	/* ldir */
	int i;
	m |= MOVE(game.ind[dstpile+1]-numcards, srcpile) << 16;
	for (i = srcindex; i < game.ind[dstpile+1]-numcards; ++i) {
	    game.cards[i] = game.cards[i+numcards];
	    game.visible[i] = game.visible[i+numcards];
	    graphic.cardy[i] = graphic.cardy[i+numcards];
	}
	memcpy(game.cards+i, tmp, numcards * sizeof(int));
	for (; i < game.ind[dstpile+1]; ++i)
	    game.visible[i] = 1;
	for (i = srcpile + 1; i <= dstpile; ++i)
	    game.ind[i] -= numcards;
    } else {
	/* lddr ; shift a block backwards; begin at the tail */
	int i;
	m |= MOVE(game.ind[dstpile+1], srcpile) << 16;
	for (i = srcindex - 1; i >= game.ind[dstpile+1]; --i) {
	    game.cards[i+numcards] = game.cards[i];
	    game.visible[i+numcards] = game.visible[i];
	    graphic.cardy[i+numcards] = graphic.cardy[i];
	}
	memcpy(game.cards + game.ind[dstpile+1], tmp, numcards * sizeof(int));
	for (i = 0; i < numcards; ++i)
	    game.visible[game.ind[dstpile+1] + i] = 1;
	for (i = dstpile+1; i <= srcpile; ++i)
	    game.ind[i] += numcards;
    }
#ifdef DEBUG
    count();
#endif
    draw_pileupdate(srcpile, -numcards);
    draw_pileupdate(dstpile, numcards);
    return m;
}

int move_to_stack(Pileindex pile)	/* pile is slot or tmp */
{   int i, srcindex;
    if (EMPTY(pile))
	return 0;
    if ((srcindex = (*rules.stackable)(pile)) == -1)
	return 0;		/* not possible to move to stack */
    for (i = 0; i < rules.numstacks; ++i) {
	if (SUIT(i) != SUIT(game.cards[srcindex]))
	    continue;
	if (move_valid(srcindex, i)) {
	    store_move(do_move(srcindex, i));
	    return 1;
	}
    }
    return 0;
}

int all_to_stack(void)
{   int i, flag, anymove;

    anymove = 0;
    do {
	flag = 0;
	for (i = 0; i < game.numpiles; ++i) {
	    switch (game.piletype[i]) {
	    case Slot:
	    case Tmp:
	    case FaceupDeck:
		if (move_to_stack(i)) {
		    flag = 1;
		    anymove = 1;
		}
		break;
	    default:
		;
	    }
	}
    } while (flag);
    return anymove;
}

static void init_seed(long seed)
{
    if (seed < 0) {
	game.seed = -1L;	/* to guarantee a mismatch later */
	seed = (long)time(NULL);
    }
    seed %= PRANDMAX;
    if (seed < 0L)
	seed += PRANDMAX;	/* I think this shouldn't happen */
    if (seed == game.seed) {	/* restart game */
	/* stored_moves stays valid */
	game.cheat_count += 1000;
    } else {
	game.seed = seed;
	game.cheat_count = game.stored_moves = 0;
	game.finished = False;	/* this is really a new game */
    }
    game.n_moves = 0;
    game.flipnum = 0;
    sprand(seed);
}

static void distribute(char *xx, int rest, int piles)
{   int i, k, invert = 0;

    if (!piles)
	return;
    if (2 * rest > piles+1) {
	rest = piles - rest;
	invert = 1;
    }
    memset(xx, invert, piles);
    if (rest) {
	k = (piles-1) / rest + 1;	/* ceil(slots/rest) */
	for (i = 0; i < piles; i += k) {
	    xx[i] = 1-invert;
	    if (!--rest)
		break;
	}
	/* the rest is distributed quite ugly */
	for (i = 0; rest; ++i)
	    if (xx[i] == invert) {
		xx[i] = 1-invert;
		--rest;
	    }
    }
}

static void gen_newgame(void)
{   int i, rest;
    char xx[MAXPILES];

    memset(xx, 0, MAXPILES);
    if (rules.facedown && !rules.faceup) {
	fprintf(stderr, "newgame(): topmost card must be face-up, correcting it\n");
	--rules.facedown;
	++rules.faceup;
    }
    rest = rules.numcards % rules.numslots;	/* cards that are too much */
    if (rules.numcards < rest + rules.numslots * (rules.faceup + rules.facedown)) {
	fprintf(stderr, "newgame(): too many cards specified, resetting to min values\n");
	rules.faceup = 1;
	rules.facedown = 0;
    }

    /* generate nice distribution of rest cards */
    if (rest > rules.numtmps || (rules.variant & FORCE_SLOTS))
 	/* distribute on slots */
	distribute(xx+FIRST_SLOT, rest, rules.numslots);
    else
	/* distribute on tmps */
	distribute(xx+LAST_SLOT+1, rest, rules.numtmps);

    for (i = 0; i <= FIRST_SLOT; ++i)
	game.ind[i] = 0;
    for (i = FIRST_SLOT; i <= LAST_SLOT; ++i) {
	int j;
	game.ind[i+1] = game.ind[i] + rules.facedown + rules.faceup + xx[i];
	for (j = game.ind[i+1] - rules.faceup; j < game.ind[i+1]; ++j)
	    game.visible[j] = 1;		/* card is turned */
    }
    while (++i < game.numpiles)
	game.ind[i] = game.ind[i-1] + xx[i-1];
    if (!rules.facedown)		/* no cards facedown */
	for (i = 0; i < rules.numcards; ++i)
	    game.visible[i] = 1;
}

void newgame(long seed)
{   int i;

    init_seed(seed);
    shuffle();	/* initialize game.cards */

    for (i = 0; i < rules.numcards; ++i)
	game.visible[i] = 0;
    for (i = 0; i < game.numpiles; ++i)
	game.ind[i] = 0;	/* no cards on the piles */
    game.ind[i] = rules.numcards;	/* rest on the deck */

    if (rules.new_game)
	(*rules.new_game)();
    else
	gen_newgame();

    if (rules.variant & DECK_SOURCE)	/* Klondike! */
	game.ind[IDECK] = game.ind[VDECK];

    for (i = 0; i < game.numpiles; ++i)
	pile_resize(i);
    (void)do_move(-1, -1);		/* reset data */
}

/* generic give-cards routine */

Move give_new_cards(void)
{   int i, rem;
    rem = CARDS_ON_PILE(IDECK);
    if (rules.numturns) {
	/* Klondike - type of giving cards */
	if (rem > rules.numturns)
	    rem = rules.numturns;
	if (rem) {
	    for (i = 0; i < rem; ++i)
		do_move(INDEX_OF_LAST_CARD(IDECK), VDECK);
	    if (CARDS_ON_PILE(VDECK) == rem)
		draw_pileupdate(VDECK, 0);
	} else {
	    while (!EMPTY(VDECK))
		do_move(INDEX_OF_LAST_CARD(VDECK), IDECK);
	    ++game.flipnum;
	    draw_pileupdate(IDECK, 0);
	    /* draw_pileupdate(VDECK, 0); */
	}
    } else {
	if (rem > rules.numslots)
	    rem = rules.numslots;
	for (i = 0; i < rem; ++i)
	    do_move(INDEX_OF_LAST_CARD(IDECK), rules.numstacks + i);
    }
    if (CARDS_ON_PILE(IDECK) == 0)		/* has given last card */
	draw_pileupdate(IDECK, 0);	/* force deck to be redrawn */
    return NEW_CARDS_MOVE | MOVE(0, rem);
}


/* check, if src... up to last card is of same color */

static int stackable(int src)	/* src is index of a card */
{   Pileindex srcpile = getpile(src);
    Cardindex lastcard, i;

    if (IS_JOKER(game.cards[src]))
	return 0;
    lastcard = INDEX_OF_LAST_CARD(srcpile);
    for (i = src+1; i <= lastcard; ++i) {
	Card c;
	c = game.cards[i];
	if (IS_JOKER(c))
	    return 0;
	if (SUIT(game.cards[i-1]) != SUIT(c))
	    return 0;
	if (RANK(game.cards[i-1]) != RANK(c) + 1)
	    return 0;
    }
    return 1;
}

int move_valid(int src, int dstpile)
{   int srcpile, dstcard;
    srcpile = getpile(src);

    if (srcpile == dstpile)
	return 0;
    switch (game.piletype[srcpile]) {	/* filter out some invalid sources */
    case FacedownDeck:
	return 0;
    case Stack:
	if (!(rules.variant & STACK_SOURCE))
	    return 0;
	break;
    case FaceupDeck:			/* always valid sources */
    case Tmp:
    case Slot:
	break;
    }
    dstcard = EMPTY(dstpile) ? -1 : game.cards[game.ind[dstpile+1]-1];
    switch (game.piletype[dstpile]) {
    case Stack:
	{   Cardindex lastcard = INDEX_OF_LAST_CARD(srcpile);
	    if (SUIT(dstpile) != SUIT(game.cards[src]))
		return 0;
	    if (!stackable(src))
		return 0;
	    /* src is a block of cards without jokers */
	    if (dstcard == -1) { /* empty stack */
		if (RANK(game.cards[lastcard]) != Ace)
		    return 0;
	    } else {
		if (RANK(game.cards[lastcard]) !=
		    1 + RANK(dstcard))
		    return 0;
	    }
	    /* top card matches */
	    if (rules.variant & STACK_SINGLE)
		return lastcard == src;
	    else
		return lastcard - src + 1 == rules.cards_per_color;
	}
    case Slot:
	if (dstcard == -1) {
	    if (rules.variant & EMPTY_KINGS) {   /* Klondike: only Kings are */
		if (!IS_JOKER(game.cards[src]) && RANK(game.cards[src]) != King)
		    return 0;
	    }
	    return 1;
	}
	return (*rules.relaxed_valid)(dstcard, game.cards[src]);
    case Tmp:
	if (game.ind[srcpile+1] - src != 1)
	    return 0;		/* only one card can be moved */
	return dstcard == -1;
    default:
	return 0;
    }
}

int Spider_alternate(Card h, Card l)
{   if (IS_JOKER(h) || IS_JOKER(l))
	return 1;
    return RANK(h) == 1 + RANK(l) && SUIT(h) == SUIT(l);
}

int Spider_relaxed_alternate(Card h, Card l)
{   if (IS_JOKER(h) || IS_JOKER(l))
	return 1;
    return RANK(h) == 1 + RANK(l);
}

int HM_alternate(Card h, Card l)
{   if (IS_JOKER(h) || IS_JOKER(l))
      return 1;                        /* a joker fits all */
    if (!DIFFERENT_COLOR(h, l))
      return 0;                        /* sex doesn't fit */
    return RANK(h) == 1 + RANK(l);     /* rank fits? */
}

extern struct rules Spider_rules, Gypsy_rules, Klondike_rules;
extern struct rules Seahaven_rules, FreeCell_rules;

struct graphic graphic;
struct game game;
struct rules rules;
struct card card;

struct rules *rulepool[] = {
    &Spider_rules,
    &Gypsy_rules,
    &Klondike_rules,
    &Seahaven_rules,
    &FreeCell_rules
};

void new_rules(const char *ruleset, int decks, int slots,
    int faceup, int facedown, int jokers, int flips, int turn)
{   int i;
    game.seed = -1L;		/* no replay is valid */
				/* that will set finished to False */
    for (i = 0; i < sizeof(rulepool) / sizeof(struct rules *); ++i)
	if (!strcmp(ruleset, rulepool[i]->shortname)) {
	    rules = *rulepool[i];
	    goto customize;
	}
    fprintf(stderr, "Unknown rule set. Known rules are:\n");
    for (i = 0; i < sizeof(rulepool) / sizeof(struct rules *); ++i)
	fprintf(stderr, "%-14s -- %s\n", rulepool[i]->shortname,
	   rulepool[i]->longname);
    exit(1);

customize:
    /* rule customization: */
    if (jokers >= 0 && jokers != rules.numjokers) {
	rules.numjokers = jokers;
	rules.variant |= CUSTOM_JOKERS;
	rules.maxscore = 0;	/* custom: maxscore unknown */
    }
    if (flips >= 0 && flips != rules.numflips) {
	rules.numflips = flips;
	rules.variant |= CUSTOM_FLIPS;
	rules.maxscore = 0;	/* custom: maxscore unknown */
    }
    if (turn > 0 && turn != rules.numturns) {
	rules.numturns = turn;
	rules.variant |= CUSTOM_TURNS;
	rules.maxscore = 0;	/* custom: maxscore unknown */
    }
    if (faceup >= 0 && faceup != rules.faceup) {
	rules.faceup = faceup;
	rules.variant |= CUSTOM_FACEUP;
	rules.maxscore = 0;	/* custom: maxscore unknown */
    }
    if (facedown >= 0 && facedown != rules.facedown) {
	rules.facedown = facedown;
	rules.variant |= CUSTOM_FACEDOWN;
	rules.maxscore = 0;	/* custom: maxscore unknown */
    }
    if (slots > 0 && slots != rules.numslots) {
	rules.numslots = slots;
	rules.variant |= CUSTOM_SLOTS;
	rules.maxscore = 0;	/* custom: maxscore unknown */
    }
    if (decks > 0 && decks != rules.numdecks) {
	rules.numdecks = decks;
	rules.variant |= CUSTOM_DECKS;
	rules.maxscore = 0;	/* custom: maxscore unknown */
    }
    rules.numstacks = rules.numdecks * 4;
    rules.numcards = rules.cards_per_color * rules.numstacks + rules.numjokers;
    if (rules.numcards > MAXCARDS) {
	fprintf(stderr, "new_rules(): parameters give too many cards\n");
	exit(1);
    }
    if (rules.numslots + rules.numstacks + 1 > MAXPILES) {
	fprintf(stderr, "new_rules(): parameters give too many slots\n");
	exit(1);
    }
    for (i = 0; i < rules.numstacks; ++i)
	game.piletype[i] = Stack;
    for (; i < rules.numstacks + rules.numslots; ++i)
	game.piletype[i] = Slot;
    for (; i < rules.numstacks + rules.numslots + rules.numtmps; ++i)
	game.piletype[i] = Tmp;
    if (rules.variant & DECK_SOURCE)
	game.piletype[i++] = FaceupDeck;
    game.piletype[i++] = FacedownDeck;
    game.numpiles = i;
}
