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

491 lines
13 KiB
C

/****************************************************************************
Transfer.c
June 91, JimH initial code
Oct 91, JimH port to Win32
Routines for transfering cards and queing cards for transfer are here.
****************************************************************************/
#include "freecell.h"
#include "freecons.h"
#include <assert.h>
#include <memory.h>
/****************************************************************************
Transfer
This function actually moves the cards. It both updates the card array,
and draws the bitmaps. Note that it moves only one card per call.
****************************************************************************/
VOID Transfer(HWND hWnd, UINT fcol, UINT fpos, UINT tcol, UINT tpos)
{
CARD c;
HDC hDC;
DEBUGMSG(TEXT("Transfer request from (%u, "), fcol);
DEBUGMSG(TEXT("%u) to ("), fpos);
DEBUGMSG(TEXT("%u, "), tcol);
DEBUGMSG(TEXT("%u)\r\n"), tpos);
assert(fpos < MAXPOS);
assert(tpos < MAXPOS);
UpdateWindow(hWnd); // ensure cards are drawn before animation starts
if (fcol == TOPROW) // can't transfer FROM home cells
{
if ((fpos > 3) || (card[TOPROW][fpos] == IDGHOST))
return;
}
else
{
if ((fpos = FindLastPos(fcol)) == EMPTY) // or from empty column
return;
if (fcol == tcol) // click and release on same column
{
hDC = GetDC(hWnd);
DrawCard(hDC, fcol, fpos, card[fcol][fpos], FACEUP);
ReleaseDC(hWnd, hDC);
return;
}
}
if (tcol == TOPROW)
{
if (tpos > 3) // if move to home cell
{
wCardCount--;
DisplayCardCount(hWnd); // update display
c = card[fcol][fpos];
home[SUIT(c)] = VALUE(c); // new card at top of home[suit]
}
}
else
tpos = FindLastPos(tcol) + 1; // bottom of column
Glide(hWnd, fcol, fpos, tcol, tpos); // send the card on its way
c = card[fcol][fpos];
card[fcol][fpos] = EMPTY;
card[tcol][tpos] = c;
/* If ACE being moved to home cell, update homesuit array. */
if (VALUE(c) == ACE && tcol == TOPROW && tpos > 3)
homesuit[SUIT(c)] = tpos;
if (tcol == TOPROW)
{
hDC = GetDC(hWnd);
DrawKing(hDC, tpos < 4 ? LEFT : RIGHT, TRUE);
ReleaseDC(hWnd, hDC);
}
}
/******************************************************************************
MoveCol
User has requested a multi-card move to an empty column
******************************************************************************/
VOID MoveCol(UINT fcol, UINT tcol)
{
UINT freecells; // number of free cells
CARD free[4]; // locations of free cells
UINT trans; // number to transfer
INT i; // counter
assert(fcol != TOPROW);
assert(tcol != TOPROW);
assert(card[fcol][0] != EMPTY);
/* Count number of free cells and put locations in free[] */
freecells = 0;
for (i = 0; i < 4; i++)
{
if (card[TOPROW][i] == EMPTY)
{
free[freecells] = i;
freecells++;
}
}
/* Find number of cards to transfer */
if (fcol == TOPROW || tcol == TOPROW)
trans = 1;
else
trans = NumberToTransfer(fcol, tcol);
if (trans > (freecells+1)) // don't transfer too many
trans = freecells+1;
/* Move to free cells */
trans--;
for (i = 0; i < (INT)trans; i++)
QueueTransfer(fcol, 0, TOPROW, free[i]);
/* Transfer last card directly */
QueueTransfer(fcol, 0, tcol, 0);
/* transfer from free cells to column */
for (i = trans-1; i >= 0; i--)
QueueTransfer(TOPROW, free[i], tcol, 0);
}
/******************************************************************************
MultiMove
User has chosen to move from one non-empty column to another.
******************************************************************************/
VOID MultiMove(UINT fcol, UINT tcol)
{
CARD free[4]; // locations of free cells
UINT freecol[MAXCOL]; // locations of free columns
UINT freecells; // number of free cells
UINT trans; // number to transfer
UINT col, pos;
INT i; // counter
assert(fcol != TOPROW);
assert(tcol != TOPROW);
assert(card[fcol][0] != EMPTY);
/* Count number of free cells and put locations in free[] */
freecells = 0;
for (pos = 0; pos < 4; pos++)
{
if (card[TOPROW][pos] == EMPTY)
{
free[freecells] = pos;
freecells++;
}
}
/* Find the number of cards to move. If the number is too big to
move all at once, push partial results into available columns. */
trans = NumberToTransfer(fcol, tcol);
if (trans > (freecells+1))
{
i = 0;
for (col = 1; col < MAXCOL; col++)
if (card[col][0] == EMPTY)
freecol[i++] = col;
/* transfer into free columns until direct transfer can be made */
i = 0;
while (trans > (freecells + 1))
{
MoveCol(fcol, freecol[i]);
trans -= (freecells + 1);
i++;
}
MoveCol(fcol, tcol); // do last transfer directly
for (i--; i >= 0; i--) // gather cards in free cells
MoveCol(freecol[i], tcol);
}
else // else all one MoveCol()
{
MoveCol(fcol, tcol);
}
}
/****************************************************************************
QueueTransfer
In order that multi-card moves happen slowly enough for the user to
watch, they are not moved as soon as they are calculated. Instead,
they first are queued using this function into the movelist array.
After the request is queued, the card array is updated to reflect the
request. This is ok because the card array is saved away in shadow
temporarily. The same logic as in Transfer() is used to update card.
****************************************************************************/
VOID QueueTransfer(UINT fcol, UINT fpos, UINT tcol, UINT tpos)
{
CARD c;
MOVE move;
assert(moveindex < MAXMOVELIST);
assert(fpos < MAXPOS);
assert(tpos < MAXPOS);
move.fcol = fcol; // package move request into MOVE type
move.fpos = fpos;
move.tcol = tcol;
move.tpos = tpos;
movelist[moveindex++] = move; // store request in array and update index
/* Now update card array if necessary. */
if (fcol == TOPROW)
{
if ((fpos > 3) || (card[TOPROW][fpos] == IDGHOST))
return;
}
else
{
if ((fpos = FindLastPos(fcol)) == EMPTY)
return;
if (fcol == tcol) // click and release on same column
return;
}
if (tcol == TOPROW)
{
if (tpos > 3)
{
c = card[fcol][fpos];
home[SUIT(c)] = VALUE(c);
}
}
else
tpos = FindLastPos(tcol) + 1;
c = card[fcol][fpos];
card[fcol][fpos] = EMPTY;
card[tcol][tpos] = c;
if (VALUE(c) == ACE && tcol == TOPROW && tpos > 3)
homesuit[SUIT(c)] = tpos;
}
/******************************************************************************
MoveCards
If there are queued transfer requests, this function moves them.
******************************************************************************/
VOID MoveCards(HWND hWnd)
{
UINT i;
if (moveindex == 0) // if there are no queued requests
return;
/* restore card to its state before requests got queued. */
memcpy(&(card[0][0]), &(shadow[0][0]), sizeof(card));
SetCursor(LoadCursor(NULL, IDC_WAIT)); // set cursor to hourglass
SetCapture(hWnd);
ShowCursor(TRUE);
for (i = 0; i < moveindex; i++)
Transfer(hWnd, movelist[i].fcol, movelist[i].fpos,
movelist[i].tcol, movelist[i].tpos);
if ((moveindex > 1) || (movelist[0].fcol != movelist[0].tcol))
{
cUndo = moveindex;
EnableMenuItem(GetMenu(hWnd), IDM_UNDO, MF_ENABLED);
}
else
{
cUndo = 0;
EnableMenuItem(GetMenu(hWnd), IDM_UNDO, MF_GRAYED);
}
moveindex = 0; // no cards left to move
ShowCursor(FALSE);
SetCursor(LoadCursor(NULL, IDC_ARROW));
ReleaseCapture();
if (wCardCount == 0) // if game is won
{
INT cLifetimeWins; // wins including .ini stats
INT wStreak, wSType; // streak length and type
INT wWins; // record win streak
INT_PTR nResponse; // dialog box response
HDC hDC;
LONG lRegResult;
cUndo = 0;
EnableMenuItem(GetMenu(hWnd), IDM_UNDO, MF_GRAYED);
lRegResult = REGOPEN
if (ERROR_SUCCESS == lRegResult)
{
bGameInProgress = FALSE;
bCheating = FALSE;
cLifetimeWins = GetInt(pszWon, 0);
if (gamenumber != oldgamenumber) // repeats don't count
{
cLifetimeWins++;
cWins++;
cGames++;
SetInt(pszWon, cLifetimeWins);
wSType = GetInt(pszSType, LOST);
if (wSType == LOST)
{
SetInt(pszSType, WON);
wStreak = 1;
SetInt(pszStreak, 1);
}
else
{
wStreak = GetInt(pszStreak, 0);
wStreak++;
SetInt(pszStreak, wStreak);
}
wWins = GetInt(pszWins, 0);
if (wWins < wStreak) // if new record
{
wWins = wStreak;
SetInt(pszWins, wWins);
}
}
REGCLOSE
}
hDC = GetDC(hWnd);
Payoff(hDC);
ReleaseDC(hWnd, hDC);
bWonState = TRUE;
nResponse = DialogBox(hInst, TEXT("YouWin"), hWnd, YouWinDlg);
if (nResponse == IDYES)
PostMessage(hWnd, WM_COMMAND,
bSelecting ? IDM_SELECT : IDM_NEWGAME, 0);
oldgamenumber = gamenumber;
gamenumber = 0; // turn off mouse handling
}
else
IsGameLost(hWnd); // check for game lost
}
/******************************************************************************
SetCursorShape
This function is called in response to WM_MOUSEMOVE. If the current pointer
position represents a legal move, the cursor shape changes to indicate this.
******************************************************************************/
VOID SetCursorShape(HWND hWnd, UINT x, UINT y)
{
UINT tcol = 0, tpos = 0;
UINT trans; // number of cards required to transfer
BOOL bFound; // is cursor over card?
HDC hDC;
/* If we're flipping, cursor is always an hourglass. */
if (bFlipping)
{
SetCursor(LoadCursor(NULL, IDC_WAIT));
return;
}
bFound = Point2Card(x, y, &tcol, &tpos);
if (bFound && tcol == TOPROW)
{
hDC = GetDC(hWnd);
if (tpos < 4)
DrawKing(hDC, LEFT, TRUE);
else
DrawKing(hDC, RIGHT, TRUE);
ReleaseDC(hWnd, hDC);
}
/* Unless we're chosing a move target, cursor is just an arrow. */
if (wMouseMode != TO)
{
SetCursor(LoadCursor(NULL, IDC_ARROW));
return;
}
/* If we're not on a card, check if we're pointing to an empty
column (up arrow), otherwise arrow. */
if (!bFound)
{
if ((tcol > 0 && tcol < 9) && (card[tcol][0] == EMPTY))
SetCursor(LoadCursor(NULL, IDC_UPARROW));
else
SetCursor(LoadCursor(NULL, IDC_ARROW));
return;
}
if (tcol != TOPROW)
tpos = FindLastPos(tcol);
/* Check for cancel request. */
if (wFromCol == tcol && wFromPos == tpos)
{
SetCursor(LoadCursor(NULL, IDC_ARROW));
return;
}
/* Check moves from or to the top row. */
if (wFromCol == TOPROW || tcol == TOPROW)
{
if (IsValidMove(hWnd, tcol, tpos))
{
if (tcol == TOPROW)
SetCursor(LoadCursor(NULL, IDC_UPARROW));
else
SetCursor(LoadCursor(hInst, TEXT("DownArrow")));
}
else
SetCursor(LoadCursor(NULL, IDC_ARROW));
return;
}
/* Check moves between columns. */
trans = NumberToTransfer(wFromCol, tcol); // how many required?
if ((trans > 0) && (trans <= MaxTransfer()))
SetCursor(LoadCursor(hInst, TEXT("DownArrow")));
else
SetCursor(LoadCursor(NULL, IDC_ARROW));
}