Source code of Windows XP (NT5)
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

547 lines
14 KiB

  1. /****************************************************************************
  2. Game2.c
  3. June 91, JimH initial code
  4. Oct 91, JimH port to Win32
  5. Routines for playing the game are here and in game.c
  6. ****************************************************************************/
  7. #include "freecell.h"
  8. #include "freecons.h"
  9. #include <assert.h>
  10. #include <ctype.h> // for isdigit()
  11. static HCURSOR hFlipCursor;
  12. /******************************************************************************
  13. MaxTransfer
  14. This function and the recursive MaxTransfer2 determine the maximum
  15. number of cards that could be transfered given the current number
  16. of free cells and empty columns.
  17. ******************************************************************************/
  18. UINT MaxTransfer()
  19. {
  20. UINT freecells = 0;
  21. UINT freecols = 0;
  22. UINT col, pos;
  23. for (pos = 0; pos < 4; pos++) // count free cells
  24. if (card[TOPROW][pos] == EMPTY)
  25. freecells++;
  26. for (col = 1; col <= 8; col++) // count empty columns
  27. if (card[col][0] == EMPTY)
  28. freecols++;
  29. return MaxTransfer2(freecells, freecols);
  30. }
  31. UINT MaxTransfer2(UINT freecells, UINT freecols)
  32. {
  33. if (freecols == 0)
  34. return(freecells + 1);
  35. return(freecells + 1 + MaxTransfer2(freecells, freecols-1));
  36. }
  37. /******************************************************************************
  38. NumberToTransfer
  39. Given a from column and a to column, this function returns the number of
  40. cards required to do the transfer, or 0 if there is no legal move.
  41. If the transfer is from a column to an empty column, this function returns
  42. the maximum number of cards that could transfer.
  43. ******************************************************************************/
  44. UINT NumberToTransfer(UINT fcol, UINT tcol)
  45. {
  46. UINT fpos, tpos;
  47. CARD tcard; // card to transfer onto
  48. UINT number = 0; // the returned result
  49. assert(fcol > 0 && fcol < 9);
  50. assert(tcol > 0 && tcol < 9);
  51. assert(card[fcol][0] != EMPTY);
  52. if (fcol == tcol)
  53. return 1; // cancellation takes one move
  54. fpos = FindLastPos(fcol);
  55. if (card[tcol][0] == EMPTY) // if transfer to empty column
  56. {
  57. while (fpos > 0)
  58. {
  59. if (!FitsUnder(card[fcol][fpos], card[fcol][fpos-1]))
  60. break;
  61. fpos--;
  62. number++;
  63. }
  64. return (number+1);
  65. }
  66. else
  67. {
  68. tpos = FindLastPos(tcol);
  69. tcard = card[tcol][tpos];
  70. for (;;)
  71. {
  72. number++;
  73. if (FitsUnder(card[fcol][fpos], tcard))
  74. return number;
  75. if (fpos == 0)
  76. return 0;
  77. if (!FitsUnder(card[fcol][fpos], card[fcol][fpos-1]))
  78. return 0;
  79. fpos--;
  80. }
  81. }
  82. }
  83. /******************************************************************************
  84. FitsUnder
  85. returns TRUE if fcard fits under tcard
  86. ******************************************************************************/
  87. BOOL FitsUnder(CARD fcard, CARD tcard)
  88. {
  89. if ((VALUE(tcard) - VALUE(fcard)) != 1)
  90. return FALSE;
  91. if (COLOUR(fcard) == COLOUR(tcard))
  92. return FALSE;
  93. return TRUE;
  94. }
  95. /******************************************************************************
  96. IsGameLost
  97. If there are legal moves remaining, the game is not lost and this function
  98. returns without doing anything.
  99. Otherwise, it pops up the YouLose dialog box.
  100. ******************************************************************************/
  101. VOID IsGameLost(HWND hWnd)
  102. {
  103. UINT col, pos;
  104. UINT fcol, tcol;
  105. CARD lastcard[MAXCOL]; // array of cards at bottoms of columns
  106. CARD c;
  107. UINT cMoves = 0; // count of legal moves remaining
  108. if (bCheating == CHEAT_LOSE)
  109. goto cheatloselabel;
  110. for (pos = 0; pos < 4; pos++) // any free cells?
  111. if (card[TOPROW][pos] == EMPTY)
  112. return;
  113. for (col = 1; col < MAXCOL; col++) // any free columns?
  114. if (card[col][0] == EMPTY)
  115. return;
  116. /* Do the bottom cards of any column fit in the home cells? */
  117. for (col = 1; col < MAXCOL; col++)
  118. {
  119. lastcard[col] = card[col][FindLastPos(col)];
  120. c = lastcard[col];
  121. if (VALUE(c) == ACE)
  122. cMoves++;
  123. if (home[SUIT(c)] == (VALUE(c) - 1)) // fits in home cell?
  124. cMoves++;
  125. }
  126. /* Do any of the cards in the free cells fit in the home cells? */
  127. for (pos = 0; pos < 4; pos++)
  128. {
  129. c = card[TOPROW][pos];
  130. if (home[SUIT(c)] == (VALUE(c) - 1))
  131. cMoves++;
  132. }
  133. /* Do any of the cards in the free cells fit under a column? */
  134. for (pos = 0; pos < 4; pos++)
  135. for (col = 1; col < MAXCOL; col++)
  136. if (FitsUnder(card[TOPROW][pos], lastcard[col]))
  137. cMoves++;
  138. /* Do any of the bottom cards fit under any other bottom card? */
  139. for (fcol = 1; fcol < MAXCOL; fcol++)
  140. for (tcol = 1; tcol < MAXCOL; tcol++)
  141. if (tcol != fcol)
  142. if (FitsUnder(lastcard[fcol], lastcard[tcol]))
  143. cMoves++;
  144. if (cMoves > 0)
  145. {
  146. if (cMoves == 1) // one move left
  147. {
  148. cFlashes = 4; // flash this many times
  149. if (idTimer != 0)
  150. KillTimer(hWnd, FLASH_TIMER);
  151. idTimer = SetTimer(hWnd, FLASH_TIMER, FLASH_INTERVAL, NULL);
  152. }
  153. return;
  154. }
  155. /* We've tried everything. There are no more legal moves. */
  156. cheatloselabel:
  157. cUndo = 0;
  158. EnableMenuItem(GetMenu(hWnd), IDM_UNDO, MF_GRAYED);
  159. DialogBox(hInst, TEXT("YouLose"), hWnd, YouLoseDlg);
  160. gamenumber = 0; // cancel mouse activity
  161. bCheating = FALSE;
  162. }
  163. /****************************************************************************
  164. FindLastPos
  165. returns position of last card in column, or EMPTY if column is empty.
  166. ****************************************************************************/
  167. UINT FindLastPos(UINT col)
  168. {
  169. UINT pos = 0;
  170. if (col > 9)
  171. return EMPTY;
  172. while (card[col][pos] != EMPTY)
  173. pos++;
  174. pos--;
  175. return pos;
  176. }
  177. /******************************************************************************
  178. UpdateLossCount
  179. If game is lost, update statistics.
  180. ******************************************************************************/
  181. VOID UpdateLossCount()
  182. {
  183. int cLifetimeLosses; // includes .ini stats
  184. int wStreak, wSType; // streak length and type
  185. int wLosses; // record loss streak
  186. LONG lRegResult; // used to store return code from registry call
  187. // repeats and negative (unwinnable) games don't count
  188. if ((gamenumber > 0) && (gamenumber != oldgamenumber))
  189. {
  190. lRegResult = REGOPEN
  191. if (ERROR_SUCCESS == lRegResult)
  192. {
  193. cLifetimeLosses = GetInt(pszLost, 0);
  194. cLifetimeLosses++;
  195. cLosses++;
  196. cGames++;
  197. SetInt(pszLost, cLifetimeLosses);
  198. wSType = GetInt(pszSType, WON);
  199. if (wSType == WON)
  200. {
  201. SetInt(pszSType, LOST);
  202. wStreak = 1;
  203. SetInt(pszStreak, wStreak);
  204. }
  205. else
  206. {
  207. wStreak = GetInt(pszStreak, 0);
  208. wStreak++;
  209. SetInt(pszStreak, wStreak);
  210. }
  211. wLosses = GetInt(pszLosses, 0);
  212. if (wLosses < wStreak) // if new record
  213. {
  214. wLosses = wStreak;
  215. SetInt(pszLosses, wLosses);
  216. }
  217. REGCLOSE
  218. }
  219. }
  220. oldgamenumber = gamenumber;
  221. }
  222. /******************************************************************************
  223. KeyboardInput
  224. Handles keyboard input from the main message loop. Only digits are considered.
  225. This function works by simulating mouse clicks for each digit pressed.
  226. Note that when you have selected a card in a free cell, but you want to
  227. select another card, you press '0' again. This function sends (not posts,
  228. sends so that bMessages can be turned off) a mouse click to deselect that
  229. card, and then looks if there is another card in free cells to the right,
  230. and if so, selects it.
  231. ******************************************************************************/
  232. VOID KeyboardInput(HWND hWnd, UINT keycode)
  233. {
  234. UINT x, y;
  235. UINT col = TOPROW;
  236. UINT pos = 0;
  237. BOOL bSave; // save status of bMessages;
  238. CARD c;
  239. if (!isdigit(keycode))
  240. return;
  241. switch (keycode) {
  242. case '0': // free cell
  243. if (wMouseMode == FROM) // select a card to transfer
  244. {
  245. for (pos = 0; pos < 4; pos++)
  246. if (card[TOPROW][pos] != EMPTY)
  247. break;
  248. if (pos == 4) // no card to select
  249. return;
  250. }
  251. else // transfer TO free cell
  252. {
  253. if (wFromCol == TOPROW) // pick new free cell
  254. {
  255. /* Turn off messages so deselection moves don't complain
  256. if there is only one move left. */
  257. bSave = bMessages;
  258. bMessages = FALSE;
  259. /* deselect current selection */
  260. Card2Point(TOPROW, wFromPos, &x, &y);
  261. SendMessage(hWnd, WM_LBUTTONDOWN, 0,
  262. MAKELONG((WORD)x, (WORD)y));
  263. /* find next non-empty free cell */
  264. for (pos = wFromPos+1; pos < 4; pos++)
  265. {
  266. if (card[TOPROW][pos] != EMPTY)
  267. break;
  268. }
  269. bMessages = bSave;
  270. if (pos == 4) // none found, so leave deselected
  271. return;
  272. }
  273. else // transfer from a column, not TOPROW
  274. {
  275. for (pos = 0; pos < 4; pos++)
  276. if (card[TOPROW][pos] == EMPTY)
  277. break;
  278. if (pos == 4) // no empty freecells
  279. pos = 0; // force an error message
  280. }
  281. }
  282. break;
  283. case '9': // home cell
  284. if (wMouseMode == FROM) // can't move from home cell
  285. return;
  286. c = card[wFromCol][wFromPos];
  287. pos = homesuit[SUIT(c)];
  288. if (pos == EMPTY) // no home suit so can't do anything
  289. pos = 4; // force error
  290. break;
  291. default: // columns 1 to 8
  292. col = keycode - '0';
  293. break;
  294. }
  295. if (col == wFromCol && wMouseMode == TO && col > 0 && col < 9 &&
  296. card[col][1] != EMPTY)
  297. {
  298. bFlipping = (BOOL) SetTimer(hWnd, FLIP_TIMER, FLIP_INTERVAL, NULL);
  299. }
  300. if (bFlipping)
  301. {
  302. hFlipCursor = SetCursor(LoadCursor(NULL, IDC_WAIT));
  303. ShowCursor(TRUE);
  304. Flip(hWnd); // do first card manually
  305. }
  306. else
  307. {
  308. Card2Point(col, pos, &x, &y);
  309. PostMessage(hWnd, WM_LBUTTONDOWN, 0,
  310. MAKELONG((WORD)x, (WORD)y));
  311. }
  312. }
  313. /******************************************************************************
  314. Flash
  315. This function is called by the FLASH_TIMER to flash main window.
  316. ******************************************************************************/
  317. VOID Flash(HWND hWnd)
  318. {
  319. FlashWindow(hWnd, TRUE);
  320. cFlashes--;
  321. if (cFlashes <= 0)
  322. {
  323. FlashWindow(hWnd, FALSE);
  324. KillTimer(hWnd, FLASH_TIMER);
  325. idTimer = 0;
  326. }
  327. }
  328. /******************************************************************************
  329. Flip
  330. This function is called by the FLIP_TIMER to flip cards through in one
  331. column. It is used for keyboard players who want to reveal hidden cards.
  332. ******************************************************************************/
  333. VOID Flip(HWND hWnd)
  334. {
  335. HDC hDC;
  336. UINT x, y;
  337. static UINT pos = 0;
  338. hDC = GetDC(hWnd);
  339. DrawCard(hDC, wFromCol, pos, card[wFromCol][pos], FACEUP);
  340. pos++;
  341. if (card[wFromCol][pos] == EMPTY)
  342. {
  343. pos = 0;
  344. KillTimer(hWnd, FLIP_TIMER);
  345. bFlipping = FALSE;
  346. ShowCursor(FALSE);
  347. SetCursor(hFlipCursor);
  348. /* cancel move */
  349. Card2Point(wFromCol, pos, &x, &y);
  350. PostMessage(hWnd, WM_LBUTTONDOWN, 0,
  351. MAKELONG((WORD)x, (WORD)y));
  352. }
  353. ReleaseDC(hWnd, hDC);
  354. }
  355. /******************************************************************************
  356. Undo
  357. Undo last move
  358. ******************************************************************************/
  359. VOID Undo(HWND hWnd)
  360. {
  361. int i;
  362. if (cUndo == 0)
  363. return;
  364. SetCursor(LoadCursor(NULL, IDC_WAIT)); // set cursor to hourglass
  365. SetCapture(hWnd);
  366. ShowCursor(TRUE);
  367. for (i = cUndo-1; i >= 0; i--)
  368. {
  369. CARD c;
  370. int fcol, fpos, tcol, tpos;
  371. fcol = movelist[i].tcol;
  372. fpos = movelist[i].tpos;
  373. tcol = movelist[i].fcol;
  374. tpos = movelist[i].fpos;
  375. if (fcol != TOPROW && fcol == tcol) // no move so exit
  376. break;
  377. if (fcol != TOPROW)
  378. fpos = FindLastPos(fcol);
  379. if (tcol != TOPROW)
  380. tpos = FindLastPos(tcol) + 1;
  381. Glide(hWnd, fcol, fpos, tcol, tpos); // send the card on its way
  382. c = card[fcol][fpos];
  383. if (fcol == TOPROW && fpos > 3) // if from home cell
  384. {
  385. wCardCount++;
  386. DisplayCardCount(hWnd); // update display
  387. home[SUIT(c)]--;
  388. if (VALUE(c) == ACE)
  389. {
  390. card[fcol][fpos] = EMPTY;
  391. homesuit[SUIT(c)] = EMPTY;
  392. }
  393. else
  394. {
  395. card[fcol][fpos] -= 4;
  396. }
  397. }
  398. else
  399. card[fcol][fpos] = EMPTY;
  400. card[tcol][tpos] = c;
  401. }
  402. cUndo = 0;
  403. EnableMenuItem(GetMenu(hWnd), IDM_UNDO, MF_GRAYED);
  404. ShowCursor(FALSE);
  405. SetCursor(LoadCursor(NULL, IDC_ARROW));
  406. ReleaseCapture();
  407. }