windows-nt/Source/XPSP1/NT/shell/osshell/ep/freecell/game.c
2020-09-26 16:20:57 +08:00

456 lines
14 KiB
C

/****************************************************************************
Game.c
June 91, JimH initial code
Oct 91, JimH port to Win32
Routines for playing the game are here and in game2.c
****************************************************************************/
#include "freecell.h"
#include "freecons.h"
#include <assert.h>
#include <memory.h>
/****************************************************************************
SetFromLoc
User clicks to select card to transfer FROM.
****************************************************************************/
VOID SetFromLoc(HWND hWnd, UINT x, UINT y)
{
HDC hDC;
UINT col, pos;
wFromCol = EMPTY; // assume we didn't find a card
wFromPos = EMPTY;
cUndo = 0;
EnableMenuItem(GetMenu(hWnd), IDM_UNDO, MF_GRAYED);
if (Point2Card(x, y, &col, &pos))
{
if (col == TOPROW)
{
if (card[TOPROW][pos] == EMPTY || pos > 3)
return;
}
else
{
pos = FindLastPos(col);
if (pos == EMPTY) // empty column
return;
}
}
else
return;
wFromCol = col; // ok, we have a card
wFromPos = pos;
wMouseMode = TO;
hDC = GetDC(hWnd);
DrawCard(hDC, col, pos, card[col][pos], HILITE);
if (col == TOPROW && pos < 4)
DrawKing(hDC, LEFT, TRUE);
ReleaseDC(hWnd, hDC);
}
/****************************************************************************
ProcessMoveRequest
After user has selected a FROM card with SetFromLoc() above, he then
chooses a TO location with the mouse which this function processes.
Note that the layout of the cards (the card array) is copied into
an array called shadow. This is so queued move requests can be determined
before we commit to moving the cards.
****************************************************************************/
VOID ProcessMoveRequest(HWND hWnd, UINT x, UINT y)
{
UINT tcol, tpos; // location to move selected card TO
UINT freecells; // number of free cells unoccupied
UINT trans; // number of cards requested transfer requires
UINT maxtrans; // MaxTransfer() number
INT i; // index
TCHAR buffer[160]; // extra buffer needed for MessageBox
assert(wFromCol != EMPTY);
/* Make copy of card[][] in shadow[][] */
memcpy(&(shadow[0][0]), &(card[0][0]), sizeof(card));
Point2Card(x, y, &tcol, &tpos); // determine requested TO loc.
if (tcol >= MAXCOL) // if illegal move selected...
{
tpos = wFromPos; // cancel it.
tcol = wFromCol;
}
if (tcol == TOPROW) // if moving to top row
{
if (tpos > 7) // illegal move...
{
tpos = wFromPos; // so cancel it.
tcol = wFromCol;
}
}
else // if moving to a column...
{
tpos = FindLastPos(tcol); // find end of column
if (tpos == EMPTY) // if column is empty...
tpos = 0; // move to top of column.
}
/* if moving between non-empty columns */
if (wFromCol != TOPROW && tcol != TOPROW && card[tcol][0] != EMPTY)
{
freecells = 0; // count free freecells
for (i = 0; i < 4; i++)
if (card[TOPROW][i] == EMPTY)
freecells++;
trans = NumberToTransfer(wFromCol, tcol); // how many required?
DEBUGMSG(TEXT("trans is %u "), trans);
DEBUGMSG(TEXT("and MaxTransfer() is %u\r\n"), MaxTransfer());
if (trans > 0) // legal move?
{
maxtrans = MaxTransfer();
if (trans <= maxtrans) // enough free cells?
{
MultiMove(wFromCol, tcol);
}
else if (bMessages)
{
LoadString(hInst, IDS_APPNAME, smallbuf, SMALL);
LoadString(hInst, IDS_TOOFEWFREE, buffer, sizeof(buffer)/sizeof(TCHAR));
wsprintf(bigbuf, buffer, trans, maxtrans);
MessageBeep(MB_ICONINFORMATION);
MessageBox(hWnd, bigbuf, smallbuf, MB_ICONINFORMATION);
/* illegal move, so deselect that card */
QueueTransfer(wFromCol, wFromPos, wFromCol, wFromPos);
}
else
return;
}
else
{
if (bMessages)
{
LoadString(hInst, IDS_APPNAME, smallbuf, SMALL);
LoadString(hInst, IDS_ILLEGAL, bigbuf, BIG);
MessageBeep(MB_ICONINFORMATION);
MessageBox(hWnd, bigbuf, smallbuf, MB_ICONINFORMATION);
// deselect
QueueTransfer(wFromCol, wFromPos, wFromCol, wFromPos);
}
else
return;
}
}
else // else move involves TOPROW or move to empty column
{
bMoveCol = 0;
if (IsValidMove(hWnd, tcol, tpos))
{
if (bMoveCol) // user selected move column?
MoveCol(wFromCol, tcol);
else
QueueTransfer(wFromCol, wFromPos, tcol, tpos);
}
else
{
if (bMessages && (bMoveCol != -1)) // user did not select Cancel
{
LoadString(hInst, IDS_APPNAME, smallbuf, SMALL);
LoadString(hInst, IDS_ILLEGAL, bigbuf, BIG);
MessageBeep(MB_ICONINFORMATION);
MessageBox(hWnd, bigbuf, smallbuf, MB_ICONINFORMATION);
// deselect
QueueTransfer(wFromCol, wFromPos, wFromCol, wFromPos);
}
else if (bMoveCol == -1) // user selected cancel
{
// deselect
QueueTransfer(wFromCol, wFromPos, wFromCol, wFromPos);
}
else
return;
}
}
Cleanup(); // queue transfers of unneeded cards
MoveCards(hWnd); // start transferring queued cards
wMouseMode = FROM; // next mouse click chooses FROM card
}
/****************************************************************************
ProcessDoubleClick
Double clicking on card in a column moves it to the first free cell found.
After finding the free cell, this routine is identical to ProcessMoveRequest.
****************************************************************************/
BOOL ProcessDoubleClick(HWND hWnd)
{
UINT freecell = EMPTY; // assume none found
INT i; // counter
for (i = 3; i >= 0; i--) // look for free cell;
if (card[TOPROW][i] == EMPTY)
freecell = (UINT) i;
if (freecell == EMPTY) // if none found
return FALSE;
memcpy(&(shadow[0][0]), &(card[0][0]), sizeof(card));
wFromPos = FindLastPos(wFromCol);
QueueTransfer(wFromCol, wFromPos, TOPROW, freecell);
Cleanup();
MoveCards(hWnd);
wMouseMode = FROM;
return TRUE;
}
/****************************************************************************
IsValidMove
This function determines if moving from wFromCol,wFromPos to tcol,tpos
is valid. It can assume wFromCol and tcol are not both non-empty columns.
In other words, except for moves to empty columns, it is concerned with
moving only one card.
If the move is to an empty column, the user can select if he wants to move
one card or as much of the column as possible.
****************************************************************************/
BOOL IsValidMove(HWND hWnd, UINT tcol, UINT tpos)
{
CARD fcard, tcard; // card values (0 to 51)
UINT trans; // num cards required for transfer
UINT freecells; // num of unoccupied free cells
UINT pos;
DEBUGMSG(TEXT("IVM: tpos is %d\r\n"), tpos);
assert (tpos < MAXPOS);
bMoveCol = FALSE; // assume FALSE
/* allow cancel move from top row. */
if (wFromCol == TOPROW && tcol == TOPROW && wFromPos == tpos)
return TRUE;
fcard = card[wFromCol][wFromPos];
tcard = card[tcol][tpos];
/* transfer to empty column */
if ((wFromCol != TOPROW) && (tcol != TOPROW) && (card[tcol][0] == EMPTY))
{
trans = NumberToTransfer(wFromCol, tcol); // how many required?
freecells = 0;
for (pos = 0; pos < 4; pos++) // count free cells
if (card[TOPROW][pos] == EMPTY)
freecells++;
if (freecells == 0 && trans > 1) // no freecells anyway
trans = 1;
if (trans == 0) // if no valid move
return FALSE;
else if (trans == 1) // if only 1 card can move
return TRUE;
/* If multiple cards can move, user must disambiguate request. */
bMoveCol = (BOOL) DialogBox(hInst, TEXT("MoveCol"), hWnd, MoveColDlg);
if (bMoveCol == -1) // CANCEL chosen
return FALSE;
return TRUE;
}
if (tcol == TOPROW)
{
if (tpos < 4) // free cells
{
if (tcard == EMPTY)
return TRUE;
else
return FALSE;
}
else // home cells
{
if (VALUE(fcard) == ACE && tcard == EMPTY) // ace on new pile
return TRUE;
else if (SUIT(fcard) == SUIT(tcard)) // same suit
{
if (VALUE(fcard) == (VALUE(tcard) + 1)) // next card
return TRUE;
else
return FALSE;
}
return FALSE;
}
}
else // tcol is not TOPROW
{
if (card[tcol][0] == EMPTY) // top of empty column always ok
return TRUE;
return FitsUnder(fcard, tcard);
}
}
/****************************************************************************
Cleanup
This function checks if exposed cards (cards in home cells or bottoms
of columns) are no longer needed (Useless.)
When it finds cards it doesn't need, it queues them for transfer. It
keeps looking until an entire pass finds no new useless cards.
****************************************************************************/
VOID Cleanup()
{
UINT col, pos;
UINT i; // counter
CARD c;
BOOL bMore = TRUE; // assume we need another pass
while (bMore)
{
bMore = FALSE;
for (pos = 0; pos < 4; pos++) // do TOPROW first
{
c = card[TOPROW][pos];
if (Useless(c)) // if card is discardable
{
bMore = TRUE; // need another pass
/* If this is the first card of this suit, we need to
determine which home cell it can use. */
if (homesuit[SUIT(c)] == EMPTY)
{
i = 4;
while (card[TOPROW][i] != EMPTY)
i++;
homesuit[SUIT(c)] = i;
}
QueueTransfer(TOPROW, pos, TOPROW, homesuit[SUIT(c)]);
}
}
for (col = 1; col <= 8; col++) // do columns next
{
pos = FindLastPos(col);
if (pos != EMPTY)
{
c = card[col][pos];
if (Useless(c))
{
bMore = TRUE;
if (homesuit[SUIT(c)] == EMPTY)
{
i = 4;
while (card[TOPROW][i] != EMPTY)
i++;
homesuit[SUIT(c)] = i;
}
QueueTransfer(col, pos, TOPROW, homesuit[SUIT(c)]);
}
}
}
}
}
/****************************************************************************
Useless
returns TRUE if a card can be safely discarded (no existing cards could
possibly play on it.)
Note that the lines identified with // *** are required in the 32 bit version
since EMPTY == 0xFFFF is not longer equal to -1 as was the case in 16 bit
****************************************************************************/
BOOL Useless(CARD c)
{
CARD limit; // top home card of this suit
if (c == EMPTY)
return FALSE; // no card to discard
if (bCheating == CHEAT_WIN)
return TRUE;
if (VALUE(c) == ACE)
return TRUE; // ACEs can always be cleaned up
else if (VALUE(c) == DEUCE) // DEUCEs need only check if ACE is up
{
if (home[SUIT(c)] == ACE)
return TRUE;
else
return FALSE;
}
else // else check both cards that can play on it
{
limit = VALUE(c) - 1;
if (home[SUIT(c)] != limit) // is this the next card?
return FALSE;
if (COLOUR(c) == RED)
{
if (home[CLUB] == EMPTY || home[SPADE] == EMPTY) // ***
return FALSE;
else
return (home[CLUB] >= limit && home[SPADE] >= limit);
}
else
{
if (home[DIAMOND] == EMPTY || home[HEART] == EMPTY) // ***
return FALSE;
else
return (home[DIAMOND] >= limit && home[HEART] >= limit);
}
}
}