diff options
Diffstat (limited to '')
-rw-r--r-- | freecell.c | 484 |
1 files changed, 0 insertions, 484 deletions
diff --git a/freecell.c b/freecell.c deleted file mode 100644 index b7f6378..0000000 --- a/freecell.c +++ /dev/null @@ -1,484 +0,0 @@ -/* Copyright (C) 2019 C. McEnroe <june@causal.agency> - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - */ - -#include <SDL.h> -#include <stdbool.h> -#include <stdio.h> -#include <stdlib.h> -#include <time.h> - -#include "asset.h" -#include "cards.h" -#include "layout.h" -#include "stack.h" - -enum { - Foundation1, - Foundation2, - Foundation3, - Foundation4, - Cell1, - Cell2, - Cell3, - Cell4, - Tableau1, - Tableau2, - Tableau3, - Tableau4, - Tableau5, - Tableau6, - Tableau7, - Tableau8, - StacksLen, -}; - -static struct Stack stacks[StacksLen]; - -static struct { - bool avail; - uint dst; - uint src; -} undo; - -static uint kingFace = Cards_KingRight; - -static void gameDeal(void) { - for (uint i = 0; i < StacksLen; ++i) { - stackClear(&stacks[i]); - } - struct Stack deck = {0}; - for (Card i = 1; i <= 52; ++i) { - stackPush(&deck, i); - } - stackShuffle(&deck); - for (uint i = Tableau1; i <= Tableau8; ++i) { - stackMoveTo(&stacks[i], &deck, (i < Tableau5 ? 7 : 6)); - } - undo.avail = false; - kingFace = Cards_KingRight; -} - -static bool gameWin(void) { - for (uint i = Foundation1; i <= Foundation4; ++i) { - if (stacks[i].len != 13) return false; - } - return true; -} - -static bool gameFind(uint *stack, Card card) { - for (*stack = 0; *stack < StacksLen; ++*stack) { - for (uint i = 0; i < stacks[*stack].len; ++i) { - if (stacks[*stack].cards[i] == card) return true; - } - } - return false; -} - -static bool gameAvail(Card card) { - uint stack; - assert(gameFind(&stack, card)); - if (stack <= Foundation4) return false; - return card == stackTop(&stacks[stack]); -} - -static bool gameMove(uint dst, Card card) { - uint src; - assert(gameFind(&src, card)); - Card top = stackTop(&stacks[dst]); - - if (src == dst) return false; - if (dst >= Cell1 && dst <= Cell4) { - if (stacks[dst].len) return false; - kingFace = Cards_KingLeft; - } - if (dst >= Foundation1 && dst <= Foundation4) { - if (!top && cardRank(card) != Cards_A) return false; - if (top && cardSuit(card) != cardSuit(top)) return false; - if (top && cardRank(card) != cardRank(top) + 1) return false; - kingFace = Cards_KingRight; - } - if (dst >= Tableau1 && dst <= Tableau8) { - if (top && cardColor(card) == cardColor(top)) return false; - if (top && cardRank(card) != cardRank(top) - 1) return false; - } - - undo.dst = src; - undo.src = dst; - undo.avail = true; - stackPush(&stacks[dst], stackPop(&stacks[src])); - return true; -} - -static bool gameUndo(void) { - if (!undo.avail) return false; - stackPush(&stacks[undo.dst], stackPop(&stacks[undo.src])); - undo.avail = false; - return true; -} - -static bool gameAuto(void) { - Card min[2] = { Cards_K, Cards_K }; - for (uint i = Cell1; i <= Tableau8; ++i) { - for (uint j = 0; j < stacks[i].len; ++j) { - Card card = stacks[i].cards[j]; - if (cardRank(card) < min[cardColor(card)]) { - min[cardColor(card)] = cardRank(card); - } - } - } - for (uint i = Cell1; i <= Tableau8; ++i) { - Card card = stackTop(&stacks[i]); - if (!card) continue; - if (cardRank(card) > Cards_2) { - if (min[!cardColor(card)] < cardRank(card)) continue; - } - for (uint dst = Foundation1; dst <= Foundation4; ++dst) { - if (gameMove(dst, card)) return true; - } - } - return false; -} - -enum { - CardWidth = Cards_CardWidth, - CardHeight = Cards_CardHeight, - - CellX = 0, - CellY = 0, - - KingMarginX = 13, - KingX = CellX + 4 * CardWidth + KingMarginX, - KingY = 18, - KingPadX = 3, - KingPadY = 3, - KingWidth = Cards_KingWidth + 2 * KingPadX, - KingHeight = Cards_KingHeight + 2 * KingPadY, - - FoundationX = KingX + KingWidth + KingMarginX, - FoundationY = CellY, - - StackMarginX = 7, - StackMarginY = 10, - - TableauX = StackMarginX, - TableauY = CellY + CardHeight + StackMarginY, - FanDownDeltaY = 17, - - KingWinMarginX = 10, - KingWinMarginY = 10, - KingWinX = KingWinMarginX, - KingWinY = CellY + CardHeight + KingWinMarginY, - KingWinWidth = 320, - KingWinHeight = 320, - - WindowWidth = 8 * CardWidth + 9 * StackMarginX + 1, - WindowHeight = KingWinY + KingWinHeight + KingWinMarginY, -}; - -static const struct Style FanDown = { 1, 0, 0, 0, FanDownDeltaY }; - -static struct SDL_Rect stackRects[StacksLen]; -static struct Layout layout; -static struct List reveal; - -static void updateLayout(void) { - layoutClear(&layout); - - SDL_Rect cell = { CellX, CellY, CardWidth, CardHeight }; - for (uint i = Cell1; i <= Cell4; ++i) { - stackRects[i] = cell; - layoutStack(&layout, &cell, &stacks[i], &Flat); - cell.x += CardWidth; - } - - SDL_Rect found = { FoundationX, FoundationY, CardWidth, CardHeight }; - for (uint i = Foundation1; i <= Foundation4; ++i) { - stackRects[i] = found; - layoutStack(&layout, &found, &stacks[i], &Flat); - found.x += CardWidth; - } - - SDL_Rect table = { TableauX, TableauY, CardWidth, CardHeight }; - for (uint i = Tableau1; i <= Tableau8; ++i) { - stackRects[i] = table; - stackRects[i].h = WindowHeight; - SDL_Rect rect = table; - layoutStack(&layout, &rect, &stacks[i], &FanDown); - table.x += CardWidth + StackMarginX; - } -} - -static void revealRank(Card rank) { - for (uint i = 0; i < layout.main.len; ++i) { - struct Item item = layout.main.items[i]; - if (cardRank(item.card) != rank) continue; - listPush(&reveal, &item.rect, item.card); - } -} - -static bool keyDown(SDL_KeyboardEvent key) { - switch (key.keysym.sym) { - case SDLK_F2: gameDeal(); return true; - case SDLK_BACKSPACE: return gameUndo(); - } - if (key.repeat) return false; - switch (key.keysym.sym) { - break; case SDLK_a: revealRank(Cards_A); - break; case SDLK_2: revealRank(Cards_2); - break; case SDLK_3: revealRank(Cards_3); - break; case SDLK_4: revealRank(Cards_4); - break; case SDLK_5: revealRank(Cards_5); - break; case SDLK_6: revealRank(Cards_6); - break; case SDLK_7: revealRank(Cards_7); - break; case SDLK_8: revealRank(Cards_8); - break; case SDLK_9: revealRank(Cards_9); - break; case SDLK_1: revealRank(Cards_10); - break; case SDLK_0: revealRank(Cards_10); - break; case SDLK_j: revealRank(Cards_J); - break; case SDLK_q: revealRank(Cards_Q); - break; case SDLK_k: revealRank(Cards_K); - break; default: return false; - } - return true; -} - -static bool keyUp(SDL_KeyboardEvent key) { - (void)key; - if (!reveal.len) return false; - listClear(&reveal); - return true; -} - -static bool mouseButtonDown(SDL_MouseButtonEvent button) { - if (button.button != SDL_BUTTON_RIGHT) return false; - struct SDL_Point point = { button.x, button.y }; - struct Item *item = listFind(&layout.main, &point); - if (!item) return false; - listPush(&reveal, &item->rect, item->card); - return true; -} - -static bool mouseButtonUp(SDL_MouseButtonEvent button) { - struct SDL_Point point = { button.x, button.y }; - - if (button.button == SDL_BUTTON_RIGHT) { - if (!reveal.len) return false; - listClear(&reveal); - return true; - } - - if (button.clicks % 2 == 0) { - Card card = layout.dragItem.card; - if (!card) { - struct Item *item = listFind(&layout.main, &point); - if (!item) return false; - card = item->card; - } - if (!gameAvail(card)) return false; - for (uint dst = Cell1; dst <= Cell4; ++dst) { - if (gameMove(dst, card)) break; - } - layout.dragItem.card = 0; - return true; - } - - if (layout.dragItem.card) { - for (uint dst = 0; dst < StacksLen; ++dst) { - if (SDL_PointInRect(&point, &stackRects[dst])) { - if (gameMove(dst, layout.dragItem.card)) break; - } - } - layout.dragItem.card = 0; - return true; - } - - struct Item *item = listFind(&layout.main, &point); - if (!item) return false; - if (!gameAvail(item->card)) return false; - layout.dragItem = *item; - return true; -} - -static SDL_Renderer *render; - -static void renderOutline(SDL_Rect rect, bool out) { - int right = rect.x + rect.w - 1; - int bottom = rect.y + rect.h - 1; - SDL_Point topLeft[3] = { - { rect.x, bottom - 1 }, - { rect.x, rect.y }, - { right - 1, rect.y }, - }; - SDL_Point bottomRight[3] = { - { rect.x + 1, bottom }, - { right, bottom }, - { right, rect.y + 1 }, - }; - SDL_SetRenderDrawColor(render, 0x00, 0x00, 0x00, 0xFF); - SDL_RenderDrawLines(render, out ? bottomRight : topLeft, 3); - SDL_SetRenderDrawColor(render, 0x00, 0xFF, 0x00, 0xFF); - SDL_RenderDrawLines(render, out ? topLeft : bottomRight, 3); -} - -static void renderOutlines(void) { - for (uint i = Foundation1; i <= Cell4; ++i) { - renderOutline(stackRects[i], false); - } -} - -static void renderKing(SDL_Texture *textures[]) { - SDL_Rect box = { KingX, KingY, KingWidth, KingHeight }; - renderOutline(box, true); - if (gameWin()) { - SDL_Rect king = { KingWinX, KingWinY, KingWinWidth, KingWinHeight }; - SDL_RenderCopy(render, textures[Cards_KingWin], NULL, &king); - } else { - SDL_Rect king = { - KingX + KingPadX, KingY + KingPadY, - Cards_KingWidth, Cards_KingHeight, - }; - SDL_RenderCopy(render, textures[kingFace], NULL, &king); - } -} - -static void renderList(SDL_Texture *textures[], const struct List *list) { - for (uint i = 0; i < list->len; ++i) { - SDL_RenderCopy( - render, textures[list->items[i].card], - NULL, &list->items[i].rect - ); - } -} - -static void err(const char *title) { - int error = SDL_ShowSimpleMessageBox( - SDL_MESSAGEBOX_ERROR, title, SDL_GetError(), NULL - ); - if (error) fprintf(stderr, "%s\n", SDL_GetError()); - exit(EXIT_FAILURE); -} - -int main(void) { - if (SDL_Init(SDL_INIT_VIDEO) < 0) err("SDL_Init"); - atexit(SDL_Quit); - - struct Paths paths; - if (assetPaths(&paths) < 0) err("SDL_GetPrefPath"); - - bool kings = false; - struct { - SDL_Surface *cards[Cards_Empty]; - SDL_Surface *kings[Cards_FreeCellCount]; - } surfaces; - - SDL_RWops *rw = assetOpenCards(&paths); - if (!rw) return EXIT_FAILURE; - int error = Cards_LoadCards( - surfaces.cards, Cards_Empty, - rw, Cards_AlphaCorners | Cards_BlackBorders - ); - if (error) err("Cards_LoadCards"); - SDL_RWclose(rw); - - rw = assetOpenFreeCell(&paths); - if (rw) { - kings = true; - int error = Cards_LoadFreeCell( - surfaces.kings, Cards_FreeCellCount, - rw, Cards_ColorKey - ); - if (error) err("Cards_LoadFreeCell"); - SDL_RWclose(rw); - } - - SDL_Window *window; - error = SDL_CreateWindowAndRenderer( - WindowWidth, WindowHeight, SDL_WINDOW_ALLOW_HIGHDPI, - &window, &render - ); - if (error) err("SDL_CreateWindowAndRenderer"); - SDL_SetWindowTitle(window, "FreeCell"); - - SDL_RenderSetIntegerScale(render, SDL_TRUE); - SDL_RenderSetLogicalSize(render, WindowWidth, WindowHeight); - - SDL_Texture *textures[Cards_Empty] = {0}; - SDL_Texture *inverted[Cards_Empty] = {0}; - SDL_Texture *kingTextures[Cards_FreeCellCount] = {0}; - - for (uint i = 0; i < Cards_Empty; ++i) { - if (!surfaces.cards[i]) continue; - - textures[i] = SDL_CreateTextureFromSurface(render, surfaces.cards[i]); - if (!textures[i]) err("SDL_CreateTextureFromSurface"); - - error = Cards_InvertSurface(surfaces.cards[i]); - if (error) err("Cards_InvertSurface"); - - inverted[i] = SDL_CreateTextureFromSurface(render, surfaces.cards[i]); - if (!inverted[i]) err("SDL_CreateTextureFromSurface"); - - SDL_FreeSurface(surfaces.cards[i]); - } - - if (kings) { - for (uint i = 0; i < Cards_FreeCellCount; ++i) { - if (!surfaces.kings[i]) continue; - kingTextures[i] = - SDL_CreateTextureFromSurface(render, surfaces.kings[i]); - if (!kingTextures[i]) err("SDL_CreateTextureFromSurface"); - SDL_FreeSurface(surfaces.kings[i]); - } - } - - srand(time(NULL)); - gameDeal(); - - for (;;) { - updateLayout(); - - SDL_SetRenderDrawColor(render, 0x00, 0xAA, 0x55, 0xFF); - SDL_RenderClear(render); - renderOutlines(); - if (kings) renderKing(kingTextures); - renderList(textures, &layout.main); - renderList(inverted, &layout.drag); - renderList(textures, &reveal); - SDL_RenderPresent(render); - - // TODO: Add more than a frame delay between automatic moves? - if (gameAuto()) continue; - - SDL_Event event; - for (;;) { - SDL_WaitEvent(&event); - if (event.type == SDL_QUIT) { - goto quit; - } else if (event.type == SDL_KEYDOWN) { - if (keyDown(event.key)) break; - } else if (event.type == SDL_KEYUP) { - if (keyUp(event.key)) break; - } else if (event.type == SDL_MOUSEBUTTONDOWN) { - if (mouseButtonDown(event.button)) break; - } else if (event.type == SDL_MOUSEBUTTONUP) { - if (mouseButtonUp(event.button)) break; - } - } - } - -quit: - SDL_Quit(); -} |