Initial commit

This commit is contained in:
Steven McLeod 2019-05-03 21:29:26 -07:00
parent 8af048cee7
commit 4b383664bb
22 changed files with 1724 additions and 0 deletions

BIN
FreeCell Normal file

Binary file not shown.

BIN
FreeCell. Normal file

Binary file not shown.

BIN
FreeCell..rsrc Normal file

Binary file not shown.

26
bitset.h Normal file
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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;
}

8
strntol.h Normal file
View File

@ -0,0 +1,8 @@
#ifndef STRNTOL_H
#define STRNTOL_H
#include <stddef.h>
long strntol(const char *nptr, size_t sz, char **endptr, int base);
#endif /* STRNTOL_H */