/*****************************************************************************/ /* */ /* */ /* X patience version 2 -- module moves.c */ /* */ /* General utility functions for all rule sets */ /* written by Heiko Eissfeldt and Michael Bischoff */ /* see COPYRIGHT.xpat2 for Copyright details */ /* */ /* */ /*****************************************************************************/ #include "xpatgame.h" #include #include /* for INT_MAX */ #include /* RNG patch: */ int rng = 1; static int myrand(int limit) { /* select one of the RNGs. */ return rng ? (int)(((double)(prand()) / (double)PRANDMAX) * (double)limit) : prand() % limit; } static Move Klondike_deal_cards(void) { int rem = CARDS_ON_PILE(IDECK); if (rem > rules.param[2]) rem = rules.param[2]; graphics_pile_control(Disable, IDECK); graphics_pile_control(Disable, VDECK); if (rem) { int i; /* without graphics: */ for (i = 0; i < rem; ++i) do_move(INDEX_OF_LAST_CARD(IDECK), VDECK); } else { while (!EMPTY(VDECK)) do_move(INDEX_OF_LAST_CARD(VDECK), IDECK); ++game.counter[1]; } graphics_pile_control(EnableAndRedraw, IDECK); graphics_pile_control(EnableAndRedraw, VDECK); return NEW_CARDS_MOVE | MOVE(0, rem); } static void Klondike_undeal_cards(int num) { graphics_pile_control(Disable, IDECK); graphics_pile_control(Disable, VDECK); if (!num) { /* undo flip */ while (!EMPTY(IDECK)) do_move(INDEX_OF_LAST_CARD(IDECK), VDECK); --game.counter[1]; } else { int i; for (i = 0; i < num; ++i) do_move(INDEX_OF_LAST_CARD(VDECK), IDECK); } graphics_pile_control(EnableAndRedraw, IDECK); graphics_pile_control(EnableAndRedraw, VDECK); } Move SlotShuffle(int fwd) { int i, tab[MAXCARDS]; int num = INDEX_OF_FIRST_CARD(LAST_SLOT+1) - INDEX_OF_FIRST_CARD(FIRST_SLOT); Cardindex start = INDEX_OF_FIRST_CARD(FIRST_SLOT); sprand(game.seed); for (i = 0; i < num; ++i) tab[i] = myrand(num - i) + i; if (fwd) { for (i = 0; i < num; ++i) if (tab[i] != i) { /* swap card */ Card c; c = game.cards[start+i]; game.cards[start+i] = game.cards[start+tab[i]]; game.cards[start+tab[i]] = c; } } else { for (i = num-1; i >= 0; --i) if (tab[i] != i) { /* swap card */ Card c; c = game.cards[start+i]; game.cards[start+i] = game.cards[start+tab[i]]; game.cards[start+tab[i]] = c; } } for (i = FIRST_SLOT; i <= LAST_SLOT; ++i) draw_pileupdate(i, 0); return SHUFFLING; } #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 memo_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(EXIT_FAILURE); } } static void makespace(int n) { if (game.move_ptr + n > game.numalloc) { Move *tmpnew; if (!(tmpnew = realloc(game.move, (game.numalloc+256) * sizeof(Move)))) { fprintf(stderr, "out of memory. saving game in xpat.tmp\n"); save_game("xpat.tmp"); exit(EXIT_FAILURE); } game.numalloc += 256; game.move = tmpnew; } } /* A new move is done. This resets the limit of redo to the current point. */ /* Also, the Bookmark is possibly taken back */ /* if the AUTOFILL_TMPS feature is set, empty tmps are filled from the deck */ void store_move(Move m) { makespace(1); if (game.bookmark > game.move_ptr) /* reset Bookmark position */ game.bookmark = game.move_ptr; if ((rules.variant & AUTOFILL_TMPS) && (m & SPECIAL_MOVE) != SPECIAL_MOVE) { Pileindex srcpile = DSTPILE(m >> 16); if (game.piletype[srcpile] == Tmp && (!EMPTY(IDECK) || !EMPTY(VDECK))) { /* this is the case we have to deal with */ makespace(3); game.move[game.move_ptr++] = COMPOUND_BEGIN; game.move[game.move_ptr++] = m; game.move[game.move_ptr++] = !EMPTY(VDECK) ? do_move(INDEX_OF_LAST_CARD(VDECK), srcpile) : do_move(INDEX_OF_LAST_CARD(IDECK), srcpile); m = COMPOUND_END; } } game.move[game.move_ptr] = m; game.stored_moves = ++game.move_ptr; ++game.n_moves; /* now that the work is done, check for finished game */ check_win_game(); } static void undo_give_cards(int num) { /* num = number of cards */ /* printf("undo give %d cards\n", num); */ if (rules.undeal_cards) (*rules.undeal_cards)(num); else if (rules.variant & KLONDIKE_DEAL) Klondike_undeal_cards(num); else { int i; graphics_pile_control(Disable, IDECK); for (i = FIRST_SLOT + num; i != FIRST_SLOT; --i) /* slot i-1 back to deck */ do_move(game.ind[i]-1, IDECK); graphics_pile_control(EnableAndRedraw, IDECK); } } int undo_move(void) { Move m; int retval = 1; /* no cheat */ if (!game.move_ptr) { /* possibly undo restart game? */ if (game.stored_moves) { jumpto_movenr(game.stored_moves); /* must replay the game */ return 1; } return 0; } m = game.move[--game.move_ptr]; --game.n_moves; if ((m & SPECIAL_MOVE) == SPECIAL_MOVE) { switch (m & SPECIAL_MASK) { case COMPOUND_END: { int remember_count, remgraphic; if ((remgraphic = game.graphic)) graphics_control(Disable); /* undo multiple moves */ remember_count = game.n_moves; while (game.move[game.move_ptr-1] != COMPOUND_BEGIN) { if (undo_move() == 2) retval = 2; } --game.move_ptr; game.n_moves = remember_count; if (remgraphic) graphics_control(EnableAndRedraw); } return retval; case ADD_CHEAT: game.cheat_count += (int)(m & SPECIAL_ARGS); return 2; case NEW_CARDS_MOVE: undo_give_cards(DSTPILE(m)); if (DSTPILE(m)) { game.cheat_count += DSTPILE(m); return 2; } return 1; case ROTATE_UP: /* change counter */ { int c; for (c = 0; c < 4; ++c) if (rules.paramstring[c] == TXTI_ROTATE) game.counter[c] -= 2; } RotateDown(SRCIND(m)); return 1; case ROTATE_DOWN: /* change counter */ { int c; for (c = 0; c < 4; ++c) if (rules.paramstring[c] == TXTI_ROTATE) game.counter[c] -= 2; } RotateUp(SRCIND(m)); return 1; case SHUFFLING: SlotShuffle(0); return 1; default: /* printf("Move in error is %08lx\n", m); */ assert(0); } return 0; } /* standard move follows */ if (m & MOVE_TURNED) { m >>= 16; /* upper part = undo information */ ++game.cheat_count; game.visible[game.ind[DSTPILE(m)+1]-1] = 0; /* invisible again! */ retval = 2; } else m >>= 16; /* upper part = undo information */ (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.move_ptr == game.stored_moves) return 0; /* no redo possible */ m = game.move[game.move_ptr++]; ++game.n_moves; if ((m & SPECIAL_MOVE) == SPECIAL_MOVE) { switch (m & SPECIAL_MASK) { case COMPOUND_BEGIN: { int remember_count, remgraphic; if ((remgraphic = game.graphic)) graphics_control(Disable); /* redo multiple moves */ remember_count = game.n_moves; while (game.move[game.move_ptr] != COMPOUND_END) { if (redo_move() == 2) retval = 2; } ++game.move_ptr; game.n_moves = remember_count; if (remgraphic) graphics_control(EnableAndRedraw); } return retval; case ADD_CHEAT: game.cheat_count -= (int)(m & SPECIAL_ARGS); return 2; case ROTATE_UP: RotateUp(SRCIND(m)); return 1; case ROTATE_DOWN: RotateDown(SRCIND(m)); return 1; case NEW_CARDS_MOVE: retval = DSTPILE(give_new_cards()); if (retval) { game.cheat_count -= retval; return 2; } return 1; case SHUFFLING: SlotShuffle(1); return 1; default: assert(0); } } if (m & MOVE_TURNED) { --game.cheat_count; retval = 2; } (void)do_move(SRCIND(m), DSTPILE(m)); return retval; } static void shuffle(void) { int i, d, v, c; int tmp[MAXCARDS]; if (game.graphic) show_message(" "); 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[myrand(rules.numcards)] = JOKER + c++; /* subst old cards */ for (i = rules.numcards; i; --i) { v = myrand(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; } /* These are the central card moving routines, i.e. the only functions which */ /* change the contents of the game.cards array within a game. */ /* Every time anything is changed, one of these functions is called. */ /* We can reset hint tables at this point. */ Move RotateUp(Cardindex src) { Pileindex p = getpile(src); int c; Cardindex i; cmd_CancelSelection(); i = INDEX_OF_LAST_CARD(p); c = game.cards[i]; while (--i >= src) game.cards[i+1] = game.cards[i]; game.cards[i+1] = c; draw_pileupdate(p, 0); /* change counter */ for (c = 0; c < 4; ++c) if (rules.paramstring[c] == TXTI_ROTATE) ++game.counter[c]; return ROTATE_UP | MOVE(src, 0); } Move RotateDown(Cardindex src) { Pileindex p = getpile(src); int c; Cardindex i; cmd_CancelSelection(); c = game.cards[src]; for (i = src; i < INDEX_OF_LAST_CARD(p); ++i) game.cards[i] = game.cards[i+1]; game.cards[i] = c; draw_pileupdate(p, 0); /* change counter */ for (c = 0; c < 4; ++c) if (rules.paramstring[c] == TXTI_ROTATE) ++game.counter[c]; return ROTATE_DOWN | MOVE(src, 0); } Move do_move(Cardindex srcindex, Pileindex dstpile) { int srcpile, numcards; int tmp[MAXCARDS]; int vis; Move m; cmd_CancelSelection(); /* calls cmd_ResetHints() */ 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] && (srcpile != IDECK || rules.variant & DECK_VISIBLE)) { game.visible[srcindex-1] = 1; m |= MOVE_TURNED; } memcpy(tmp, game.cards+srcindex, numcards * sizeof(int)); /* moved cards are normally visible */ vis = dstpile != IDECK || rules.variant & DECK_VISIBLE; /* 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) move_card_data(i, i+numcards); memcpy(game.cards+i, tmp, numcards * sizeof(int)); for (; i < game.ind[dstpile+1]; ++i) game.visible[i] = vis; 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) move_card_data(i+numcards, 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] = vis; for (i = dstpile+1; i <= srcpile; ++i) game.ind[i] += numcards; } #ifdef DEBUG count(); #endif /* inform graphics interface of change */ draw_pileupdate(srcpile, -numcards); draw_pileupdate(dstpile, numcards); /* printf("Move done is %08lx\n", m); */ return m; } 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.move_ptr = 0; game.counter[0] = 0; game.counter[1] = 0; game.counter[2] = 0; game.counter[3] = 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; } if (rules.newgame_bits & SLOTS_SAME) rest = 0; else 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.newgame_bits & 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]; /* all cards on the tmps are faceup */ for (i = game.ind[LAST_SLOT+1]; i < game.ind[IDECK]; ++i) game.visible[i] = 1; if (rules.variant & DECK_VISIBLE) game.visible[rules.numcards-1] = 1; /* topmost card on deck */ if (!rules.facedown) for (i = game.ind[FIRST_SLOT]; i < game.ind[LAST_SLOT+1]; ++i) game.visible[i] = 1; /* all cards on the slots visible */ } static void MO_newgame(void) { int i, remcards = rules.numcards; /* specific part: */ for (i = 0; i < rules.numslots; ++i) { int here; here = min(remcards, rules.faceup + rules.facedown); remcards -= here; game.ind[rules.numstacks+i+1] = game.ind[rules.numstacks+i] + here; if (here) { Cardindex j; j = game.ind[rules.numstacks+i+1] - 1; do game.visible[j--] = 1; while (j >= game.ind[rules.numstacks+i] + rules.facedown); } } } void newgame(long seed) { int i; game.randomgame = (seed == -1L); game.savecount = 0; game.bookmark = 0; 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 if (rules.newgame_bits & SEQUENTIAL) MO_newgame(); else gen_newgame(); if (game.graphic) for (i = 0; i < game.numpiles; ++i) pile_resize(i); /* (void)do_move(-1, -1); */ /* reset game.*/ cmd_ResetHints(); game.srcind = UNSELECTED; } /* generic give-cards routine */ Move give_new_cards(void) { if (rules.deal_cards) return (*rules.deal_cards)(); else if (rules.variant & KLONDIKE_DEAL) return Klondike_deal_cards(); else { int i, rem; graphics_pile_control(Disable, IDECK); rem = CARDS_ON_PILE(IDECK); if (rem > rules.numslots) rem = rules.numslots; for (i = 0; i < rem; ++i) do_move(INDEX_OF_LAST_CARD(IDECK), rules.numstacks + i); graphics_pile_control(EnableAndRedraw, IDECK); return NEW_CARDS_MOVE | MOVE(0, rem); } } extern struct rules Spider_rules, Gypsy_rules, Klondike_rules, modSpider_rules; extern struct rules Seahaven_rules, FreeCell_rules, IdiotsDelight_rules, MonteCarlo_rules, MidnightOil_rules, Calculation_rules, Michaels_rules, Canfield_rules, modCanfield_rules, Royal_rules, Bakers_rules, Oonsoo_rules, BlueMoon_rules; struct game game; struct rules rules; struct rules *rulepool[] = { &Spider_rules, &Gypsy_rules, &Klondike_rules, &Seahaven_rules, &FreeCell_rules, &IdiotsDelight_rules, &MonteCarlo_rules, &MidnightOil_rules, &Calculation_rules, &modCanfield_rules, &Michaels_rules, &Canfield_rules, &Royal_rules, &Bakers_rules, &Oonsoo_rules, &BlueMoon_rules, NULL }; struct rules *getrules(const char *ruleset) { int i; for (i = 0; rulepool[i]; ++i) if (!strcmp(ruleset, rulepool[i]->shortname) || (rulepool[i]->abbrev && !strcmp(ruleset, rulepool[i]->abbrev))) { return rulepool[i]; } return NULL; } void new_rules(const char *ruleset, int decks, int slots, int faceup, int facedown, int jokers, int tmps, int param0, int param1, int param2, int param3) { int i; struct rules *rp; if (!game.numalloc) memo_alloc(512); game.seed = -1L; /* no replay is valid */ /* that will set finished to False */ if (!(rp = getrules(ruleset))) { fprintf(stderr, "Unknown rule set. Known rules are:\n"); for (i = 0; rulepool[i]; ++i) { char buff[20]; if (rulepool[i]->abbrev) sprintf(buff, "(Abbrev %-2s)", rulepool[i]->abbrev); else strcpy(buff, " "); fprintf(stderr, "%-15s %s -- %s\n", rulepool[i]->shortname, buff, rulepool[i]->longname ? rulepool[i]->longname : ""); } exit(EXIT_FAILURE); } rules = *rp; /* copy std game.*/ /* rule customization: */ if (!(rules.customizable & CUSTOM_DECKS )) decks = -1; if (!(rules.customizable & CUSTOM_SLOTS )) slots = -1; if (!(rules.customizable & CUSTOM_JOKERS)) jokers = -1; if (!(rules.customizable & CUSTOM_TMPS )) tmps = -1; if (!(rules.customizable & CUSTOM_FACEUP)) faceup = -1; if (!(rules.customizable & CUSTOM_FACEDOWN)) facedown = -1; if (!(rules.customizable & CUSTOM_PARAM0)) param0 = -1; if (!(rules.customizable & CUSTOM_PARAM1)) param1 = -1; if (!(rules.customizable & CUSTOM_PARAM2)) param2 = -1; if (!(rules.customizable & CUSTOM_PARAM3)) param3 = -1; if (jokers >= 0 && jokers != rules.numjokers) { rules.numjokers = jokers; rules.customized |= CUSTOM_JOKERS; rules.maxscore = 0; /* custom: maxscore unknown */ } if (param0 >= 0 && param0 != rules.param[0]) { rules.param[0] = param0; rules.customized |= CUSTOM_PARAM0; rules.maxscore = 0; /* custom: maxscore unknown */ } if (param1 >= 0 && param1 != rules.param[1]) { rules.param[1] = param1; rules.customized |= CUSTOM_PARAM1; rules.maxscore = 0; /* custom: maxscore unknown */ } if (param2 > 0 && param2 != rules.param[2]) { rules.param[2] = param2; rules.customized |= CUSTOM_PARAM2; rules.maxscore = 0; /* custom: maxscore unknown */ } if (param3 >= 0 && param3 != rules.param[3]) { rules.param[3] = param3; rules.customized |= CUSTOM_PARAM3; } if (faceup >= 0 && faceup != rules.faceup) { rules.faceup = faceup; rules.customized |= CUSTOM_FACEUP; rules.maxscore = 0; /* custom: maxscore unknown */ } if (facedown >= 0 && facedown != rules.facedown) { rules.facedown = facedown; rules.customized |= CUSTOM_FACEDOWN; rules.maxscore = 0; /* custom: maxscore unknown */ } if (slots > 0 && slots != rules.numslots) { rules.numslots = slots; rules.customized |= CUSTOM_SLOTS; rules.maxscore = 0; /* custom: maxscore unknown */ } if (decks > 0 && decks != rules.numdecks) { rules.numdecks = decks; rules.customized |= CUSTOM_DECKS; rules.maxscore = 0; /* custom: maxscore unknown */ } if (tmps >= 0 && tmps != rules.numtmps) { rules.numtmps = tmps; rules.customized |= CUSTOM_TMPS; } if (rules.numstacks >= 4) rules.numstacks = rules.numdecks * 4; rules.numcards = rules.cards_per_color * 4 * rules.numdecks + rules.numjokers; if (rules.numcards > MAXCARDS) { fprintf(stderr, "new_rules(): parameters give too many cards\n"); exit(EXIT_FAILURE); } if (rules.numslots + rules.numstacks + 1 > MAXPILES) { fprintf(stderr, "new_rules(): parameters give too many slots\n"); exit(EXIT_FAILURE); } 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; for (i = 0; i <= game.numpiles; ++i)/* this fixes coredump-bug */ game.ind[i] = 0; /* no card currently there */ if (rules.initfunc) /* initrules-hook */ (*rules.initfunc)(); rp->inited = 1; }