Freecell68k/freecell.c
2019-05-03 21:29:26 -07:00

265 lines
5.8 KiB
C

#include "freecell.h"
#include "common.h"
#include <string.h>
#define FC_CEXPAND 2
static void SequenceDeck(Card *deck);
static void AddColumnCard(FCColumn **col, Card card);
static Card GetColumnCard(FCColumn *col);
static Card RemoveColumnCard(FCColumn *col);
static FCColumn *ExpandColumnSize(FCColumn *old, Size ptrSize);
static Boolean AlternatingColours(Card a, Card b);
void FreecellInit(FCState *state) {
memset(state, 0, sizeof(*state));
}
void FreecellStartGame(FCState *state, ushort seed) {
state->moves = 0;
state->seedno = seed;
state->cols = FreecellShuffle(seed, state->cols);
memset(state->store, 0, sizeof(state->store));
memset(state->foundation, 0, sizeof(state->foundation));
}
FCColumn **FreecellShuffle(ushort seed, FCColumn **deal) {
ulong state = seed;
ushort i, j;
Card deck[CARD_QTY];
/* Allocate deal memory */
if(!deal) {
deal = (FCColumn **) NewPtr(FC_COLS * sizeof(FCColumn *));
if(!deal) return NULL;
for(i = 0; i < FC_COLS; ++i) {
deal[i] = (FCColumn *) NewPtr(SIZEOF_FCCOLUMN(FC_DCSIZE));
if(!deal[i]) goto err_inner;
}
}
SequenceDeck(deck);
/* Freecell deal algorithm */
for(i = 0; i < CARD_QTY - 1; ++i) {
ushort cidx;
Card toswap;
state = (state * 214013L + 2531011L) & 0x7FFFFFFFL;
cidx = CARD_QTY - 1 - ((state >> 16) % (CARD_QTY - i));
toswap = deck[cidx];
deck[cidx] = deck[i];
deck[i] = toswap;
}
for(i = 0; i < FC_COLS; ++i) {
for(j = 0; 8*j + i < CARD_QTY; ++j) {
deal[i]->cards[j] = deck[8*j + i];
}
deal[i]->qty = j;
}
return deal;
err_inner:
/* Inner deal array error */
while(i--) {
DisposPtr(deal[i]);
}
DisposPtr(deal);
return NULL;
}
void FreecellDisposeDeal(FCColumn **deal) {
ushort i;
if(deal) {
for(i = 0; i < FC_COLS; ++i) {
DisposPtr(deal[i]);
}
DisposPtr(deal);
}
}
/* Legal move requirements:
1: If to is a LastCard, then val(to) == NULL OR
val(to) - 1 == val(from) and to, from have alternating colours.
2: If to is a storage, then val(to) == CARD_NULL
3: If to is a foundation, then val(to) + 1 == val(from) OR
val(to) == NULL and val(from) == 1 and have the same suit.
*/
Boolean FreecellLegalMove(FCState *state, FCZone from, FCZone to) {
Card cardFrom = GetCardAt(state, from);
Card cardTo = GetCardAt(state, to);
if(CARD_EMPTY(cardFrom))
return false;
switch(to.zone) {
case FCZ_LASTCARD:
return CARD_EMPTY(cardTo)
|| (CARD_GETNUM(cardTo) - 1 == CARD_GETNUM(cardFrom)
&& AlternatingColours(cardTo, cardFrom));
case FCZ_COLUMN:
/* Can refer to empty columns as either */
return CARD_EMPTY(cardTo) && CARD_GETNUM(cardFrom) == 13;
case FCZ_STORAGE:
return CARD_EMPTY(cardTo);
case FCZ_FOUNDATION:
return (CARD_EMPTY(cardTo) && CARD_GETNUM(cardFrom) == 1)
|| (CARD_GETNUM(cardTo) + 1 == CARD_GETNUM(cardFrom)
&& CARD_GETSUIT(cardTo) == CARD_GETSUIT(cardFrom));
}
return false;
}
Boolean FreecellPlayMove(FCState *state, FCZone from, FCZone to) {
if(!FreecellLegalMove(state, from, to))
return false;
FreecellForceMove(state, from, to);
++state->moves;
state->lastMove[0] = from;
state->lastMove[1] = to;
return true;
}
void FreecellForceMove(FCState *state, FCZone from, FCZone to) {
Card moveCard;
switch(from.zone) {
case FCZ_LASTCARD:
moveCard = RemoveColumnCard(state->cols[from.num]);
break;
case FCZ_STORAGE:
moveCard = state->store[from.num];
state->store[from.num] = CARD_NULL;
break;
case FCZ_FOUNDATION:
moveCard = state->foundation[from.num];
if(CARD_GETNUM(moveCard) == 1) {
state->foundation[from.num] = CARD_NULL;
} else {
ushort num = CARD_GETNUM(moveCard) - 1;
CARD_SETNUM(state->foundation[from.num], num);
}
break;
}
switch(to.zone) {
case FCZ_LASTCARD:
AddColumnCard(&state->cols[to.num], moveCard);
break;
case FCZ_STORAGE:
state->store[to.num] = moveCard;
break;
case FCZ_FOUNDATION:
state->foundation[to.num] = moveCard;
break;
}
}
void FreecellUndoMove(FCState *state) {
FCZone tmp;
if(state->lastMove[0].zone == FCZ_NONE)
return;
FreecellForceMove(state, state->lastMove[1], state->lastMove[0]);
--state->moves;
tmp = state->lastMove[0];
state->lastMove[0] = state->lastMove[1];
state->lastMove[1] = tmp;
}
void SequenceDeck(Card *deck) {
ushort i;
deck[0] = TO_CARD(0, 3, 13);
for(i = 1; i < CARD_QTY; ++i) {
if(CARD_GETSUIT(deck[i-1]) == 0)
deck[i] = TO_CARD(0, 3, CARD_GETNUM(deck[i-1]) - 1);
else
deck[i] = deck[i-1] - TO_CARD(0, 1, 0);
}
}
void AddColumnCard(FCColumn **col, Card card) {
FCColumn *workCol = *col;
Size ptrSize, reqSize;
if(workCol->qty >= FC_DCSIZE) {
ptrSize = GetPtrSize(workCol);
reqSize = SIZEOF_FCCOLUMN(workCol->qty+1);
if(reqSize > ptrSize) {
*col = ExpandColumnSize(workCol, ptrSize);
workCol = *col;
}
}
workCol->cards[workCol->qty] = card;
++workCol->qty;
}
static Card GetColumnCard(FCColumn *col) {
if(col->qty == 0)
return CARD_NULL;
return col->cards[col->qty-1];
}
Card RemoveColumnCard(FCColumn *col) {
Card card;
if(col->qty == 0)
return CARD_NULL;
--col->qty;
card = col->cards[col->qty];
col->cards[col->qty] = CARD_NULL;
return card;
}
FCColumn *ExpandColumnSize(FCColumn *old, Size ptrSize) {
FCColumn *new = (FCColumn *) NewPtr(ptrSize + FC_CEXPAND * sizeof(Card));
memcpy(new, old, ptrSize);
return new;
}
Card GetCardAt(FCState *state, FCZone elem) {
switch(elem.zone) {
case FCZ_LASTCARD:
case FCZ_COLUMN:
return GetColumnCard(state->cols[elem.num]);
case FCZ_STORAGE:
return state->store[elem.num];
case FCZ_FOUNDATION:
return state->foundation[elem.num];
}
return CARD_NULL;
}
Boolean AlternatingColours(Card a, Card b) {
if(CARD_EMPTY(a) || CARD_EMPTY(b))
return false;
if(CARD_GETSUIT(a) == C_DIAMOND || CARD_GETSUIT(a) == C_HEART) {
return CARD_GETSUIT(b) == C_CLUB || CARD_GETSUIT(b) == C_SPADE;
}
return CARD_GETSUIT(b) == C_DIAMOND || CARD_GETSUIT(b) == C_HEART;
}