CARDS.DLL loader for SDL
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.

freecell.c 12KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484
  1. /* Copyright (C) 2019 C. McEnroe <june@causal.agency>
  2. *
  3. * This program is free software: you can redistribute it and/or modify
  4. * it under the terms of the GNU Affero General Public License as published by
  5. * the Free Software Foundation, either version 3 of the License, or
  6. * (at your option) any later version.
  7. *
  8. * This program is distributed in the hope that it will be useful,
  9. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  10. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  11. * GNU Affero General Public License for more details.
  12. *
  13. * You should have received a copy of the GNU Affero General Public License
  14. * along with this program. If not, see <http://www.gnu.org/licenses/>.
  15. */
  16. #include <SDL.h>
  17. #include <stdbool.h>
  18. #include <stdio.h>
  19. #include <stdlib.h>
  20. #include <time.h>
  21. #include "asset.h"
  22. #include "cards.h"
  23. #include "layout.h"
  24. #include "stack.h"
  25. enum {
  26. Foundation1,
  27. Foundation2,
  28. Foundation3,
  29. Foundation4,
  30. Cell1,
  31. Cell2,
  32. Cell3,
  33. Cell4,
  34. Tableau1,
  35. Tableau2,
  36. Tableau3,
  37. Tableau4,
  38. Tableau5,
  39. Tableau6,
  40. Tableau7,
  41. Tableau8,
  42. StacksLen,
  43. };
  44. static struct Stack stacks[StacksLen];
  45. static struct {
  46. bool avail;
  47. uint dst;
  48. uint src;
  49. } undo;
  50. static uint kingFace = Cards_KingRight;
  51. static void gameDeal(void) {
  52. for (uint i = 0; i < StacksLen; ++i) {
  53. stackClear(&stacks[i]);
  54. }
  55. struct Stack deck = {0};
  56. for (Card i = 1; i <= 52; ++i) {
  57. stackPush(&deck, i);
  58. }
  59. stackShuffle(&deck);
  60. for (uint i = Tableau1; i <= Tableau8; ++i) {
  61. stackMoveTo(&stacks[i], &deck, (i < Tableau5 ? 7 : 6));
  62. }
  63. undo.avail = false;
  64. kingFace = Cards_KingRight;
  65. }
  66. static bool gameWin(void) {
  67. for (uint i = Foundation1; i <= Foundation4; ++i) {
  68. if (stacks[i].len != 13) return false;
  69. }
  70. return true;
  71. }
  72. static bool gameFind(uint *stack, Card card) {
  73. for (*stack = 0; *stack < StacksLen; ++*stack) {
  74. for (uint i = 0; i < stacks[*stack].len; ++i) {
  75. if (stacks[*stack].cards[i] == card) return true;
  76. }
  77. }
  78. return false;
  79. }
  80. static bool gameAvail(Card card) {
  81. uint stack;
  82. assert(gameFind(&stack, card));
  83. if (stack <= Foundation4) return false;
  84. return card == stackTop(&stacks[stack]);
  85. }
  86. static bool gameMove(uint dst, Card card) {
  87. uint src;
  88. assert(gameFind(&src, card));
  89. Card top = stackTop(&stacks[dst]);
  90. if (src == dst) return false;
  91. if (dst >= Cell1 && dst <= Cell4) {
  92. if (stacks[dst].len) return false;
  93. kingFace = Cards_KingLeft;
  94. }
  95. if (dst >= Foundation1 && dst <= Foundation4) {
  96. if (!top && cardRank(card) != Cards_A) return false;
  97. if (top && cardSuit(card) != cardSuit(top)) return false;
  98. if (top && cardRank(card) != cardRank(top) + 1) return false;
  99. kingFace = Cards_KingRight;
  100. }
  101. if (dst >= Tableau1 && dst <= Tableau8) {
  102. if (top && cardColor(card) == cardColor(top)) return false;
  103. if (top && cardRank(card) != cardRank(top) - 1) return false;
  104. }
  105. undo.dst = src;
  106. undo.src = dst;
  107. undo.avail = true;
  108. stackPush(&stacks[dst], stackPop(&stacks[src]));
  109. return true;
  110. }
  111. static bool gameUndo(void) {
  112. if (!undo.avail) return false;
  113. stackPush(&stacks[undo.dst], stackPop(&stacks[undo.src]));
  114. undo.avail = false;
  115. return true;
  116. }
  117. static bool gameAuto(void) {
  118. Card min[2] = { Cards_K, Cards_K };
  119. for (uint i = Cell1; i <= Tableau8; ++i) {
  120. for (uint j = 0; j < stacks[i].len; ++j) {
  121. Card card = stacks[i].cards[j];
  122. if (cardRank(card) < min[cardColor(card)]) {
  123. min[cardColor(card)] = cardRank(card);
  124. }
  125. }
  126. }
  127. for (uint i = Cell1; i <= Tableau8; ++i) {
  128. Card card = stackTop(&stacks[i]);
  129. if (!card) continue;
  130. if (cardRank(card) > Cards_2) {
  131. if (min[!cardColor(card)] < cardRank(card)) continue;
  132. }
  133. for (uint dst = Foundation1; dst <= Foundation4; ++dst) {
  134. if (gameMove(dst, card)) return true;
  135. }
  136. }
  137. return false;
  138. }
  139. enum {
  140. CardWidth = Cards_CardWidth,
  141. CardHeight = Cards_CardHeight,
  142. CellX = 0,
  143. CellY = 0,
  144. KingMarginX = 13,
  145. KingX = CellX + 4 * CardWidth + KingMarginX,
  146. KingY = 18,
  147. KingPadX = 3,
  148. KingPadY = 3,
  149. KingWidth = Cards_KingWidth + 2 * KingPadX,
  150. KingHeight = Cards_KingHeight + 2 * KingPadY,
  151. FoundationX = KingX + KingWidth + KingMarginX,
  152. FoundationY = CellY,
  153. StackMarginX = 7,
  154. StackMarginY = 10,
  155. TableauX = StackMarginX,
  156. TableauY = CellY + CardHeight + StackMarginY,
  157. FanDownDeltaY = 17,
  158. KingWinMarginX = 10,
  159. KingWinMarginY = 10,
  160. KingWinX = KingWinMarginX,
  161. KingWinY = CellY + CardHeight + KingWinMarginY,
  162. KingWinWidth = 320,
  163. KingWinHeight = 320,
  164. WindowWidth = 8 * CardWidth + 9 * StackMarginX + 1,
  165. WindowHeight = KingWinY + KingWinHeight + KingWinMarginY,
  166. };
  167. static const struct Style FanDown = { 1, 0, 0, 0, FanDownDeltaY };
  168. static struct SDL_Rect stackRects[StacksLen];
  169. static struct Layout layout;
  170. static struct List reveal;
  171. static void updateLayout(void) {
  172. layoutClear(&layout);
  173. SDL_Rect cell = { CellX, CellY, CardWidth, CardHeight };
  174. for (uint i = Cell1; i <= Cell4; ++i) {
  175. stackRects[i] = cell;
  176. layoutStack(&layout, &cell, &stacks[i], &Flat);
  177. cell.x += CardWidth;
  178. }
  179. SDL_Rect found = { FoundationX, FoundationY, CardWidth, CardHeight };
  180. for (uint i = Foundation1; i <= Foundation4; ++i) {
  181. stackRects[i] = found;
  182. layoutStack(&layout, &found, &stacks[i], &Flat);
  183. found.x += CardWidth;
  184. }
  185. SDL_Rect table = { TableauX, TableauY, CardWidth, CardHeight };
  186. for (uint i = Tableau1; i <= Tableau8; ++i) {
  187. stackRects[i] = table;
  188. stackRects[i].h = WindowHeight;
  189. SDL_Rect rect = table;
  190. layoutStack(&layout, &rect, &stacks[i], &FanDown);
  191. table.x += CardWidth + StackMarginX;
  192. }
  193. }
  194. static void revealRank(Card rank) {
  195. for (uint i = 0; i < layout.main.len; ++i) {
  196. struct Item item = layout.main.items[i];
  197. if (cardRank(item.card) != rank) continue;
  198. listPush(&reveal, &item.rect, item.card);
  199. }
  200. }
  201. static bool keyDown(SDL_KeyboardEvent key) {
  202. switch (key.keysym.sym) {
  203. case SDLK_F2: gameDeal(); return true;
  204. case SDLK_BACKSPACE: return gameUndo();
  205. }
  206. if (key.repeat) return false;
  207. switch (key.keysym.sym) {
  208. break; case SDLK_a: revealRank(Cards_A);
  209. break; case SDLK_2: revealRank(Cards_2);
  210. break; case SDLK_3: revealRank(Cards_3);
  211. break; case SDLK_4: revealRank(Cards_4);
  212. break; case SDLK_5: revealRank(Cards_5);
  213. break; case SDLK_6: revealRank(Cards_6);
  214. break; case SDLK_7: revealRank(Cards_7);
  215. break; case SDLK_8: revealRank(Cards_8);
  216. break; case SDLK_9: revealRank(Cards_9);
  217. break; case SDLK_1: revealRank(Cards_10);
  218. break; case SDLK_0: revealRank(Cards_10);
  219. break; case SDLK_j: revealRank(Cards_J);
  220. break; case SDLK_q: revealRank(Cards_Q);
  221. break; case SDLK_k: revealRank(Cards_K);
  222. break; default: return false;
  223. }
  224. return true;
  225. }
  226. static bool keyUp(SDL_KeyboardEvent key) {
  227. (void)key;
  228. if (!reveal.len) return false;
  229. listClear(&reveal);
  230. return true;
  231. }
  232. static bool mouseButtonDown(SDL_MouseButtonEvent button) {
  233. if (button.button != SDL_BUTTON_RIGHT) return false;
  234. struct SDL_Point point = { button.x, button.y };
  235. struct Item *item = listFind(&layout.main, &point);
  236. if (!item) return false;
  237. listPush(&reveal, &item->rect, item->card);
  238. return true;
  239. }
  240. static bool mouseButtonUp(SDL_MouseButtonEvent button) {
  241. struct SDL_Point point = { button.x, button.y };
  242. if (button.button == SDL_BUTTON_RIGHT) {
  243. if (!reveal.len) return false;
  244. listClear(&reveal);
  245. return true;
  246. }
  247. if (button.clicks % 2 == 0) {
  248. Card card = layout.dragItem.card;
  249. if (!card) {
  250. struct Item *item = listFind(&layout.main, &point);
  251. if (!item) return false;
  252. card = item->card;
  253. }
  254. if (!gameAvail(card)) return false;
  255. for (uint dst = Cell1; dst <= Cell4; ++dst) {
  256. if (gameMove(dst, card)) break;
  257. }
  258. layout.dragItem.card = 0;
  259. return true;
  260. }
  261. if (layout.dragItem.card) {
  262. for (uint dst = 0; dst < StacksLen; ++dst) {
  263. if (SDL_PointInRect(&point, &stackRects[dst])) {
  264. if (gameMove(dst, layout.dragItem.card)) break;
  265. }
  266. }
  267. layout.dragItem.card = 0;
  268. return true;
  269. }
  270. struct Item *item = listFind(&layout.main, &point);
  271. if (!item) return false;
  272. if (!gameAvail(item->card)) return false;
  273. layout.dragItem = *item;
  274. return true;
  275. }
  276. static SDL_Renderer *render;
  277. static void renderOutline(SDL_Rect rect, bool out) {
  278. int right = rect.x + rect.w - 1;
  279. int bottom = rect.y + rect.h - 1;
  280. SDL_Point topLeft[3] = {
  281. { rect.x, bottom - 1 },
  282. { rect.x, rect.y },
  283. { right - 1, rect.y },
  284. };
  285. SDL_Point bottomRight[3] = {
  286. { rect.x + 1, bottom },
  287. { right, bottom },
  288. { right, rect.y + 1 },
  289. };
  290. SDL_SetRenderDrawColor(render, 0x00, 0x00, 0x00, 0xFF);
  291. SDL_RenderDrawLines(render, out ? bottomRight : topLeft, 3);
  292. SDL_SetRenderDrawColor(render, 0x00, 0xFF, 0x00, 0xFF);
  293. SDL_RenderDrawLines(render, out ? topLeft : bottomRight, 3);
  294. }
  295. static void renderOutlines(void) {
  296. for (uint i = Foundation1; i <= Cell4; ++i) {
  297. renderOutline(stackRects[i], false);
  298. }
  299. }
  300. static void renderKing(SDL_Texture *textures[]) {
  301. SDL_Rect box = { KingX, KingY, KingWidth, KingHeight };
  302. renderOutline(box, true);
  303. if (gameWin()) {
  304. SDL_Rect king = { KingWinX, KingWinY, KingWinWidth, KingWinHeight };
  305. SDL_RenderCopy(render, textures[Cards_KingWin], NULL, &king);
  306. } else {
  307. SDL_Rect king = {
  308. KingX + KingPadX, KingY + KingPadY,
  309. Cards_KingWidth, Cards_KingHeight,
  310. };
  311. SDL_RenderCopy(render, textures[kingFace], NULL, &king);
  312. }
  313. }
  314. static void renderList(SDL_Texture *textures[], const struct List *list) {
  315. for (uint i = 0; i < list->len; ++i) {
  316. SDL_RenderCopy(
  317. render, textures[list->items[i].card],
  318. NULL, &list->items[i].rect
  319. );
  320. }
  321. }
  322. static void err(const char *title) {
  323. int error = SDL_ShowSimpleMessageBox(
  324. SDL_MESSAGEBOX_ERROR, title, SDL_GetError(), NULL
  325. );
  326. if (error) fprintf(stderr, "%s\n", SDL_GetError());
  327. exit(EXIT_FAILURE);
  328. }
  329. int main(void) {
  330. if (SDL_Init(SDL_INIT_VIDEO) < 0) err("SDL_Init");
  331. atexit(SDL_Quit);
  332. struct Paths paths;
  333. if (assetPaths(&paths) < 0) err("SDL_GetPrefPath");
  334. bool kings = false;
  335. struct {
  336. SDL_Surface *cards[Cards_Empty];
  337. SDL_Surface *kings[Cards_FreeCellCount];
  338. } surfaces;
  339. SDL_RWops *rw = assetOpenCards(&paths);
  340. if (!rw) return EXIT_FAILURE;
  341. int error = Cards_LoadCards(
  342. surfaces.cards, Cards_Empty,
  343. rw, Cards_AlphaCorners | Cards_BlackBorders
  344. );
  345. if (error) err("Cards_LoadCards");
  346. SDL_RWclose(rw);
  347. rw = assetOpenFreeCell(&paths);
  348. if (rw) {
  349. kings = true;
  350. int error = Cards_LoadFreeCell(
  351. surfaces.kings, Cards_FreeCellCount,
  352. rw, Cards_ColorKey
  353. );
  354. if (error) err("Cards_LoadFreeCell");
  355. SDL_RWclose(rw);
  356. }
  357. SDL_Window *window;
  358. error = SDL_CreateWindowAndRenderer(
  359. WindowWidth, WindowHeight, SDL_WINDOW_ALLOW_HIGHDPI,
  360. &window, &render
  361. );
  362. if (error) err("SDL_CreateWindowAndRenderer");
  363. SDL_SetWindowTitle(window, "FreeCell");
  364. SDL_RenderSetIntegerScale(render, SDL_TRUE);
  365. SDL_RenderSetLogicalSize(render, WindowWidth, WindowHeight);
  366. SDL_Texture *textures[Cards_Empty] = {0};
  367. SDL_Texture *inverted[Cards_Empty] = {0};
  368. SDL_Texture *kingTextures[Cards_FreeCellCount] = {0};
  369. for (uint i = 0; i < Cards_Empty; ++i) {
  370. if (!surfaces.cards[i]) continue;
  371. textures[i] = SDL_CreateTextureFromSurface(render, surfaces.cards[i]);
  372. if (!textures[i]) err("SDL_CreateTextureFromSurface");
  373. error = Cards_InvertSurface(surfaces.cards[i]);
  374. if (error) err("Cards_InvertSurface");
  375. inverted[i] = SDL_CreateTextureFromSurface(render, surfaces.cards[i]);
  376. if (!inverted[i]) err("SDL_CreateTextureFromSurface");
  377. SDL_FreeSurface(surfaces.cards[i]);
  378. }
  379. if (kings) {
  380. for (uint i = 0; i < Cards_FreeCellCount; ++i) {
  381. if (!surfaces.kings[i]) continue;
  382. kingTextures[i] =
  383. SDL_CreateTextureFromSurface(render, surfaces.kings[i]);
  384. if (!kingTextures[i]) err("SDL_CreateTextureFromSurface");
  385. SDL_FreeSurface(surfaces.kings[i]);
  386. }
  387. }
  388. srand(time(NULL));
  389. gameDeal();
  390. for (;;) {
  391. updateLayout();
  392. SDL_SetRenderDrawColor(render, 0x00, 0xAA, 0x55, 0xFF);
  393. SDL_RenderClear(render);
  394. renderOutlines();
  395. if (kings) renderKing(kingTextures);
  396. renderList(textures, &layout.main);
  397. renderList(inverted, &layout.drag);
  398. renderList(textures, &reveal);
  399. SDL_RenderPresent(render);
  400. // TODO: Add more than a frame delay between automatic moves?
  401. if (gameAuto()) continue;
  402. SDL_Event event;
  403. for (;;) {
  404. SDL_WaitEvent(&event);
  405. if (event.type == SDL_QUIT) {
  406. goto quit;
  407. } else if (event.type == SDL_KEYDOWN) {
  408. if (keyDown(event.key)) break;
  409. } else if (event.type == SDL_KEYUP) {
  410. if (keyUp(event.key)) break;
  411. } else if (event.type == SDL_MOUSEBUTTONDOWN) {
  412. if (mouseButtonDown(event.button)) break;
  413. } else if (event.type == SDL_MOUSEBUTTONUP) {
  414. if (mouseButtonUp(event.button)) break;
  415. }
  416. }
  417. }
  418. quit:
  419. SDL_Quit();
  420. }