548 lines
14 KiB
C
548 lines
14 KiB
C
/****************************************************************************
|
|
|
|
Game2.c
|
|
|
|
June 91, JimH initial code
|
|
Oct 91, JimH port to Win32
|
|
|
|
|
|
Routines for playing the game are here and in game.c
|
|
|
|
****************************************************************************/
|
|
|
|
#include "freecell.h"
|
|
#include "freecons.h"
|
|
#include <assert.h>
|
|
#include <ctype.h> // for isdigit()
|
|
|
|
|
|
static HCURSOR hFlipCursor;
|
|
|
|
/******************************************************************************
|
|
|
|
MaxTransfer
|
|
|
|
This function and the recursive MaxTransfer2 determine the maximum
|
|
number of cards that could be transfered given the current number
|
|
of free cells and empty columns.
|
|
|
|
******************************************************************************/
|
|
|
|
UINT MaxTransfer()
|
|
{
|
|
UINT freecells = 0;
|
|
UINT freecols = 0;
|
|
UINT col, pos;
|
|
|
|
for (pos = 0; pos < 4; pos++) // count free cells
|
|
if (card[TOPROW][pos] == EMPTY)
|
|
freecells++;
|
|
|
|
for (col = 1; col <= 8; col++) // count empty columns
|
|
if (card[col][0] == EMPTY)
|
|
freecols++;
|
|
|
|
return MaxTransfer2(freecells, freecols);
|
|
}
|
|
|
|
UINT MaxTransfer2(UINT freecells, UINT freecols)
|
|
{
|
|
if (freecols == 0)
|
|
return(freecells + 1);
|
|
return(freecells + 1 + MaxTransfer2(freecells, freecols-1));
|
|
}
|
|
|
|
|
|
/******************************************************************************
|
|
|
|
NumberToTransfer
|
|
|
|
Given a from column and a to column, this function returns the number of
|
|
cards required to do the transfer, or 0 if there is no legal move.
|
|
|
|
If the transfer is from a column to an empty column, this function returns
|
|
the maximum number of cards that could transfer.
|
|
|
|
******************************************************************************/
|
|
|
|
UINT NumberToTransfer(UINT fcol, UINT tcol)
|
|
{
|
|
UINT fpos, tpos;
|
|
CARD tcard; // card to transfer onto
|
|
UINT number = 0; // the returned result
|
|
|
|
assert(fcol > 0 && fcol < 9);
|
|
assert(tcol > 0 && tcol < 9);
|
|
assert(card[fcol][0] != EMPTY);
|
|
|
|
if (fcol == tcol)
|
|
return 1; // cancellation takes one move
|
|
|
|
fpos = FindLastPos(fcol);
|
|
|
|
if (card[tcol][0] == EMPTY) // if transfer to empty column
|
|
{
|
|
while (fpos > 0)
|
|
{
|
|
if (!FitsUnder(card[fcol][fpos], card[fcol][fpos-1]))
|
|
break;
|
|
fpos--;
|
|
number++;
|
|
}
|
|
return (number+1);
|
|
}
|
|
else
|
|
{
|
|
tpos = FindLastPos(tcol);
|
|
tcard = card[tcol][tpos];
|
|
for (;;)
|
|
{
|
|
number++;
|
|
if (FitsUnder(card[fcol][fpos], tcard))
|
|
return number;
|
|
if (fpos == 0)
|
|
return 0;
|
|
if (!FitsUnder(card[fcol][fpos], card[fcol][fpos-1]))
|
|
return 0;
|
|
fpos--;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/******************************************************************************
|
|
|
|
FitsUnder
|
|
|
|
returns TRUE if fcard fits under tcard
|
|
|
|
******************************************************************************/
|
|
|
|
BOOL FitsUnder(CARD fcard, CARD tcard)
|
|
{
|
|
if ((VALUE(tcard) - VALUE(fcard)) != 1)
|
|
return FALSE;
|
|
|
|
if (COLOUR(fcard) == COLOUR(tcard))
|
|
return FALSE;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
|
|
/******************************************************************************
|
|
|
|
IsGameLost
|
|
|
|
If there are legal moves remaining, the game is not lost and this function
|
|
returns without doing anything.
|
|
|
|
Otherwise, it pops up the YouLose dialog box.
|
|
|
|
******************************************************************************/
|
|
|
|
VOID IsGameLost(HWND hWnd)
|
|
{
|
|
UINT col, pos;
|
|
UINT fcol, tcol;
|
|
CARD lastcard[MAXCOL]; // array of cards at bottoms of columns
|
|
CARD c;
|
|
UINT cMoves = 0; // count of legal moves remaining
|
|
|
|
if (bCheating == CHEAT_LOSE)
|
|
goto cheatloselabel;
|
|
|
|
for (pos = 0; pos < 4; pos++) // any free cells?
|
|
if (card[TOPROW][pos] == EMPTY)
|
|
return;
|
|
|
|
for (col = 1; col < MAXCOL; col++) // any free columns?
|
|
if (card[col][0] == EMPTY)
|
|
return;
|
|
|
|
/* Do the bottom cards of any column fit in the home cells? */
|
|
|
|
for (col = 1; col < MAXCOL; col++)
|
|
{
|
|
lastcard[col] = card[col][FindLastPos(col)];
|
|
c = lastcard[col];
|
|
if (VALUE(c) == ACE)
|
|
cMoves++;
|
|
if (home[SUIT(c)] == (VALUE(c) - 1)) // fits in home cell?
|
|
cMoves++;
|
|
}
|
|
|
|
/* Do any of the cards in the free cells fit in the home cells? */
|
|
|
|
for (pos = 0; pos < 4; pos++)
|
|
{
|
|
c = card[TOPROW][pos];
|
|
if (home[SUIT(c)] == (VALUE(c) - 1))
|
|
cMoves++;
|
|
}
|
|
|
|
/* Do any of the cards in the free cells fit under a column? */
|
|
|
|
for (pos = 0; pos < 4; pos++)
|
|
for (col = 1; col < MAXCOL; col++)
|
|
if (FitsUnder(card[TOPROW][pos], lastcard[col]))
|
|
cMoves++;
|
|
|
|
/* Do any of the bottom cards fit under any other bottom card? */
|
|
|
|
for (fcol = 1; fcol < MAXCOL; fcol++)
|
|
for (tcol = 1; tcol < MAXCOL; tcol++)
|
|
if (tcol != fcol)
|
|
if (FitsUnder(lastcard[fcol], lastcard[tcol]))
|
|
cMoves++;
|
|
|
|
if (cMoves > 0)
|
|
{
|
|
if (cMoves == 1) // one move left
|
|
{
|
|
cFlashes = 4; // flash this many times
|
|
|
|
if (idTimer != 0)
|
|
KillTimer(hWnd, FLASH_TIMER);
|
|
|
|
idTimer = SetTimer(hWnd, FLASH_TIMER, FLASH_INTERVAL, NULL);
|
|
}
|
|
return;
|
|
}
|
|
|
|
/* We've tried everything. There are no more legal moves. */
|
|
|
|
cheatloselabel:
|
|
cUndo = 0;
|
|
EnableMenuItem(GetMenu(hWnd), IDM_UNDO, MF_GRAYED);
|
|
DialogBox(hInst, TEXT("YouLose"), hWnd, YouLoseDlg);
|
|
gamenumber = 0; // cancel mouse activity
|
|
bCheating = FALSE;
|
|
}
|
|
|
|
|
|
/****************************************************************************
|
|
|
|
FindLastPos
|
|
|
|
returns position of last card in column, or EMPTY if column is empty.
|
|
|
|
****************************************************************************/
|
|
|
|
UINT FindLastPos(UINT col)
|
|
{
|
|
UINT pos = 0;
|
|
|
|
if (col > 9)
|
|
return EMPTY;
|
|
|
|
while (card[col][pos] != EMPTY)
|
|
pos++;
|
|
pos--;
|
|
|
|
return pos;
|
|
}
|
|
|
|
|
|
|
|
/******************************************************************************
|
|
|
|
UpdateLossCount
|
|
|
|
If game is lost, update statistics.
|
|
|
|
******************************************************************************/
|
|
|
|
VOID UpdateLossCount()
|
|
{
|
|
int cLifetimeLosses; // includes .ini stats
|
|
int wStreak, wSType; // streak length and type
|
|
int wLosses; // record loss streak
|
|
LONG lRegResult; // used to store return code from registry call
|
|
|
|
// repeats and negative (unwinnable) games don't count
|
|
|
|
if ((gamenumber > 0) && (gamenumber != oldgamenumber))
|
|
{
|
|
lRegResult = REGOPEN
|
|
|
|
if (ERROR_SUCCESS == lRegResult)
|
|
{
|
|
cLifetimeLosses = GetInt(pszLost, 0);
|
|
cLifetimeLosses++;
|
|
cLosses++;
|
|
cGames++;
|
|
SetInt(pszLost, cLifetimeLosses);
|
|
wSType = GetInt(pszSType, WON);
|
|
if (wSType == WON)
|
|
{
|
|
SetInt(pszSType, LOST);
|
|
wStreak = 1;
|
|
SetInt(pszStreak, wStreak);
|
|
}
|
|
else
|
|
{
|
|
wStreak = GetInt(pszStreak, 0);
|
|
wStreak++;
|
|
SetInt(pszStreak, wStreak);
|
|
}
|
|
|
|
wLosses = GetInt(pszLosses, 0);
|
|
if (wLosses < wStreak) // if new record
|
|
{
|
|
wLosses = wStreak;
|
|
SetInt(pszLosses, wLosses);
|
|
}
|
|
|
|
REGCLOSE
|
|
}
|
|
}
|
|
oldgamenumber = gamenumber;
|
|
}
|
|
|
|
|
|
/******************************************************************************
|
|
|
|
KeyboardInput
|
|
|
|
Handles keyboard input from the main message loop. Only digits are considered.
|
|
This function works by simulating mouse clicks for each digit pressed.
|
|
|
|
Note that when you have selected a card in a free cell, but you want to
|
|
select another card, you press '0' again. This function sends (not posts,
|
|
sends so that bMessages can be turned off) a mouse click to deselect that
|
|
card, and then looks if there is another card in free cells to the right,
|
|
and if so, selects it.
|
|
|
|
******************************************************************************/
|
|
|
|
VOID KeyboardInput(HWND hWnd, UINT keycode)
|
|
{
|
|
UINT x, y;
|
|
UINT col = TOPROW;
|
|
UINT pos = 0;
|
|
BOOL bSave; // save status of bMessages;
|
|
CARD c;
|
|
|
|
if (!isdigit(keycode))
|
|
return;
|
|
|
|
switch (keycode) {
|
|
|
|
case '0': // free cell
|
|
if (wMouseMode == FROM) // select a card to transfer
|
|
{
|
|
for (pos = 0; pos < 4; pos++)
|
|
if (card[TOPROW][pos] != EMPTY)
|
|
break;
|
|
if (pos == 4) // no card to select
|
|
return;
|
|
}
|
|
else // transfer TO free cell
|
|
{
|
|
if (wFromCol == TOPROW) // pick new free cell
|
|
{
|
|
/* Turn off messages so deselection moves don't complain
|
|
if there is only one move left. */
|
|
|
|
bSave = bMessages;
|
|
bMessages = FALSE;
|
|
|
|
/* deselect current selection */
|
|
|
|
Card2Point(TOPROW, wFromPos, &x, &y);
|
|
SendMessage(hWnd, WM_LBUTTONDOWN, 0,
|
|
MAKELONG((WORD)x, (WORD)y));
|
|
|
|
/* find next non-empty free cell */
|
|
|
|
for (pos = wFromPos+1; pos < 4; pos++)
|
|
{
|
|
if (card[TOPROW][pos] != EMPTY)
|
|
break;
|
|
}
|
|
|
|
bMessages = bSave;
|
|
if (pos == 4) // none found, so leave deselected
|
|
return;
|
|
}
|
|
else // transfer from a column, not TOPROW
|
|
{
|
|
for (pos = 0; pos < 4; pos++)
|
|
if (card[TOPROW][pos] == EMPTY)
|
|
break;
|
|
|
|
if (pos == 4) // no empty freecells
|
|
pos = 0; // force an error message
|
|
}
|
|
}
|
|
break;
|
|
|
|
case '9': // home cell
|
|
if (wMouseMode == FROM) // can't move from home cell
|
|
return;
|
|
|
|
c = card[wFromCol][wFromPos];
|
|
pos = homesuit[SUIT(c)];
|
|
if (pos == EMPTY) // no home suit so can't do anything
|
|
pos = 4; // force error
|
|
break;
|
|
|
|
default: // columns 1 to 8
|
|
col = keycode - '0';
|
|
break;
|
|
}
|
|
|
|
if (col == wFromCol && wMouseMode == TO && col > 0 && col < 9 &&
|
|
card[col][1] != EMPTY)
|
|
{
|
|
bFlipping = (BOOL) SetTimer(hWnd, FLIP_TIMER, FLIP_INTERVAL, NULL);
|
|
}
|
|
|
|
if (bFlipping)
|
|
{
|
|
hFlipCursor = SetCursor(LoadCursor(NULL, IDC_WAIT));
|
|
ShowCursor(TRUE);
|
|
Flip(hWnd); // do first card manually
|
|
}
|
|
else
|
|
{
|
|
Card2Point(col, pos, &x, &y);
|
|
PostMessage(hWnd, WM_LBUTTONDOWN, 0,
|
|
MAKELONG((WORD)x, (WORD)y));
|
|
}
|
|
}
|
|
|
|
|
|
/******************************************************************************
|
|
|
|
Flash
|
|
|
|
This function is called by the FLASH_TIMER to flash main window.
|
|
|
|
******************************************************************************/
|
|
|
|
VOID Flash(HWND hWnd)
|
|
{
|
|
FlashWindow(hWnd, TRUE);
|
|
cFlashes--;
|
|
|
|
if (cFlashes <= 0)
|
|
{
|
|
FlashWindow(hWnd, FALSE);
|
|
KillTimer(hWnd, FLASH_TIMER);
|
|
idTimer = 0;
|
|
}
|
|
}
|
|
|
|
|
|
/******************************************************************************
|
|
|
|
Flip
|
|
|
|
This function is called by the FLIP_TIMER to flip cards through in one
|
|
column. It is used for keyboard players who want to reveal hidden cards.
|
|
|
|
******************************************************************************/
|
|
|
|
VOID Flip(HWND hWnd)
|
|
{
|
|
HDC hDC;
|
|
UINT x, y;
|
|
static UINT pos = 0;
|
|
|
|
hDC = GetDC(hWnd);
|
|
DrawCard(hDC, wFromCol, pos, card[wFromCol][pos], FACEUP);
|
|
pos++;
|
|
if (card[wFromCol][pos] == EMPTY)
|
|
{
|
|
pos = 0;
|
|
KillTimer(hWnd, FLIP_TIMER);
|
|
bFlipping = FALSE;
|
|
ShowCursor(FALSE);
|
|
SetCursor(hFlipCursor);
|
|
|
|
/* cancel move */
|
|
|
|
Card2Point(wFromCol, pos, &x, &y);
|
|
PostMessage(hWnd, WM_LBUTTONDOWN, 0,
|
|
MAKELONG((WORD)x, (WORD)y));
|
|
}
|
|
ReleaseDC(hWnd, hDC);
|
|
}
|
|
|
|
|
|
/******************************************************************************
|
|
|
|
Undo
|
|
|
|
Undo last move
|
|
|
|
******************************************************************************/
|
|
|
|
VOID Undo(HWND hWnd)
|
|
{
|
|
int i;
|
|
|
|
if (cUndo == 0)
|
|
return;
|
|
|
|
SetCursor(LoadCursor(NULL, IDC_WAIT)); // set cursor to hourglass
|
|
SetCapture(hWnd);
|
|
ShowCursor(TRUE);
|
|
|
|
for (i = cUndo-1; i >= 0; i--)
|
|
{
|
|
CARD c;
|
|
int fcol, fpos, tcol, tpos;
|
|
|
|
fcol = movelist[i].tcol;
|
|
fpos = movelist[i].tpos;
|
|
tcol = movelist[i].fcol;
|
|
tpos = movelist[i].fpos;
|
|
|
|
if (fcol != TOPROW && fcol == tcol) // no move so exit
|
|
break;
|
|
|
|
if (fcol != TOPROW)
|
|
fpos = FindLastPos(fcol);
|
|
|
|
if (tcol != TOPROW)
|
|
tpos = FindLastPos(tcol) + 1;
|
|
|
|
Glide(hWnd, fcol, fpos, tcol, tpos); // send the card on its way
|
|
|
|
c = card[fcol][fpos];
|
|
|
|
if (fcol == TOPROW && fpos > 3) // if from home cell
|
|
{
|
|
wCardCount++;
|
|
DisplayCardCount(hWnd); // update display
|
|
|
|
home[SUIT(c)]--;
|
|
|
|
if (VALUE(c) == ACE)
|
|
{
|
|
card[fcol][fpos] = EMPTY;
|
|
homesuit[SUIT(c)] = EMPTY;
|
|
}
|
|
else
|
|
{
|
|
card[fcol][fpos] -= 4;
|
|
}
|
|
}
|
|
else
|
|
card[fcol][fpos] = EMPTY;
|
|
|
|
card[tcol][tpos] = c;
|
|
}
|
|
|
|
cUndo = 0;
|
|
EnableMenuItem(GetMenu(hWnd), IDM_UNDO, MF_GRAYED);
|
|
|
|
ShowCursor(FALSE);
|
|
SetCursor(LoadCursor(NULL, IDC_ARROW));
|
|
ReleaseCapture();
|
|
}
|