|
|
/****************************************************************************
Display.c
June 91, JimH initial code Oct 91, JimH port to Win32
Contains routines dealing with pixels and card shuffling.
****************************************************************************/
#include "freecell.h"
#include "freecons.h"
#include <assert.h>
#include <stdlib.h> // rand() prototype
#include <time.h>
// This static data has to do with positions of the cards on the screen.
// See CalcOffsets() below for descriptions. Note that wOffset[TOPROW]
// is the left edge of the leftmose home cell.
static UINT wOffset[MAXCOL]; // left edge of column n (n from 1 to 8)
static UINT wIconOffset; // left edge of icon (btwn home & free)
static UINT wVSpace; // vert space between home and columns
static UINT wUpdateCol, wUpdatePos; // card user chose to transfer FROM
static BOOL bCardRevealed; // right mouse button show a card?
#define BGND (255) // used for cdtDrawExt
#define ICONY ((dyCrd - ICONHEIGHT) / 3)
/****************************************************************************
CalcOffsets
This function determines the locations where cards are drawn.
****************************************************************************/
VOID CalcOffsets(HWND hWnd) { RECT rect; UINT i; UINT leftedge; BOOL bEGAmode = FALSE;
if (GetSystemMetrics(SM_CYSCREEN) <= 350) // EGA
bEGAmode = TRUE;
GetClientRect(hWnd, &rect);
wOffset[TOPROW] = rect.right - (4 * dxCrd); // home cells
leftedge = (rect.right - ((MAXCOL-1) * dxCrd)) / MAXCOL; for (i = 1; i < MAXCOL; i++) wOffset[i] = leftedge + (((i-1) * (rect.right-leftedge)) / (MAXCOL-1));
/* place icon half way between free cells and home cells */
wIconOffset = (rect.right-ICONWIDTH) / 2;
if (bEGAmode) wVSpace = 4; else wVSpace = 10;
/* dyTops is the vertical space between stacked cards. To fit the
theoretical maximum, the formula is dyTops = (dyCrd * 9) / 50. A compromise is used to make the cards easier to see. It is possible, therefore, that some stacks could get long enough for the bottom cards no to be visible. The situation for EGA is worse, as cards are both closer together, and more likely to fall off the bottom. An alternative is to squish the bitmaps dyCrd = (35 * dyCrd) / 48. */
dyTops = (dyCrd * 9) / 46; // space between tops of cards in columns
if (bEGAmode) dyTops = (dyTops * 4) / 5; }
/****************************************************************************
ShuffleDeck
If seed is non-zero, that number is used as rand() seed to shuffle deck. Otherwise, a seed is generated and presented to the user who may change it in a dialog box.
****************************************************************************/
VOID ShuffleDeck(HWND hWnd, UINT_PTR seed) { UINT i, j; // generic counters
UINT col, pos; UINT wLeft = 52; // cards left to be chosen in shuffle
CARD deck[52]; // deck of 52 unique cards
if (seed == 0) // if user must select seed
{ gamenumber = GenerateRandomGameNum();
/* Keep calling GameNumDlg until valid number chosen. */
while (!DialogBox(hInst, TEXT("GameNum"), hWnd, GameNumDlg)) { }
if (gamenumber == CANCELGAME) // if user chose CANCEL button
return; } else { gamenumber = (INT) seed; }
LoadString(hInst, IDS_APPNAME2, bigbuf, BIG); wsprintf(smallbuf, bigbuf, gamenumber); SetWindowText(hWnd, smallbuf);
for (col = 0; col < MAXCOL; col++) // clear the deck
{ for (pos = 0; pos < MAXPOS; pos++) { card[col][pos] = EMPTY; } }
/* shuffle cards */
for (i = 0; i < 52; i++) // put unique card in each deck loc.
{ deck[i] = i; }
if (gamenumber == -1) // special unwinnable shuffle
{ i = 0;
for (pos = 0; pos < 7; pos++) { for (col = 1; col < 5; col++) { card[col][pos] = i++; }
i+= 4; }
for (pos = 0; pos < 6; pos++) { i -= 12;
for (col = 5; col < 9; col++) { card[col][pos] = i++; } } } else if (gamenumber == -2) { i = 3;
for (col = 1; col < 5; col++) { card[col][0] = i--; }
i = 51;
for (pos = 1; pos < 7; pos++) { for (col = 1; col < 5; col++) { card[col][pos] = i--; } }
for (pos = 0; pos < 6; pos++) { for (col = 5; col < 9; col++) { card[col][pos] = i--; } } } else {
//
// Caution:
// This shuffle algorithm has been published to people all around. The intention
// was to let people track the games by game numbers. So far all the games between
// 1 and 32767 except one have been proved to have a winning solution. Do not change
// the shuffling algorithm else you will incur the wrath of people who have invested
// a huge amount of time solving these games.
//
// The game number can now be upto a million as the srand takes an integer but the
// the random value generated by rand() is only from 0 to 32767.
//
srand(gamenumber); for (i = 0; i < 52; i++) { j = rand() % wLeft; wLeft --; card[(i%8)+1][i/8] = deck[j]; deck[j] = deck[wLeft]; } } }
/****************************************************************************
PaintMainWindow
This function is called in response to WM_PAINT.
****************************************************************************/
VOID PaintMainWindow(HWND hWnd) { PAINTSTRUCT ps; UINT col, pos; UINT y; // y location of icon
CARD c; INT mode; // mode to draw card (FACEUP or HILITE)
HCURSOR hCursor; // original cursor
HPEN hOldPen;
BeginPaint(hWnd, &ps);
/* Draw icon with 3D box around it. */
y = ICONY;
hOldPen = SelectObject(ps.hdc, hBrightPen); MoveToEx(ps.hdc, wIconOffset-3, y + ICONHEIGHT + 1, NULL); LineTo(ps.hdc, wIconOffset-3, y-3); LineTo(ps.hdc, wIconOffset+ICONWIDTH + 2, y-3);
SelectObject(ps.hdc, GetStockObject(BLACK_PEN)); MoveToEx(ps.hdc, wIconOffset + ICONWIDTH + 2, y-2, NULL); LineTo(ps.hdc, wIconOffset + ICONWIDTH + 2, y + ICONHEIGHT + 2); LineTo(ps.hdc, wIconOffset - 3, y + ICONHEIGHT + 2);
DrawKing(ps.hdc, SAME, TRUE);
hCursor = SetCursor(LoadCursor(NULL, IDC_WAIT)); ShowCursor(TRUE);
/* Top row first */
for (pos = 0; pos < 8; pos++) { mode = FACEUP; if ((c = card[TOPROW][pos]) == EMPTY) c = IDGHOST; else if (wMouseMode == TO && pos == wFromPos && TOPROW == wFromCol) mode = HILITE;
DrawCard(ps.hdc, TOPROW, pos, c, mode); }
/* Then, the 8 columns */
for (col = 1; col < MAXCOL; col++) { for (pos = 0; pos < MAXPOS; pos++) { if ((c = card[col][pos]) == EMPTY) break;
if (wMouseMode == TO && pos == wFromPos && col == wFromCol) mode = HILITE; else mode = FACEUP;
DrawCard(ps.hdc, col, pos, c, mode); } }
if (bWonState) Payoff(ps.hdc);
ShowCursor(FALSE); SetCursor(hCursor); SelectObject(ps.hdc, hOldPen); EndPaint(hWnd, &ps); DisplayCardCount(hWnd); }
/****************************************************************************
DrawCard
This function takes a card value and position (in col and pos), converts it to x and y, and displays it in the specified mode.
****************************************************************************/
VOID DrawCard(HDC hDC, UINT col, UINT pos, CARD card, INT mode) { UINT x, y; HDC hMemDC; HBITMAP hOldBitmap;
Card2Point(col, pos, &x, &y); if (card == IDGHOST && hBM_Ghost) { hMemDC = CreateCompatibleDC(hDC); if (hMemDC) { hOldBitmap = SelectObject(hMemDC, hBM_Ghost); BitBlt(hDC, x, y, dxCrd, dyCrd, hMemDC, 0, 0, SRCCOPY); SelectObject(hMemDC, hOldBitmap); DeleteDC(hMemDC); } } else cdtDrawExt(hDC, x, y, dxCrd, dyCrd, card, mode, BGND); }
VOID DrawCardMem(HDC hMemDC, CARD card, INT mode) { cdtDrawExt(hMemDC, 0, 0-dyTops, dxCrd, dyCrd, card, mode, BGND); }
/****************************************************************************
RevealCard
When the user chooses a hidden card by clicking the right mouse button, this function displays the entire card bitmap.
****************************************************************************/
VOID RevealCard(HWND hWnd, UINT x, UINT y) { UINT col, pos; HDC hDC;
bCardRevealed = FALSE; // no card revealed yet
if (Point2Card(x, y, &col, &pos)) { wUpdateCol = col; // save for RestoreColumn()
wUpdatePos = pos; } else return; // not a card
if (col == 0 || pos == (MAXPOS-1)) return;
if (card[col][pos+1] == EMPTY) return;
hDC = GetDC(hWnd); DrawCard(hDC, col, pos, card[col][pos], FACEUP); ReleaseDC(hWnd, hDC); bCardRevealed = TRUE; // ok, card has been revealed
}
/****************************************************************************
RestoreColumn
After RevealCard has messed up a column by revealing a hidden card, this routine patches it up again by redisplaying all the cards from the revealed card down to the bottom of the column. If the bottom card is selected for a move, it is correctly shown hilighted.
****************************************************************************/
VOID RestoreColumn(HWND hWnd) { HDC hDC; UINT pos; UINT lastpos = EMPTY; // last pos in column (for HILITE mode)
INT mode; // HILITE or FACEUP
if (!bCardRevealed) return;
if (wMouseMode == TO) lastpos = FindLastPos(wUpdateCol);
hDC = GetDC(hWnd); mode = FACEUP; for (pos = (wUpdatePos + 1); pos < MAXPOS; pos++) { if (card[wUpdateCol][pos] == EMPTY) break;
if (wMouseMode == TO && pos == lastpos && wUpdateCol == wFromCol) mode = HILITE;
DrawCard(hDC, wUpdateCol, pos, card[wUpdateCol][pos], mode); } ReleaseDC(hWnd, hDC); }
/****************************************************************************
Point2Card
Given an x,y location (typically a mouse click) this function returns the column and position of that card through pointers. The function return value is TRUE if it found a card, and FALSE otherwise.
****************************************************************************/
BOOL Point2Card(UINT x, UINT y, UINT *col, UINT *pos) { if (y < dyCrd) // TOPROW
{ if (x < (UINT) (4 * dxCrd)) // free cells
{ *col = TOPROW; *pos = x / dxCrd; return TRUE; } else if (x < wOffset[TOPROW]) // between free cells & home cells
return FALSE;
x -= wOffset[TOPROW]; if (x < (UINT) (4 * dxCrd)) // home cells
{ *col = TOPROW; *pos = (x / dxCrd) + 4; return TRUE; } else // right of home cells
return FALSE; }
if (y < (dyCrd + wVSpace)) // above column cards
return FALSE;
if (x < wOffset[1]) // left of column 1
return FALSE;
*col = (x - wOffset[1]) / (wOffset[2] - wOffset[1]); (*col)++;
if (x > (wOffset[*col] + dxCrd)) return FALSE; // between columns
y -= (dyCrd + wVSpace);
*pos = min((y / dyTops), MAXPOS);
if (card[*col][0] == EMPTY) return FALSE; // empty column
if (*pos < (MAXPOS-1)) { if (card[*col][(*pos)+1] != EMPTY) // if partially hidden card...
return TRUE; // we're done
}
while (card[*col][*pos] == EMPTY) (*pos)--;
if (y > ((*pos * dyTops) + dyCrd)) return FALSE; // below last card in column
else return TRUE; }
/****************************************************************************
Card2Point
Given a column and position, this function returns the x and y pixel location of the upper left hand corner of the card.
****************************************************************************/
VOID Card2Point(UINT col, UINT pos, UINT *x, UINT *y) { assert(col <= MAXCOL); assert(pos <= MAXPOS);
if (col == TOPROW) // column 0 is really the top row of 8 slots
{ *y = 0; *x = pos * dxCrd; if (pos > 3) *x += wOffset[TOPROW] - (4 * dxCrd); } else { *x = wOffset[col]; *y = dyCrd + wVSpace + (pos * dyTops); } }
/****************************************************************************
DisplayCardCount
This function displays wCardCount (the number of cards not in home cells) at the right edge of the menu bar. If necessary, the old value is erased.
****************************************************************************/
VOID DisplayCardCount(HWND hWnd) { RECT rect; // client rect
HDC hDC; TCHAR buffer[25]; // current value in ASCII stored here
TCHAR oldbuffer[25]; // previous value in ASCII
UINT xLoc; // x pixel loction for count
UINT wCount; // temp wCardCount holder
static UINT yLoc = 0; // y pixel location for count
static UINT wOldCount = 0; // previous count value
HFONT hOldFont = NULL; SIZE size;
if (IsIconic(hWnd)) // don't draw on icon!
return;
hDC = GetWindowDC(hWnd); // get DC for entire window
if (!hDC) return;
SetBkColor(hDC, GetSysColor(COLOR_MENU));
if (hMenuFont) hOldFont = SelectObject(hDC, hMenuFont);
wCount = wCardCount; if (wCount == 0xFFFF) // decremented past 0?
wCount = 0;
LoadString(hInst, IDS_CARDSLEFT, smallbuf, SMALL); wsprintf(buffer, smallbuf, wCount);
if (yLoc == 0) // needs to be set only once
{ TEXTMETRIC tm; int offset;
GetTextMetrics(hDC, &tm); offset = (GetSystemMetrics(SM_CYMENU) - tm.tmHeight) / 2;
yLoc = GetSystemMetrics(SM_CYFRAME) // sizing frame
+ GetSystemMetrics(SM_CYCAPTION) // height of caption
+ offset; }
GetClientRect(hWnd, &rect); GetTextExtentPoint32(hDC, buffer, lstrlen(buffer), &size); xLoc = rect.right - size.cx;
if (xLoc > xOldLoc) // need to erase old score?
{ SetTextColor(hDC, GetSysColor(COLOR_MENU)); // background colour
wsprintf(oldbuffer, smallbuf, wOldCount); TextOut(hDC, xOldLoc, yLoc, oldbuffer, lstrlen(buffer)); } SetTextColor(hDC, GetSysColor(COLOR_MENUTEXT)); TextOut(hDC, xLoc, yLoc, buffer, lstrlen(buffer));
xOldLoc = xLoc; wOldCount = wCount;
if (hMenuFont) SelectObject(hDC, hOldFont);
ReleaseDC(hWnd, hDC); }
/****************************************************************************
Payoff
Draws the Big King when you win the game.
****************************************************************************/
VOID Payoff(HDC hDC) { HDC hMemDC; // bitmap memory DC
HBITMAP hBitmap; HBITMAP hOldBitmap; INT xStretch = 320; // stretched size of bitmap
INT yStretch = 320;
if (GetSystemMetrics(SM_CYSCREEN) <= 350) // EGA
{ xStretch = 32 * 8; yStretch = 32 * 6; }
DrawKing(hDC, NONE, TRUE);
hMemDC = CreateCompatibleDC(hDC); if (!hMemDC) return;
hBitmap = LoadBitmap(hInst, TEXT("KingSmile"));
if (hBitmap) { hOldBitmap = SelectObject(hMemDC, hBitmap); StretchBlt(hDC, 10, dyCrd + 10, xStretch, yStretch, hMemDC, 0, 0, BMWIDTH, BMHEIGHT, SRCCOPY); SelectObject(hMemDC, hOldBitmap); DeleteObject(hBitmap); } DeleteDC(hMemDC); }
/****************************************************************************
DrawKing
Draws the small king in the box between the free and home cells. If state is SAME, the previous bitmap is displayed. If bDraw is FALSE, oldstate is updated, but the bitmap is not displayed.
****************************************************************************/
VOID DrawKing(HDC hDC, UINT state, BOOL bDraw) { HDC hMemDC; // bitmap memory DC
HBITMAP hBitmap; HBITMAP hOldBitmap; static UINT oldstate = RIGHT; // previous state -- RIGHT is default
HBRUSH hOldBrush;
if (state == oldstate) return;
if (state == SAME) state = oldstate;
if (!bDraw) { oldstate = state; return; }
hMemDC = CreateCompatibleDC(hDC); if (!hMemDC) return;
if (state == RIGHT) hBitmap = LoadBitmap(hInst, TEXT("KingBitmap")); else if (state == LEFT) hBitmap = LoadBitmap(hInst, TEXT("KingLeft")); else if (state == SMILE) hBitmap = LoadBitmap(hInst, TEXT("KingSmile")); else // NONE
hBitmap = CreateCompatibleBitmap(hDC, BMWIDTH, BMHEIGHT);
if (hBitmap) { hOldBitmap = SelectObject(hMemDC, hBitmap); if (state == NONE) { hOldBrush = SelectObject(hMemDC, hBgndBrush); PatBlt(hMemDC, 0, 0, BMWIDTH, BMHEIGHT, PATCOPY); SelectObject(hMemDC, hOldBrush); } BitBlt(hDC,wIconOffset,ICONY,BMWIDTH,BMHEIGHT,hMemDC,0,0,SRCCOPY); SelectObject(hMemDC, hOldBitmap); DeleteObject(hBitmap); } DeleteDC(hMemDC); oldstate = state; }
/****************************************************************************
GenerateRandomGameNum
returns a UINT from 1 to MAXGAMENUBMER
****************************************************************************/
UINT GenerateRandomGameNum() { UINT wGameNum;
srand((unsigned int)time(NULL)); rand(); rand(); wGameNum = rand(); while (wGameNum < 1 || wGameNum > MAXGAMENUMBER) wGameNum = rand(); return wGameNum; }
|