mirror of
https://github.com/StevenMcLeod/Freecell68k.git
synced 2024-11-23 05:33:14 +00:00
Initial commit
This commit is contained in:
parent
8af048cee7
commit
4b383664bb
BIN
FreeCell..rsrc
Normal file
BIN
FreeCell..rsrc
Normal file
Binary file not shown.
26
bitset.h
Normal file
26
bitset.h
Normal file
@ -0,0 +1,26 @@
|
||||
//
|
||||
// Created by Steven on 10-Dec-17.
|
||||
//
|
||||
|
||||
#ifndef BITSET_H
|
||||
#define BITSET_H
|
||||
|
||||
//Reference: https://graphics.stanford.edu/~seander/bithacks.html#ConditionalSetOrClearBitsWithoutBranching
|
||||
|
||||
#define EVAL_BOOL(n) ((n)!=0)
|
||||
|
||||
#define BITS_SET(val, bits) (val) |= (bits)
|
||||
#define BITS_RESET(val, bits) (val) &= ~(bits)
|
||||
#define BITS_FLIP(val, bits) (val) ^= (bits)
|
||||
#define BITS_TEST(val, bits) ((val) & (bits))
|
||||
#define BITS_TESTNOT(val, bits) (~(val) & (bits))
|
||||
|
||||
#define BITS_SET_BIT(val, bitpos) BITS_SET(val, 1 << (bitpos))
|
||||
#define BITS_RESET_BIT(val, bitpos) BITS_RESET(val, 1 << (bitpos))
|
||||
#define BITS_FLIP_BIT(val, bitpos) BITS_FLIP(val, 1 << (bitpos))
|
||||
#define BITS_TEST_BIT(val, bitpos) EVAL_BOOL(BITS_TEST(val, 1 << (bitpos)))
|
||||
#define BITS_TESTNOT_BIT(val, bitpos) EVAL_BOOL(!BITS_TESTNOT(val, 1 << (bitpos)))
|
||||
#define BITS_COND_BIT(val, bitpos, bit) do { if(bit) BITS_SET(val, 1 << (bitpos)); else BITS_RESET(val, 1 << (bitpos)); } while(0)
|
||||
|
||||
|
||||
#endif //BITSET_H
|
35
common.h
Normal file
35
common.h
Normal file
@ -0,0 +1,35 @@
|
||||
#ifndef COMMON_H
|
||||
#define COMMON_H
|
||||
|
||||
typedef unsigned short ushort;
|
||||
typedef unsigned long ulong;
|
||||
|
||||
struct PSTRUCT {
|
||||
Byte len;
|
||||
Byte str[];
|
||||
};
|
||||
|
||||
#define ABS(x) ((x)>0?(x):-(x))
|
||||
|
||||
#define HIWORD(x) (((x) & 0xFFFF0000) >> 16)
|
||||
#define LOWORD(x) ((x) & 0x0000FFFF)
|
||||
|
||||
#define TO_PSTRUCT(x) (* (struct PSTRUCT *) (x))
|
||||
|
||||
#define PLEN(x) (TO_PSTRUCT(x).len)
|
||||
#define PSTR(x) (TO_PSTRUCT(x).str)
|
||||
|
||||
#ifndef M_PI
|
||||
#define M_PI 3.1415926535897932
|
||||
#endif
|
||||
|
||||
#define ITOC(n) ((n) + '0')
|
||||
#define CTOI(n) ((n) - '0')
|
||||
|
||||
#define ARRLEN(a) (sizeof(a)/sizeof(*(a)))
|
||||
|
||||
#define L2PT(p,n) do { (p).v = HIWORD(n); (p).h = LOWORD(n); } while(0)
|
||||
#define PT2L(p) (((long)(p).v << 16) | (long) (p).h)
|
||||
#define TO_PTL(v,h) (((long)LOWORD(v) << 16) | (long) LOWORD(h))
|
||||
|
||||
#endif
|
265
freecell.c
Normal file
265
freecell.c
Normal file
@ -0,0 +1,265 @@
|
||||
#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;
|
||||
}
|
63
freecell.h
Normal file
63
freecell.h
Normal file
@ -0,0 +1,63 @@
|
||||
#ifndef FREECELL_H
|
||||
#define FREECELL_H
|
||||
|
||||
#include "common.h"
|
||||
#include "gametypes.h"
|
||||
|
||||
#define FC_STORES 4
|
||||
#define FC_COLS 8
|
||||
#define FC_DCSIZE 13
|
||||
|
||||
/* Seed values [1, 32001) */
|
||||
#define FC_SEEDLO 1
|
||||
#define FC_SEEDHI 32001
|
||||
|
||||
/* GameElem Structs */
|
||||
enum {
|
||||
FCZ_NONE=0,
|
||||
FCZ_LASTCARD,
|
||||
FCZ_COLUMN,
|
||||
FCZ_STORAGE,
|
||||
FCZ_FOUNDATION
|
||||
};
|
||||
|
||||
typedef struct {
|
||||
Byte zone;
|
||||
Byte num;
|
||||
} FCZone;
|
||||
|
||||
#define FCZONE_EQ(a, b) (\
|
||||
((a).zone == (b).zone) \
|
||||
&& ((a).num == (b).num) \
|
||||
)
|
||||
|
||||
#define SIZEOF_FCCOLUMN(qty) (sizeof(FCColumn) + (qty) * sizeof(Card))
|
||||
|
||||
typedef struct {
|
||||
ushort qty;
|
||||
Card cards[];
|
||||
} FCColumn;
|
||||
|
||||
typedef struct {
|
||||
ushort moves;
|
||||
ushort seedno;
|
||||
FCColumn **cols; /* Size FC_COLS */
|
||||
Card store[FC_STORES];
|
||||
Card foundation[4];
|
||||
FCZone lastMove[2];
|
||||
} FCState;
|
||||
|
||||
/* Game Setup */
|
||||
void FreecellInit(FCState *state);
|
||||
void FreecellStartGame(FCState *state, ushort seed);
|
||||
FCColumn **FreecellShuffle(ushort seed, FCColumn **deal);
|
||||
void FreecellDisposeDeal(FCColumn **deal);
|
||||
|
||||
/* Game Play */
|
||||
Card GetCardAt(FCState *state, FCZone elem);
|
||||
Boolean FreecellLegalMove(FCState *state, FCZone from, FCZone to);
|
||||
Boolean FreecellPlayMove(FCState *state, FCZone from, FCZone to);
|
||||
void FreecellForceMove(FCState *state, FCZone from, FCZone to);
|
||||
void FreecellUndoMove(FCState *state);
|
||||
|
||||
#endif /* FREECELL_H */
|
245
gameintf.c
Normal file
245
gameintf.c
Normal file
@ -0,0 +1,245 @@
|
||||
#include "gameintf.h"
|
||||
#include "gamewind.h"
|
||||
#include "gamewindlow.h"
|
||||
#include "gamemenu.h"
|
||||
#include "freecell.h"
|
||||
|
||||
/* HandleGameClick, DragActionProc */
|
||||
static FCZone startHoverElem;
|
||||
static FCZone lastHoverElem;
|
||||
static Boolean lastHoverLegal;
|
||||
|
||||
static pascal void DragActionProc(void);
|
||||
|
||||
static short RndRange(short lower, short upper);
|
||||
|
||||
FCClickErr HandleGameClick(FCState *state, Point hitPt) {
|
||||
FCZone hitElem, dragElem;
|
||||
Point dragPt;
|
||||
long theLPoint;
|
||||
Rect boundsRect = {-3, -3, 3, 3};
|
||||
Rect cardRect;
|
||||
RgnHandle cardRgn;
|
||||
FCClickErr theErr;
|
||||
|
||||
hitElem = GetGamePtLoc(state, hitPt, &cardRect);
|
||||
if(hitElem.zone == FCZ_NONE
|
||||
|| CARD_EMPTY(GetCardAt(state, hitElem))) {
|
||||
theErr = FCCE_BADHIT;
|
||||
goto err;
|
||||
}
|
||||
|
||||
/* Drag has slack to differentiate from double-click */
|
||||
dragPt = hitPt;
|
||||
OffsetRect(&boundsRect, dragPt.h, dragPt.v);
|
||||
while(PtInRect(dragPt, &boundsRect) && StillDown()) {
|
||||
GetMouse(&dragPt);
|
||||
}
|
||||
|
||||
if(!StillDown()) {
|
||||
theErr = FCCE_NODRAG;
|
||||
goto err;
|
||||
}
|
||||
|
||||
/* Rect now starts in a different location */
|
||||
OffsetRect(&cardRect, dragPt.h - hitPt.h, dragPt.v - hitPt.v);
|
||||
cardRgn = NewRgn();
|
||||
OpenRgn();
|
||||
FrameRoundRect(&cardRect, CARD_XRATIO, CARD_YRATIO);
|
||||
CloseRgn(cardRgn);
|
||||
|
||||
/* Create drag outline */
|
||||
boundsRect = FrontWindow()->portRect;
|
||||
startHoverElem = hitElem;
|
||||
lastHoverElem.zone = FCZ_NONE;
|
||||
lastHoverLegal = false;
|
||||
theLPoint = DragGrayRgn(cardRgn, dragPt, &boundsRect,
|
||||
&boundsRect, noConstraint, &DragActionProc);
|
||||
if(BAD_PTL(theLPoint)) {
|
||||
theErr = FCCE_OOBDRAG;
|
||||
goto err;
|
||||
}
|
||||
|
||||
dragPt.v += HIWORD(theLPoint);
|
||||
dragPt.h += LOWORD(theLPoint);
|
||||
dragElem = GetGamePtLoc(state, dragPt, &cardRect);
|
||||
if(dragElem.zone == FCZ_NONE) {
|
||||
theErr = FCCE_BADDRAG;
|
||||
goto err;
|
||||
}
|
||||
|
||||
if(!lastHoverLegal) {
|
||||
theErr = FCCE_BADMOVE;
|
||||
goto err;
|
||||
}
|
||||
|
||||
InvertRoundRect(&cardRect, CARD_XRATIO, CARD_YRATIO);
|
||||
/* Play move */
|
||||
if(!FreecellPlayMove(state, hitElem, dragElem)) {
|
||||
theErr = FCCE_BADMOVE;
|
||||
goto err;
|
||||
}
|
||||
|
||||
GameDrawMove(state, hitElem, dragElem);
|
||||
return FCCE_OK;
|
||||
|
||||
err:
|
||||
/* In case other deinit needs to occur */
|
||||
return theErr;
|
||||
}
|
||||
|
||||
pascal void DragActionProc(void) {
|
||||
Point mousePoint;
|
||||
FCZone hoverElem;
|
||||
Rect hoverRect, unhoverRect;
|
||||
FCState *state;
|
||||
|
||||
/* Invert spaces that are able to be placed */
|
||||
GetMouse(&mousePoint);
|
||||
state = (FCState *) GetWRefCon(FrontWindow());
|
||||
hoverElem = GetGamePtLoc(state, mousePoint, &hoverRect);
|
||||
if(FCZONE_EQ(hoverElem, lastHoverElem))
|
||||
return;
|
||||
|
||||
if(lastHoverLegal) {
|
||||
GetFCZoneRect(state, lastHoverElem, &unhoverRect);
|
||||
InvertRoundRect(&unhoverRect, CARD_XRATIO, CARD_YRATIO);
|
||||
}
|
||||
|
||||
lastHoverElem = hoverElem;
|
||||
lastHoverLegal = FreecellLegalMove(state, startHoverElem, hoverElem);
|
||||
if(lastHoverLegal) {
|
||||
InvertRoundRect(&hoverRect, CARD_XRATIO, CARD_YRATIO);
|
||||
}
|
||||
}
|
||||
|
||||
void GameDrawMove(FCState *state, FCZone from, FCZone to) {
|
||||
Card replaceCard = GetCardAt(state, from);
|
||||
Card moveCard = GetCardAt(state, to);
|
||||
|
||||
switch(from.zone) {
|
||||
case FCZ_LASTCARD:
|
||||
DrawRemovedColCard(state->cols[from.num], from.num);
|
||||
break;
|
||||
|
||||
case FCZ_STORAGE:
|
||||
DrawStorageCard(replaceCard, from.num);
|
||||
break;
|
||||
|
||||
case FCZ_FOUNDATION:
|
||||
DrawFoundationCard(replaceCard, from.num);
|
||||
break;
|
||||
}
|
||||
|
||||
switch(to.zone) {
|
||||
case FCZ_LASTCARD:
|
||||
DrawLastColumnCard(state->cols[to.num], to.num);
|
||||
break;
|
||||
|
||||
case FCZ_STORAGE:
|
||||
DrawStorageCard(moveCard, to.num);
|
||||
break;
|
||||
|
||||
case FCZ_FOUNDATION:
|
||||
DrawFoundationCard(moveCard, to.num);
|
||||
break;
|
||||
}
|
||||
|
||||
if(state->moves <= 1) {
|
||||
MenuUndoState(true);
|
||||
}
|
||||
}
|
||||
|
||||
void GameNewGame(FCState *state, ushort seed) {
|
||||
Boolean needTitleUpdate;
|
||||
if(!seed) {
|
||||
seed = RndRange(FC_SEEDLO, FC_SEEDHI);
|
||||
}
|
||||
|
||||
needTitleUpdate = seed != state->seedno;
|
||||
FreecellStartGame(state, seed);
|
||||
if(needTitleUpdate) {
|
||||
WindUpdateTitle(FrontWindow());
|
||||
}
|
||||
MenuUndoState(false);
|
||||
ForceRedraw();
|
||||
}
|
||||
|
||||
/* To optimize */
|
||||
/* elemRect can be NULL if not required */
|
||||
FCZone GetGamePtLoc(FCState *state, Point pt, Rect *elemRect) {
|
||||
FCZone res;
|
||||
Rect boundsRect;
|
||||
ushort i;
|
||||
|
||||
for(i = 0; i < FC_STORES + 4; ++i) {
|
||||
GetStoreRect(i, &boundsRect);
|
||||
if(PtInRect(pt, &boundsRect)) {
|
||||
res.zone = (i >= FC_STORES) ? FCZ_FOUNDATION : FCZ_STORAGE;
|
||||
res.num = (i >= FC_STORES) ? i - FC_STORES : i;
|
||||
if(elemRect) *elemRect = boundsRect;
|
||||
return res;
|
||||
}
|
||||
}
|
||||
|
||||
for(i = 0; i < FC_COLS; ++i) {
|
||||
GetColumnRect(i, state->cols[i]->qty, &boundsRect);
|
||||
if(PtInRect(pt, &boundsRect)) {
|
||||
if(elemRect) *elemRect = boundsRect;
|
||||
GetLastStackedRect(i, state->cols[i]->qty, &boundsRect);
|
||||
if(PtInRect(pt, &boundsRect)) {
|
||||
res.zone = FCZ_LASTCARD;
|
||||
if(elemRect) *elemRect = boundsRect;
|
||||
} else {
|
||||
res.zone = FCZ_COLUMN;
|
||||
}
|
||||
|
||||
res.num = i;
|
||||
return res;
|
||||
}
|
||||
}
|
||||
|
||||
res.zone = FCZ_NONE;
|
||||
if(elemRect)
|
||||
SetRect(elemRect, 0, 0, 0, 0);
|
||||
return res;
|
||||
}
|
||||
|
||||
void GetFCZoneRect(FCState *state, FCZone elem, Rect *elemRect) {
|
||||
switch(elem.zone) {
|
||||
case FCZ_LASTCARD: {
|
||||
FCColumn *theColumn = state->cols[elem.num];
|
||||
GetLastStackedRect(elem.num, theColumn->qty, elemRect);
|
||||
} break;
|
||||
case FCZ_COLUMN: {
|
||||
FCColumn *theColumn = state->cols[elem.num];
|
||||
GetColumnRect(elem.num, theColumn->qty, elemRect);
|
||||
} break;
|
||||
|
||||
case FCZ_STORAGE:
|
||||
GetStoreRect(elem.num, elemRect);
|
||||
break;
|
||||
|
||||
case FCZ_FOUNDATION:
|
||||
GetStoreRect(elem.num + FC_STORES, elemRect);
|
||||
break;
|
||||
|
||||
default:
|
||||
SetRect(elemRect, 0, 0, 0, 0);
|
||||
}
|
||||
}
|
||||
|
||||
/* If upper <= lower then result undefined. */
|
||||
short RndRange(short lower, short upper) {
|
||||
asm {
|
||||
CLR.W -(sp) ; d0 = Random()
|
||||
_Random
|
||||
CLR.L d0
|
||||
MOVE.W (sp)+, d0
|
||||
MOVE.W upper, d1 ; d1 = upper - lower
|
||||
SUB.W lower, d1
|
||||
DIVU.W d1, d0 ; d0 = d0 % d1
|
||||
SWAP d0
|
||||
ADD.W lower, d0
|
||||
}
|
||||
}
|
26
gameintf.h
Normal file
26
gameintf.h
Normal file
@ -0,0 +1,26 @@
|
||||
#ifndef GAMEINTF_H
|
||||
#define GAMEINTF_H
|
||||
|
||||
#include "freecell.h"
|
||||
|
||||
#define GAME_RANDOMSEED 0
|
||||
|
||||
typedef enum {
|
||||
FCCE_OK=0,
|
||||
FCCE_BADHIT,
|
||||
FCCE_NODRAG,
|
||||
FCCE_OOBDRAG,
|
||||
FCCE_BADDRAG,
|
||||
FCCE_BADMOVE
|
||||
} FCClickErr;
|
||||
|
||||
/* Mouse and Gameplay Handlers */
|
||||
FCClickErr HandleGameClick(FCState *state, Point hitPt);
|
||||
void GameDrawMove(FCState *state, FCZone from, FCZone to);
|
||||
|
||||
void GameNewGame(FCState *state, ushort seed);
|
||||
|
||||
FCZone GetGamePtLoc(FCState *state, Point pt, Rect *elemRect);
|
||||
void GetFCZoneRect(FCState *state, FCZone elem, Rect *elemRect);
|
||||
|
||||
#endif /* GAMEINTF_H */
|
131
gamemenu.c
Normal file
131
gamemenu.c
Normal file
@ -0,0 +1,131 @@
|
||||
#include "gamemenu.h"
|
||||
#include "common.h"
|
||||
#include "gamewind.h"
|
||||
#include "gamestate.h"
|
||||
#include "gameintf.h"
|
||||
|
||||
#define MBAR_ID 128
|
||||
|
||||
enum {
|
||||
appleID=128,
|
||||
fileID,
|
||||
editID
|
||||
};
|
||||
|
||||
enum {
|
||||
apple_aboutID=1
|
||||
};
|
||||
|
||||
enum {
|
||||
file_newID=1,
|
||||
file_openID,
|
||||
file_restartID,
|
||||
file_quitID=5
|
||||
};
|
||||
|
||||
enum {
|
||||
edit_undoID=1,
|
||||
edit_cutID=3,
|
||||
edit_copyID,
|
||||
edit_pasteID,
|
||||
edit_clearID
|
||||
};
|
||||
|
||||
static void DoAppleMenu(short item);
|
||||
static void DoFileMenu(short item);
|
||||
static void DoEditMenu(short item);
|
||||
|
||||
void MenuCreate(void) {
|
||||
Handle mHandle;
|
||||
mHandle = GetNewMBar(MBAR_ID);
|
||||
SetMenuBar(mHandle);
|
||||
DrawMenuBar();
|
||||
|
||||
mHandle = (Handle) GetMHandle(appleID);
|
||||
AddResMenu((MenuHandle) mHandle, 'DRVR');
|
||||
}
|
||||
|
||||
void MenuEvent(long menuitem) {
|
||||
short menuID = HIWORD(menuitem);
|
||||
short itemID = LOWORD(menuitem);
|
||||
|
||||
switch(menuID) {
|
||||
case appleID: DoAppleMenu(itemID); break;
|
||||
case fileID: DoFileMenu(itemID); break;
|
||||
case editID: DoEditMenu(itemID); break;
|
||||
}
|
||||
}
|
||||
|
||||
void MenuEditState(Boolean active) {
|
||||
MenuHandle theMenu = GetMHandle(editID);
|
||||
if(active) {
|
||||
EnableItem(theMenu, edit_cutID);
|
||||
EnableItem(theMenu, edit_copyID);
|
||||
EnableItem(theMenu, edit_pasteID);
|
||||
EnableItem(theMenu, edit_clearID);
|
||||
} else {
|
||||
DisableItem(theMenu, edit_cutID);
|
||||
DisableItem(theMenu, edit_copyID);
|
||||
DisableItem(theMenu, edit_pasteID);
|
||||
DisableItem(theMenu, edit_clearID);
|
||||
}
|
||||
}
|
||||
|
||||
void MenuUndoState(Boolean active) {
|
||||
MenuHandle theMenu = GetMHandle(editID);
|
||||
if(active) {
|
||||
EnableItem(theMenu, edit_undoID);
|
||||
} else {
|
||||
DisableItem(theMenu, edit_undoID);
|
||||
}
|
||||
}
|
||||
|
||||
void DoAppleMenu(short item) {
|
||||
GrafPtr *oldPort;
|
||||
MenuHandle theMenu = GetMHandle(appleID);
|
||||
|
||||
if(item > apple_aboutID) {
|
||||
StringPtr name = (StringPtr) NewPtr(sizeof(Str255));
|
||||
GetPort(&oldPort);
|
||||
GetItem(theMenu, item, name);
|
||||
OpenDeskAcc(name);
|
||||
SetPort(oldPort);
|
||||
DisposPtr(name);
|
||||
} else {
|
||||
DlogAbout();
|
||||
}
|
||||
}
|
||||
|
||||
void DoFileMenu(short item) {
|
||||
MenuHandle theMenu = GetMHandle(fileID);
|
||||
switch(item) {
|
||||
case file_newID:
|
||||
GameNewGame(&gstate.fcGame, GAME_RANDOMSEED);
|
||||
break;
|
||||
|
||||
case file_openID: {
|
||||
ushort openSeed = DlogOpenGame();
|
||||
if(openSeed != -1) {
|
||||
GameNewGame(&gstate.fcGame, openSeed);
|
||||
}
|
||||
} break;
|
||||
|
||||
case file_restartID:
|
||||
GameNewGame(&gstate.fcGame, GAME_RANDOMSEED);
|
||||
break;
|
||||
|
||||
case file_quitID:
|
||||
gstate.running = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void DoEditMenu(short item) {
|
||||
switch(item) {
|
||||
case edit_undoID:
|
||||
FreecellUndoMove(&gstate.fcGame);
|
||||
GameDrawMove(&gstate.fcGame, gstate.fcGame.lastMove[0],
|
||||
gstate.fcGame.lastMove[1]);
|
||||
break;
|
||||
}
|
||||
}
|
9
gamemenu.h
Normal file
9
gamemenu.h
Normal file
@ -0,0 +1,9 @@
|
||||
#ifndef GAMEMENU_H
|
||||
#define GAMEMENU_H
|
||||
|
||||
void MenuCreate(void);
|
||||
void MenuEvent(long menuitem);
|
||||
void MenuEditState(Boolean active);
|
||||
void MenuUndoState(Boolean active);
|
||||
|
||||
#endif /* GAMEMENU_H */
|
13
gamestate.h
Normal file
13
gamestate.h
Normal file
@ -0,0 +1,13 @@
|
||||
#ifndef GAMESTATE_H
|
||||
#define GAMESTATE_H
|
||||
|
||||
#include "freecell.h"
|
||||
|
||||
struct GlobalState {
|
||||
FCState fcGame;
|
||||
Boolean running;
|
||||
};
|
||||
|
||||
extern struct GlobalState gstate;
|
||||
|
||||
#endif /* GAMESTATE_H */
|
31
gametypes.h
Normal file
31
gametypes.h
Normal file
@ -0,0 +1,31 @@
|
||||
#ifndef GAMETYPES_H
|
||||
#define GAMETYPES_H
|
||||
|
||||
/* Card Type: ffssnnnn. High order can be used for flags. */
|
||||
#define CARD_FMASK 0xC0
|
||||
#define CARD_SMASK 0x30
|
||||
#define CARD_NMASK 0x0F
|
||||
|
||||
#define CARD_GETFLAGS(c) ((Byte) ((c & CARD_FMASK) >> 6))
|
||||
#define CARD_GETSUIT(c) ((Suit) ((c & CARD_SMASK) >> 4))
|
||||
#define CARD_GETNUM(c) ((short) (c & CARD_NMASK))
|
||||
#define CARD_SETFLAGS(c, f) ((c) = ((c & ~CARD_FMASK) | ((f) << 6)))
|
||||
#define CARD_SETSUIT(c, s) ((c) = ((c & ~CARD_SMASK) | ((s) << 4)))
|
||||
#define CARD_SETNUM(c, n) ((c) = ((c & ~CARD_NMASK) | (n)))
|
||||
#define TO_CARD(f, s, n) ((Card) (((f) << 6) | ((s) << 4) | (n)))
|
||||
|
||||
#define CARD_NULL ((Card) 0)
|
||||
#define CARD_EMPTY(c) (CARD_GETNUM(c) == 0)
|
||||
#define CARD_QTY 52
|
||||
|
||||
typedef Byte Card;
|
||||
|
||||
typedef enum Suit {
|
||||
C_CLUB=0,
|
||||
C_DIAMOND,
|
||||
C_HEART,
|
||||
C_SPADE
|
||||
} Suit;
|
||||
|
||||
|
||||
#endif /* GAMETYPES_H */
|
344
gamewind.c
Normal file
344
gamewind.c
Normal file
@ -0,0 +1,344 @@
|
||||
#include "gamewind.h"
|
||||
#include "gamewindlow.h"
|
||||
#include "common.h"
|
||||
#include "pstring.h"
|
||||
#include "strntol.h"
|
||||
#include <string.h>
|
||||
#include <ctype.h>
|
||||
|
||||
#define TITLE_PREF "\pFreeCell - #"
|
||||
#define DLOG_OPEN 128
|
||||
#define DLOG_OPEN_INPUT 3
|
||||
#define DLOG_ABOUT 129
|
||||
|
||||
typedef struct {
|
||||
short dlgMaxIndex;
|
||||
Handle itmHndl;
|
||||
Rect itmRect;
|
||||
short itmType;
|
||||
Byte itmData[];
|
||||
} DialogItemList;
|
||||
|
||||
static pascal Boolean DigitInputFilter(DialogPtr theDialog,
|
||||
EventRecord *theEvent, short *itemHit);
|
||||
static pascal Boolean AboutFilter(DialogPtr theDialog,
|
||||
EventRecord *theEvent, int *itemHit);
|
||||
|
||||
WindowPtr WindCreateTestEnv(Rect *bounds, StringPtr title) {
|
||||
WindowPtr theWind;
|
||||
Rect windRect;
|
||||
|
||||
title = title ? title : "\pTest Area";
|
||||
theWind = NewWindow(0L, bounds, title, true,
|
||||
noGrowDocProc, (WindowPtr) -1L, true, 0);
|
||||
|
||||
return theWind;
|
||||
}
|
||||
|
||||
WindowPtr WindCreateGame(FCState *state) {
|
||||
WindowPtr theWind;
|
||||
Rect boundsRect;
|
||||
Rect windRect;
|
||||
StringPtr titleString;
|
||||
unsigned char gameNumBuf[6];
|
||||
Pattern bkpat;
|
||||
/*
|
||||
titleString = (StringPtr) NewPtr(sizeof(Str255));
|
||||
if(!titleString) return NULL;
|
||||
memcpy(titleString, TITLE_PREF, sizeof(TITLE_PREF));
|
||||
NumToString(state->seedno, gameNumBuf);
|
||||
strcat_p(titleString, gameNumBuf);*/
|
||||
|
||||
SetRect(&windRect, 0, 0, WIND_XLENGTH, WIND_YLENGTH);
|
||||
boundsRect = screenBits.bounds;
|
||||
boundsRect.top += 20;
|
||||
CentreRect(&windRect, &boundsRect);
|
||||
theWind = NewWindow(0L, &windRect, "\p", true, noGrowDocProc,
|
||||
(WindowPtr) -1L, true, 0);
|
||||
/*DisposPtr(titleString);*/
|
||||
if(theWind) {
|
||||
SetPort(theWind);
|
||||
GetIndPattern(&bkpat, sysPatListID, 21);
|
||||
BackPat(bkpat);
|
||||
SetWRefCon(theWind, (long) state);
|
||||
}
|
||||
return theWind;
|
||||
}
|
||||
|
||||
void WindUpdateTitle(WindowPtr theWind) {
|
||||
StringPtr titleString;
|
||||
unsigned char gameNumBuf[6];
|
||||
FCState *state = (FCState *) GetWRefCon(theWind);
|
||||
|
||||
titleString = (StringPtr) NewPtr(sizeof(Str255));
|
||||
if(!titleString) return;
|
||||
memcpy(titleString, TITLE_PREF, sizeof(TITLE_PREF));
|
||||
NumToString(state->seedno, gameNumBuf);
|
||||
strcat_p(titleString, gameNumBuf);
|
||||
SetWTitle(theWind, titleString);
|
||||
DisposPtr(titleString);
|
||||
}
|
||||
|
||||
ushort DlogOpenGame(void) {
|
||||
DialogPtr theDialog;
|
||||
Rect dlogRect, boundsRect;
|
||||
short itemNo;
|
||||
Handle textboxHandle;
|
||||
volatile Size textboxHndlSize;
|
||||
long inputText;
|
||||
|
||||
theDialog = GetNewDialog(DLOG_OPEN, NULL, (WindowPtr) -1);
|
||||
if(!theDialog) return -1;
|
||||
dlogRect = theDialog->portRect;
|
||||
boundsRect = screenBits.bounds;
|
||||
boundsRect.top += 20;
|
||||
CentreRect(&dlogRect, &boundsRect);
|
||||
MoveWindow(theDialog, dlogRect.left, dlogRect.top, true);
|
||||
ShowWindow(theDialog);
|
||||
|
||||
do {
|
||||
do {
|
||||
ModalDialog(&DigitInputFilter, &itemNo);
|
||||
if(itemNo == cancel) {
|
||||
inputText = -1;
|
||||
goto err;
|
||||
}
|
||||
} while(itemNo != ok);
|
||||
|
||||
GetDItem(theDialog, DLOG_OPEN_INPUT, &itemNo,
|
||||
&textboxHandle, &boundsRect);
|
||||
textboxHndlSize = GetHandleSize(textboxHandle);
|
||||
/* Ensure whitespace before / after string */
|
||||
HLock(textboxHandle);
|
||||
inputText = strntol(*textboxHandle, textboxHndlSize, NULL, 10);
|
||||
HUnlock(textboxHandle);
|
||||
if(inputText < 1 || inputText > 32000) {
|
||||
SysBeep(1);
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
} while(true);
|
||||
|
||||
err:
|
||||
DisposDialog(theDialog);
|
||||
return (ushort) inputText;
|
||||
}
|
||||
|
||||
pascal Boolean DigitInputFilter(DialogPtr theDialog, EventRecord *theEvent,
|
||||
short *itemHit) {
|
||||
char theChar;
|
||||
|
||||
if(theEvent->what == keyDown || theEvent->what == autoKey) {
|
||||
theChar = theEvent->message & 0xFF;
|
||||
if(theChar == '\r' || theChar == '\x03') {
|
||||
*itemHit = ok;
|
||||
return true;
|
||||
} else if(!isdigit(theChar) && theChar != '\b') {
|
||||
SysBeep(1);
|
||||
theEvent->what = nullEvent;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void DlogAbout(void) {
|
||||
DialogPtr theDialog;
|
||||
Rect dlogRect;
|
||||
Rect boundsRect;
|
||||
short itemNo;
|
||||
|
||||
theDialog = GetNewDialog(DLOG_ABOUT, NULL, (WindowPtr) -1);
|
||||
if(!theDialog) return;
|
||||
dlogRect = theDialog->portRect;
|
||||
boundsRect = screenBits.bounds;
|
||||
boundsRect.top += 20;
|
||||
boundsRect.bottom /= 2;
|
||||
CentreRect(&dlogRect, &boundsRect);
|
||||
MoveWindow(theDialog, dlogRect.left, dlogRect.top, true);
|
||||
ShowWindow(theDialog);
|
||||
ModalDialog(&AboutFilter, &itemNo);
|
||||
DisposDialog(theDialog);
|
||||
}
|
||||
|
||||
pascal Boolean AboutFilter(DialogPtr theDialog, EventRecord *theEvent,
|
||||
int *itemHit) {
|
||||
int windowCode;
|
||||
WindowPtr theWindow;
|
||||
|
||||
if(theEvent->what != mouseDown)
|
||||
return false;
|
||||
windowCode = FindWindow(theEvent->where, &theWindow);
|
||||
if(windowCode != inContent || theWindow != theDialog)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void DrawGameInit(void) {
|
||||
Point drawPoint;
|
||||
ushort i;
|
||||
|
||||
drawPoint.v = CARD_BD_Y;
|
||||
drawPoint.h = CARD_BD_X;
|
||||
for(i = 0; i < FC_STORES; ++i) {
|
||||
DrawEmptyFrame(drawPoint);
|
||||
drawPoint.h += CARD_ST_X + CARD_XLENGTH;
|
||||
}
|
||||
|
||||
drawPoint.h = WIND_XLENGTH - CARD_BD_X - CARD_XLENGTH;
|
||||
for(i = 0; i < 4; ++i) {
|
||||
DrawEmptyFrame(drawPoint);
|
||||
drawPoint.h -= CARD_ST_X + CARD_XLENGTH;
|
||||
}
|
||||
}
|
||||
|
||||
void ForceRedraw(void) {
|
||||
GrafPtr thePort;
|
||||
GetPort(&thePort);
|
||||
InvalRect(&thePort->portRect);
|
||||
}
|
||||
|
||||
void DrawAll(FCState *state) {
|
||||
DrawClear();
|
||||
DrawStorage(state->store);
|
||||
DrawFoundation(state->foundation);
|
||||
DrawPlayfield(state->cols);
|
||||
}
|
||||
|
||||
void DrawClear(void) {
|
||||
GrafPtr thePort;
|
||||
Rect *clearRect;
|
||||
GetPort(&thePort);
|
||||
clearRect = &thePort->portRect;
|
||||
EraseRect(clearRect);
|
||||
}
|
||||
|
||||
void DrawPlayfield(FCColumn **cols) {
|
||||
Point drawPoint;
|
||||
ushort i;
|
||||
|
||||
drawPoint.v = CARD_BD_Y + CARD_YLENGTH + CARD_ST_SEP;
|
||||
drawPoint.h = CARD_BD_X;
|
||||
for(i = 0; i < FC_COLS; ++i) {
|
||||
DrawStack(cols[i]->cards, drawPoint, cols[i]->qty);
|
||||
drawPoint.h += CARD_XLENGTH + CARD_PF_X;
|
||||
}
|
||||
}
|
||||
|
||||
void DrawStorage(Card *cards) {
|
||||
Point drawPoint;
|
||||
ushort i;
|
||||
|
||||
drawPoint.v = CARD_BD_Y;
|
||||
drawPoint.h = CARD_BD_X;
|
||||
for(i = 0; i < FC_STORES; ++i) {
|
||||
if(!CARD_EMPTY(cards[i])) {
|
||||
DrawCard(cards[i], drawPoint);
|
||||
} else {
|
||||
DrawEmptyFrame(drawPoint);
|
||||
}
|
||||
drawPoint.h += CARD_ST_X + CARD_XLENGTH;
|
||||
}
|
||||
}
|
||||
|
||||
void DrawFoundation(Card *cards) {
|
||||
Point drawPoint;
|
||||
ushort i;
|
||||
|
||||
drawPoint.v = CARD_BD_Y;
|
||||
drawPoint.h = WIND_XLENGTH - CARD_BD_X - CARD_XLENGTH;
|
||||
for(i = 0; i < 4; ++i) {
|
||||
if(!CARD_EMPTY(cards[3-i])) {
|
||||
DrawCard(cards[3-i], drawPoint);
|
||||
} else {
|
||||
DrawEmptyFrame(drawPoint);
|
||||
}
|
||||
drawPoint.h -= CARD_ST_X + CARD_XLENGTH;
|
||||
}
|
||||
}
|
||||
|
||||
void DrawStorageCard(Card card, ushort pos) {
|
||||
Point drawPoint;
|
||||
|
||||
drawPoint = GetStorePt(pos);
|
||||
if(!CARD_EMPTY(card)) {
|
||||
DrawCard(card, drawPoint);
|
||||
} else {
|
||||
DrawEmptyFrame(drawPoint);
|
||||
}
|
||||
}
|
||||
|
||||
void DrawFoundationCard(Card card, ushort pos) {
|
||||
Point drawPoint;
|
||||
|
||||
drawPoint = GetStorePt(FC_STORES + pos);
|
||||
if(!CARD_EMPTY(card)) {
|
||||
DrawCard(card, drawPoint);
|
||||
} else {
|
||||
DrawEmptyFrame(drawPoint);
|
||||
}
|
||||
}
|
||||
|
||||
void DrawLastColumnCard(FCColumn *col, ushort colno) {
|
||||
Point drawPoint;
|
||||
|
||||
drawPoint = GetColumnPt(colno);
|
||||
DrawStackedCard(col->cards[col->qty-1], drawPoint, col->qty-1);
|
||||
}
|
||||
|
||||
void DrawRemovedColCard(FCColumn *col, ushort colno) {
|
||||
Point drawPoint;
|
||||
|
||||
drawPoint = GetColumnPt(colno);
|
||||
DrawStackedCard(CARD_NULL, drawPoint, col->qty);
|
||||
|
||||
if(col->qty != 0) {
|
||||
DrawStackedCard(col->cards[col->qty-1], drawPoint, col->qty-1);
|
||||
} else {
|
||||
DrawEmptyFrame(drawPoint);
|
||||
}
|
||||
}
|
||||
|
||||
void CentreRect(Rect *toCentre, const Rect *bounds) {
|
||||
short bdx, bdy, cdx, cdy;
|
||||
|
||||
bdx = bounds->right - bounds->left;
|
||||
bdy = bounds->bottom - bounds->top;
|
||||
cdx = toCentre->right - toCentre->left;
|
||||
cdy = toCentre->bottom - toCentre->top;
|
||||
if(cdx > bdx || cdy > bdy) return;
|
||||
|
||||
toCentre->top = bounds->top + bdy/2 - cdy/2;
|
||||
toCentre->left = bounds->left + bdx/2 - cdx/2;
|
||||
toCentre->bottom = toCentre->top + cdy;
|
||||
toCentre->right = toCentre->left + cdx;
|
||||
}
|
||||
|
||||
/*
|
||||
switch(elem.zone) {
|
||||
case FCZ_LASTCARD: {
|
||||
FCColumn *theColumn = state->cols[elem.num];
|
||||
if(CARD_EMPTY(theColumn->cards[theColumn->qty-1]))
|
||||
goto err;
|
||||
|
||||
GetLastStackedRect(elem.num, theColumn->qty, &drawRect);
|
||||
} break;
|
||||
|
||||
case FCZ_STORAGE:
|
||||
if(CARD_EMPTY(state->store[elem.num]))
|
||||
goto err;
|
||||
|
||||
GetStoreRect(elem.num, &drawRect);
|
||||
break;
|
||||
|
||||
case FCZ_FOUNDATION:
|
||||
if(CARD_EMPTY(state->foundation[elem.num]))
|
||||
goto err;
|
||||
|
||||
GetStoreRect(elem.num + FC_STORES, &drawRect);
|
||||
break;
|
||||
|
||||
default:
|
||||
goto err;
|
||||
}
|
||||
*/
|
33
gamewind.h
Normal file
33
gamewind.h
Normal file
@ -0,0 +1,33 @@
|
||||
#ifndef GAMEWIND_H
|
||||
#define GAMEWIND_H
|
||||
|
||||
#include "common.h"
|
||||
#include "gametypes.h"
|
||||
#include "freecell.h"
|
||||
|
||||
/* Window / Dialog creation */
|
||||
WindowPtr WindCreateTestEnv(Rect *bounds, StringPtr title);
|
||||
WindowPtr WindCreateGame(FCState *state);
|
||||
void WindUpdateTitle(WindowPtr theWind);
|
||||
|
||||
ushort DlogOpenGame(void);
|
||||
void DlogAbout(void);
|
||||
|
||||
/* High-Level Drawing */
|
||||
void DrawGameInit(void);
|
||||
void ForceRedraw(void);
|
||||
void DrawAll(FCState *state);
|
||||
void DrawClear(void);
|
||||
void DrawPlayfield(FCColumn **cols);
|
||||
void DrawStorage(Card *cards);
|
||||
void DrawFoundation(Card *cards);
|
||||
|
||||
void DrawStorageCard(Card card, ushort pos);
|
||||
void DrawFoundationCard(Card card, ushort pos);
|
||||
void DrawLastColumnCard(FCColumn *col, ushort colno);
|
||||
void DrawRemovedColCard(FCColumn *col, ushort colno);
|
||||
|
||||
/* Misc */
|
||||
void CentreRect(Rect *toCentre, const Rect *bounds);
|
||||
|
||||
#endif /* GAMEWIND_H */
|
156
gamewindlow.c
Normal file
156
gamewindlow.c
Normal file
@ -0,0 +1,156 @@
|
||||
#include "gamewindlow.h"
|
||||
|
||||
void DrawCard(Card card, Point loc) {
|
||||
Rect cardRect;
|
||||
static short lineAscent = -1;
|
||||
static short lineSpacing = -1;
|
||||
|
||||
SetRect(&cardRect, 0, 0, CARD_XLENGTH, CARD_YLENGTH);
|
||||
OffsetRect(&cardRect, loc.h, loc.v);
|
||||
EraseRoundRect(&cardRect, CARD_XRATIO, CARD_YRATIO);
|
||||
if(CARD_EMPTY(card)) return;
|
||||
|
||||
TextFont(CARDFONT_ID);
|
||||
TextSize(9);
|
||||
if(lineSpacing == -1) {
|
||||
FontInfo cardFontInfo;
|
||||
GetFontInfo(&cardFontInfo);
|
||||
lineAscent = cardFontInfo.ascent;
|
||||
lineSpacing = cardFontInfo.ascent +
|
||||
cardFontInfo.descent +
|
||||
cardFontInfo.leading;
|
||||
}
|
||||
|
||||
FillRoundRect(&cardRect, CARD_XRATIO, CARD_YRATIO, white);
|
||||
FrameRoundRect(&cardRect, CARD_XRATIO, CARD_YRATIO);
|
||||
InsetRect(&cardRect, (CARD_XLENGTH * 9) / 40,
|
||||
(CARD_YLENGTH * 9) / 40);
|
||||
FillRoundRect(&cardRect, CARD_XRATIO, CARD_YRATIO, gray);
|
||||
|
||||
loc.h += (CARD_XLENGTH * 2) / 40;
|
||||
loc.v += (CARD_YLENGTH * 2) / 40 + lineAscent;
|
||||
MoveTo(loc.h, loc.v);
|
||||
DrawChar(ITOC(CARD_GETNUM(card)));
|
||||
/*MoveTo(loc.h, loc.v + lineSpacing);*/
|
||||
DrawChar(SUIT2CHAR(CARD_GETSUIT(card)));
|
||||
}
|
||||
|
||||
void DrawStack(Card cards[], Point loc, ushort qty) {
|
||||
ushort i;
|
||||
|
||||
if(qty == 0) {
|
||||
DrawEmptyFrame(loc);
|
||||
return;
|
||||
}
|
||||
|
||||
for(i = 0; i < qty; ++i) {
|
||||
DrawCard(cards[i], loc);
|
||||
loc.v += CARD_PF_Y;
|
||||
}
|
||||
}
|
||||
|
||||
void DrawStackedCard(Card card, Point loc, ushort pos) {
|
||||
loc.v += CARD_PF_Y * pos;
|
||||
DrawCard(card, loc);
|
||||
}
|
||||
|
||||
void DrawEmptyFrame(Point loc) {
|
||||
PenState ps;
|
||||
Rect frameRect = {0, 0, CARD_YLENGTH, CARD_XLENGTH};
|
||||
|
||||
GetPenState(&ps);
|
||||
PenPat(black);
|
||||
PenSize(1,1);
|
||||
OffsetRect(&frameRect, loc.h, loc.v);
|
||||
FillRoundRect(&frameRect, CARD_XRATIO, CARD_YRATIO, white);
|
||||
FrameRoundRect(&frameRect, CARD_XRATIO, CARD_YRATIO);
|
||||
SetPenState(&ps);
|
||||
}
|
||||
|
||||
/* Returns (INVAL_LOC, INVAL_LOC) on error */
|
||||
Point GetStorePt(ushort store) {
|
||||
Point res;
|
||||
if(store >= FC_STORES + 4) {
|
||||
L2PT(res, INVAL_PTL);
|
||||
return res;
|
||||
}
|
||||
|
||||
if(store >= FC_STORES) {
|
||||
/* Set count from 4-7 to 1-4 backwards */
|
||||
/*store = (4 - 1) - (store - FC_STORES) + 1;*/
|
||||
store = (FC_STORES + 4) - store;
|
||||
res.h = WIND_XLENGTH - CARD_BD_X + CARD_ST_X /* Correction Term */
|
||||
- store * (CARD_XLENGTH + CARD_ST_X);
|
||||
} else {
|
||||
res.h = CARD_BD_X + store * (CARD_XLENGTH + CARD_ST_X);
|
||||
}
|
||||
|
||||
res.v = CARD_BD_Y;
|
||||
return res;
|
||||
}
|
||||
|
||||
Point GetColumnPt(ushort col) {
|
||||
Point res;
|
||||
if(col >= FC_COLS) {
|
||||
L2PT(res, INVAL_PTL);
|
||||
return res;
|
||||
}
|
||||
|
||||
res.v = CARD_BD_Y + CARD_YLENGTH + CARD_ST_SEP;
|
||||
res.h = CARD_BD_X + col * (CARD_XLENGTH + CARD_PF_X);
|
||||
return res;
|
||||
}
|
||||
|
||||
void GetStoreRect(ushort store, Rect *res) {
|
||||
topLeft(*res) = GetStorePt(store);
|
||||
if(BAD_PT(topLeft(*res))) {
|
||||
SetRect(res, 0, 0, 0, 0);
|
||||
return;
|
||||
}
|
||||
|
||||
res->bottom = res->top + CARD_YLENGTH;
|
||||
res->right = res->left + CARD_XLENGTH;
|
||||
}
|
||||
|
||||
void GetColumnRect(ushort col, ushort csize, Rect *res) {
|
||||
topLeft(*res) = GetColumnPt(col);
|
||||
if(BAD_PT(topLeft(*res))) {
|
||||
SetRect(res, 0, 0, 0, 0);
|
||||
return;
|
||||
}
|
||||
|
||||
if(csize == 0) csize = 1;
|
||||
res->bottom = res->top + (csize - 1) * CARD_PF_Y + CARD_YLENGTH;
|
||||
res->right = res->left + CARD_XLENGTH;
|
||||
}
|
||||
|
||||
/* 0 treated as 1 so that cards can be played on empty columns */
|
||||
void GetStackedCardRect(ushort col, ushort csize, ushort pos, Rect *res) {
|
||||
if(csize == 0) csize = 1;
|
||||
|
||||
if(pos >= csize)
|
||||
goto err;
|
||||
|
||||
topLeft(*res) = GetColumnPt(col);
|
||||
if(BAD_PT(topLeft(*res)))
|
||||
goto err;
|
||||
|
||||
res->top += pos * CARD_PF_Y;
|
||||
res->right = res->left + CARD_XLENGTH;
|
||||
|
||||
/* If card is last in column, use full area. Otherwise whats seen. */
|
||||
if(pos == csize - 1) {
|
||||
res->bottom = res->top + CARD_YLENGTH;
|
||||
} else {
|
||||
res->bottom = res->top + CARD_PF_Y;
|
||||
}
|
||||
|
||||
return;
|
||||
err:
|
||||
SetRect(res, 0, 0, 0, 0);
|
||||
}
|
||||
|
||||
void GetLastStackedRect(ushort col, ushort csize, Rect *res) {
|
||||
if(csize < 1) csize = 1;
|
||||
GetStackedCardRect(col, csize, csize-1, res);
|
||||
}
|
77
gamewindlow.h
Normal file
77
gamewindlow.h
Normal file
@ -0,0 +1,77 @@
|
||||
#ifndef GAMEWINDLOW_H
|
||||
#define GAMEWINDLOW_H
|
||||
|
||||
#include "common.h"
|
||||
#include "freecell.h"
|
||||
|
||||
/* Game Dimensions */
|
||||
|
||||
#define CARDFONT_ID 25
|
||||
#define CARDFONT_PT 12
|
||||
|
||||
#define CARD_XRATIO 5
|
||||
#define CARD_YRATIO 7
|
||||
#define CARD_SCALE 9
|
||||
|
||||
#define CARD_XLENGTH (CARD_XRATIO * CARD_SCALE)
|
||||
#define CARD_YLENGTH (CARD_YRATIO * CARD_SCALE)
|
||||
|
||||
/*
|
||||
^
|
||||
|BD_Y
|
||||
|
|
||||
BD_Xv ST_X ST_GAP
|
||||
<-->+--+<---->+--+ ... <------>
|
||||
| | | |
|
||||
| | | |
|
||||
+--+ +--+
|
||||
^
|
||||
ST|
|
||||
SEP| XLEN
|
||||
v PF_X <-->
|
||||
+--+<----->+--+^
|
||||
PF_Y| | | ||YLEN
|
||||
+--+ | ||
|
||||
| | +--+v
|
||||
...
|
||||
*/
|
||||
/* Set to a ratio ? */
|
||||
#define CARD_PF_X 10
|
||||
#define CARD_PF_Y 12
|
||||
|
||||
#define CARD_ST_X ((WIND_XLENGTH - (2*CARD_BD_X) - (8*CARD_XLENGTH) \
|
||||
- CARD_ST_GAP) / 6)
|
||||
#define CARD_ST_GAP 40
|
||||
#define CARD_ST_SEP (2*CARD_BD_Y)
|
||||
|
||||
#define CARD_BD_X 10
|
||||
#define CARD_BD_Y 10
|
||||
|
||||
#define WIND_XLENGTH ((2*CARD_BD_X)+(8*CARD_XLENGTH)+(7*CARD_PF_X))
|
||||
#define WIND_YLENGTH \
|
||||
(((long) (screenBits.bounds.bottom-20) * WIND_XLENGTH) / screenBits.bounds.right)
|
||||
|
||||
#define SUIT2CHAR(s) ('0' - 1 - (s))
|
||||
|
||||
#define INVAL_LOC (0x8000)
|
||||
#define INVAL_PTL (0x80008000L)
|
||||
#define BAD_PT(p) (((p).h == INVAL_LOC) && ((p).v == INVAL_LOC))
|
||||
/*#define BAD_PT(p) BAD_PTL(PT2L(p))*/
|
||||
#define BAD_PTL(p) (p == INVAL_PTL)
|
||||
|
||||
/* Low-Level Drawing */
|
||||
void DrawCard(Card card, Point loc);
|
||||
void DrawStackedCard(Card card, Point loc, ushort pos);
|
||||
void DrawStack(Card cards[], Point loc, ushort qty);
|
||||
void DrawEmptyFrame(Point loc);
|
||||
|
||||
/* Low-Level Point Access */
|
||||
Point GetStorePt(ushort store);
|
||||
Point GetColumnPt(ushort col);
|
||||
void GetStoreRect(ushort store, Rect *res);
|
||||
void GetColumnRect(ushort col, ushort csize, Rect *res);
|
||||
void GetStackedCardRect(ushort col, ushort csize, ushort pos, Rect *res);
|
||||
void GetLastStackedRect(ushort col, ushort csize, Rect *res);
|
||||
|
||||
|
||||
#endif /* GAMEWINDLOW_H */
|
144
main.c
Normal file
144
main.c
Normal file
@ -0,0 +1,144 @@
|
||||
/* TODO
|
||||
- Bug where starting a new game causes some cards to not be drawn properly
|
||||
and may leave residual cards in foundation / storage
|
||||
- Optimize DlogOpenGame string to int
|
||||
*/
|
||||
|
||||
#include "gamewind.h"
|
||||
#include "gameintf.h"
|
||||
#include "gamemenu.h"
|
||||
#include "gamestate.h"
|
||||
#include "freecell.h"
|
||||
|
||||
struct GlobalState gstate = {0};
|
||||
static Rect dragRect;
|
||||
|
||||
static void InitMacintosh(void);
|
||||
static void InitGameState(void);
|
||||
static void HandleEvent(short eventMask);
|
||||
static void HandleMouseDown(EventRecord *theEvent);
|
||||
static void HandleContentClick(Point mousePt);
|
||||
short RndRange(short lower, short upper);
|
||||
|
||||
void InitMacintosh(void) {
|
||||
MaxApplZone();
|
||||
|
||||
InitGraf(&thePort);
|
||||
InitFonts();
|
||||
InitWindows();
|
||||
InitMenus();
|
||||
TEInit();
|
||||
InitDialogs(0L);
|
||||
randSeed = RndSeed;
|
||||
|
||||
FlushEvents(everyEvent, 0);
|
||||
InitCursor();
|
||||
}
|
||||
|
||||
void InitGameState(void) {
|
||||
FreecellInit(&gstate.fcGame);
|
||||
gstate.running = true;
|
||||
}
|
||||
|
||||
void HandleEvent(short eventMask) {
|
||||
int res;
|
||||
EventRecord theEvent;
|
||||
|
||||
HiliteMenu(0);
|
||||
SystemTask(); /* Handle desk accessories */
|
||||
|
||||
if (!GetNextEvent(eventMask, &theEvent)) return;
|
||||
switch (theEvent.what) {
|
||||
case mouseDown:
|
||||
HandleMouseDown(&theEvent);
|
||||
break;
|
||||
|
||||
case keyDown:
|
||||
if(theEvent.modifiers & cmdKey) {
|
||||
MenuEvent(MenuKey(theEvent.message & 0xFF));
|
||||
}
|
||||
|
||||
/* FALLTHROUGH */
|
||||
case autoKey:
|
||||
break;
|
||||
|
||||
case activateEvt:
|
||||
if(theEvent.modifiers & activeFlag) {
|
||||
MenuEditState(false);
|
||||
} else {
|
||||
MenuEditState(true);
|
||||
}
|
||||
break;
|
||||
|
||||
case updateEvt:
|
||||
BeginUpdate((WindowPtr) theEvent.message);
|
||||
/*EraseRect(&((WindowPtr) theEvent.message)->portRect);*/
|
||||
DrawAll(&gstate.fcGame);
|
||||
EndUpdate((WindowPtr) theEvent.message);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void HandleMouseDown(EventRecord *theEvent) {
|
||||
WindowPtr theWindow;
|
||||
short windowCode = FindWindow(theEvent->where, &theWindow);
|
||||
|
||||
switch(windowCode) {
|
||||
case inSysWindow:
|
||||
SystemClick (theEvent, theWindow);
|
||||
break;
|
||||
|
||||
case inDrag:
|
||||
DragWindow(theWindow, theEvent->where, &dragRect);
|
||||
break;
|
||||
|
||||
case inMenuBar:
|
||||
MenuEvent(MenuSelect(theEvent->where));
|
||||
break;
|
||||
|
||||
case inContent:
|
||||
if(theWindow != FrontWindow()) {
|
||||
SelectWindow(theWindow);
|
||||
} else {
|
||||
GlobalToLocal(&theEvent->where);
|
||||
switch(HandleGameClick(&gstate.fcGame, theEvent->where)) {
|
||||
/*case FCCE_BADDRAG:*/
|
||||
case FCCE_BADMOVE:
|
||||
SysBeep(1);
|
||||
break;
|
||||
|
||||
default:
|
||||
;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case inGoAway:
|
||||
if(TrackGoAway(theWindow, theEvent->where)) {
|
||||
HideWindow(theWindow);
|
||||
gstate.running = false;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
int main(void) {
|
||||
Point cardPoint = {10, 10};
|
||||
WindowPtr testWind;
|
||||
|
||||
InitMacintosh();
|
||||
InitGameState();
|
||||
MenuCreate();
|
||||
|
||||
SetRect(&dragRect, 4, 24, screenBits.bounds.right-4,
|
||||
screenBits.bounds.bottom-4);
|
||||
|
||||
testWind = WindCreateGame(&gstate.fcGame);
|
||||
GameNewGame(&gstate.fcGame, GAME_RANDOMSEED);
|
||||
|
||||
while(gstate.running) {
|
||||
HandleEvent(everyEvent);
|
||||
}
|
||||
|
||||
FreecellDisposeDeal(gstate.fcGame.cols);
|
||||
}
|
22
pstring.c
Normal file
22
pstring.c
Normal file
@ -0,0 +1,22 @@
|
||||
/* Parameter List and Prototypes disabled so that stack frame not created */
|
||||
|
||||
#pragma options(!require_protos)
|
||||
StringPtr strcat_p(/*StringPtr s1, const StringPtr s2*/) {
|
||||
asm {
|
||||
MOVEA.L 4(sp), a0 ; A0 = s1
|
||||
MOVEA.L 8(sp), a1 ; A1 = s2
|
||||
CLR.L d0
|
||||
CLR.L d1
|
||||
MOVE.B (a0), d0 ; D0 = n(s1)
|
||||
MOVE.B (a1)+, d1 ; D1 = n(s2)
|
||||
ADD.B d1, (a0)+ ; Update n(s1)
|
||||
ADDA.L d0, a0 ; Offset s1
|
||||
TST.B d1
|
||||
BRA.S @2
|
||||
@1 MOVE.B (a1)+, (a0)+
|
||||
SUBQ.B #1, d1
|
||||
@2 BNE.S @1
|
||||
|
||||
MOVE.L 4(sp), d0
|
||||
}
|
||||
}
|
6
pstring.h
Normal file
6
pstring.h
Normal file
@ -0,0 +1,6 @@
|
||||
#ifndef PSTRING_H
|
||||
#define PSTRING_H
|
||||
|
||||
StringPtr strcat_p(StringPtr a, const StringPtr b);
|
||||
|
||||
#endif /* PSTRING_H */
|
90
strntol.c
Normal file
90
strntol.c
Normal file
@ -0,0 +1,90 @@
|
||||
/*
|
||||
* Adapted from GCC strtol.c
|
||||
*/
|
||||
|
||||
#include "strntol.h"
|
||||
#include <limits.h>
|
||||
#include <ctype.h>
|
||||
|
||||
long strntol(const char *nptr, size_t sz, char **endptr, int base) {
|
||||
const char *s = nptr;
|
||||
unsigned long acc = 0;
|
||||
int c;
|
||||
unsigned long cutoff;
|
||||
int neg = 0, any = 0, cutlim;
|
||||
|
||||
++sz; /* Bounds fix */
|
||||
|
||||
/* Skip whitespace, pick up +/- sign, detect prefix. */
|
||||
do {
|
||||
c = *s++;
|
||||
--sz;
|
||||
} while(isspace(c) && sz > 0);
|
||||
if(sz == 0) {
|
||||
goto err;
|
||||
}
|
||||
|
||||
if(c == '-') {
|
||||
neg = 1;
|
||||
c = *s++;
|
||||
sz--;
|
||||
} else if(c == '+') {
|
||||
c = *s++;
|
||||
}
|
||||
|
||||
|
||||
if((base == 0 || base == 16) &&
|
||||
sz >= 2 && c == '0' && (*s == 'x' || *s == 'X')) {
|
||||
c = s[1];
|
||||
s += 2;
|
||||
sz -= 2;
|
||||
base = 16;
|
||||
}
|
||||
|
||||
if(base == 0) {
|
||||
base = c == '0' ? 8 : 10;
|
||||
}
|
||||
|
||||
/* Compute cutoff between legal / illegal numbers */
|
||||
cutoff = neg ? -(unsigned long) LONG_MIN : LONG_MAX;
|
||||
cutlim = cutoff % (unsigned long) base;
|
||||
cutoff /= (unsigned long) base;
|
||||
for(;; --sz, c = *s++) {
|
||||
if(sz == 0) {
|
||||
break;
|
||||
}
|
||||
|
||||
if(isdigit(c)) {
|
||||
c -= '0';
|
||||
} else if(isalpha(c)) {
|
||||
c -= isupper(c) ? 'A' - 10 : 'a' - 10;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
|
||||
if(c >= base) {
|
||||
break;
|
||||
}
|
||||
|
||||
if(any < 0 || acc > cutoff || (acc == cutoff && c > cutlim)) {
|
||||
any = -1;
|
||||
} else {
|
||||
any = 1;
|
||||
acc *= base;
|
||||
acc += c;
|
||||
}
|
||||
}
|
||||
|
||||
if(any < 0) {
|
||||
acc = neg ? LONG_MIN : LONG_MAX;
|
||||
} else if(neg) {
|
||||
acc = -acc;
|
||||
}
|
||||
|
||||
err:
|
||||
if(endptr != 0) {
|
||||
*endptr = (char *) (any ? s - 1 : nptr);
|
||||
}
|
||||
|
||||
return acc;
|
||||
}
|
Loading…
Reference in New Issue
Block a user