aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorC. McEnroe <june@causal.agency>2019-08-22 15:43:24 -0400
committerC. McEnroe <june@causal.agency>2019-08-22 15:43:24 -0400
commitbeba76119fbc8e29bf22093bfa54316ad2591d30 (patch)
tree49a978628c83d61dffd55ded8580b35338db3a38
parentMark card functions inline (diff)
downloadcards-beba76119fbc8e29bf22093bfa54316ad2591d30.tar.gz
cards-beba76119fbc8e29bf22093bfa54316ad2591d30.zip
Remove games
-rw-r--r--.gitignore3
-rw-r--r--Makefile14
-rw-r--r--asset.h72
-rw-r--r--freecell.c484
-rw-r--r--layout.h110
-rw-r--r--sol.c393
-rw-r--r--stack.h108
7 files changed, 7 insertions, 1177 deletions
diff --git a/.gitignore b/.gitignore
index c15f5a2..0ca9d1f 100644
--- a/.gitignore
+++ b/.gitignore
@@ -5,5 +5,4 @@ FREECELL.EXE
SOL.EXE
config.mk
dump
-freecell
-sol
+example
diff --git a/Makefile b/Makefile
index 073f0be..8990dd0 100644
--- a/Makefile
+++ b/Makefile
@@ -2,18 +2,16 @@ CFLAGS += -std=c99 -Wall -Wextra -Wpedantic
include config.mk
-BINS = dump freecell sol
+BINS = dump
-all: $(BINS)
+all: ${BINS}
-$(BINS): cards.o
+${BINS}: cards.o
.o:
- $(CC) $(LDFLAGS) $< cards.o $(LDLIBS) -o $@
+ ${CC} ${LDFLAGS} $< cards.o ${LDLIBS} -o $@
-cards.o dump.o freecell.o sol.o: cards.h
-
-freecell.o sol.o: asset.h layout.h stack.h
+cards.o dump.o: cards.h
clean:
- rm -f $(BINS) *.o *.bmp
+ rm -f ${BINS} *.o *.bmp
diff --git a/asset.h b/asset.h
deleted file mode 100644
index cc37709..0000000
--- a/asset.h
+++ /dev/null
@@ -1,72 +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/>.
- */
-
-#ifndef ASSET_H
-#define ASSET_H
-
-#include <SDL_filesystem.h>
-#include <SDL_rwops.h>
-#include <stdio.h>
-#include <stdlib.h>
-
-struct Paths {
- char *base;
- char *pref;
-};
-
-static inline int assetPaths(struct Paths *paths) {
- paths->base = SDL_GetBasePath();
- if (!paths->base) return -1;
- paths->pref = SDL_GetPrefPath("Causal Agency", "Entertainment Pack");
- if (!paths->pref) return -1;
- return 0;
-}
-
-static inline SDL_RWops *
-assetOpen(const struct Paths *paths, const char *names[], size_t len) {
- const char *dirs[3] = { paths->pref, paths->base, "" };
- for (size_t i = 0; i < 3; ++i) {
- for (size_t j = 0; j < len; ++j) {
- char path[1024];
- snprintf(path, sizeof(path), "%s%s", dirs[i], names[j]);
- SDL_RWops *rw = SDL_RWFromFile(path, "rb");
- if (rw) return rw;
- }
- }
- return NULL;
-}
-
-static inline SDL_RWops *assetOpenCards(const struct Paths *paths) {
- const char *names[3] = { "CARDS.DLL", "SOL.EXE", "cards.dll" };
- SDL_RWops *rw = assetOpen(paths, names, 3);
- if (rw) return rw;
- char msg[4096];
- snprintf(
- msg, sizeof(msg), "%s or %s not found in:\n%s\n%s",
- names[0], names[1], paths->pref, paths->base
- );
- SDL_ShowSimpleMessageBox(
- SDL_MESSAGEBOX_ERROR, "Entertainment Pack", msg, NULL
- );
- return NULL;
-}
-
-static inline SDL_RWops *assetOpenFreeCell(const struct Paths *paths) {
- const char *names[2] = { "FREECELL.EXE", "freecell.exe" };
- return assetOpen(paths, names, 2);
-}
-
-#endif
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();
-}
diff --git a/layout.h b/layout.h
deleted file mode 100644
index cbff6df..0000000
--- a/layout.h
+++ /dev/null
@@ -1,110 +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/>.
- */
-
-#ifndef LAYOUT_H
-#define LAYOUT_H
-
-#include <SDL_rect.h>
-#include <assert.h>
-
-#include "stack.h"
-
-#ifndef LAYOUT_CAP
-#define LAYOUT_CAP 64
-#endif
-
-typedef unsigned uint;
-
-struct Item {
- SDL_Rect rect;
- Card card;
-};
-
-struct List {
- uint len;
- struct Item items[LAYOUT_CAP];
-};
-
-static inline void listClear(struct List *list) {
- list->len = 0;
-}
-
-static inline void
-listPush(struct List *list, const struct SDL_Rect *rect, Card card) {
- assert(list->len < LAYOUT_CAP);
- struct Item item = { *rect, card };
- list->items[list->len++] = item;
-}
-
-static inline struct Item *listFind(struct List *list, const SDL_Point *point) {
- for (uint i = list->len - 1; i < list->len; --i) {
- if (SDL_PointInRect(point, &list->items[i].rect)) {
- return &list->items[i];
- }
- }
- return NULL;
-}
-
-struct Layout {
- struct List main;
- struct List drag;
- struct Item dragItem;
-};
-
-static inline void layoutClear(struct Layout *layout) {
- listClear(&layout->main);
- listClear(&layout->drag);
-}
-
-struct Style {
- uint increment;
- int deltaXBack;
- int deltaYBack;
- int deltaXFront;
- int deltaYFront;
-};
-
-enum {
- SquareIncrement = 24 / 3,
- SquareDeltaX = 2,
- SquareDeltaY = 1,
-};
-static const struct Style Flat = { 1, 0, 0, 0, 0 };
-static const struct Style Square = {
- SquareIncrement, SquareDeltaX, SquareDeltaY, SquareDeltaX, SquareDeltaY,
-};
-
-static inline void
-layoutStack(
- struct Layout *layout, SDL_Rect *rect,
- const struct Stack *stack, const struct Style *style
-) {
- struct List *list = &layout->main;
- for (uint i = 0; i < stack->len; ++i) {
- Card card = stack->cards[i];
- if (card == layout->dragItem.card) {
- list = &layout->drag;
- *rect = layout->dragItem.rect;
- }
- listPush(list, rect, card);
- if ((i + 1) % style->increment == 0) {
- rect->x += (card > 0 ? style->deltaXFront : style->deltaXBack);
- rect->y += (card > 0 ? style->deltaYFront : style->deltaYBack);
- }
- }
-}
-
-#endif
diff --git a/sol.c b/sol.c
deleted file mode 100644
index cf643f2..0000000
--- a/sol.c
+++ /dev/null
@@ -1,393 +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"
-
-#define ARRAY_LEN(a) (sizeof(a) / sizeof((a)[0]))
-
-enum {
- Stock,
- Waste1,
- Waste3,
- Foundation1,
- Foundation2,
- Foundation3,
- Foundation4,
- Tableau1,
- Tableau2,
- Tableau3,
- Tableau4,
- Tableau5,
- Tableau6,
- Tableau7,
- StacksLen,
-};
-
-static struct Stack stacks[StacksLen];
-
-// TODO: Scoring method, score, time.
-static struct {
- uint draw;
-} game = {
- .draw = 3,
-};
-
-typedef void StackFn(struct Stack *dst, struct Stack *src, Uint8 len);
-
-static struct {
- StackFn *fn;
- uint dst;
- uint src;
- uint len;
-} undo;
-
-static void gameDo(StackFn *fn, uint dst, uint src, uint len) {
- undo.fn = fn;
- undo.dst = src;
- undo.src = dst;
- undo.len = len;
- fn(&stacks[dst], &stacks[src], len);
-}
-
-static bool gameUndo(void) {
- if (!undo.fn) return false;
- undo.fn(&stacks[undo.dst], &stacks[undo.src], undo.len);
- undo.fn = NULL;
- return true;
-}
-
-static void gameDeal(void) {
- undo.fn = NULL;
- for (uint i = 0; i < StacksLen; ++i) {
- stackClear(&stacks[i]);
- }
- for (Card i = 1; i <= 52; ++i) {
- stackPush(&stacks[Stock], -i);
- }
- stackShuffle(&stacks[Stock]);
- for (uint i = Tableau1; i <= Tableau7; ++i) {
- stackMoveTo(&stacks[i], &stacks[Stock], i - Tableau1);
- stackFlipTo(&stacks[i], &stacks[Stock], 1);
- }
-}
-
-static void gameDraw(void) {
- stackMoveTo(&stacks[Waste1], &stacks[Waste3], stacks[Waste3].len);
- if (stacks[Stock].len) {
- if (game.draw > 1) {
- gameDo(stackFlipTo, Waste3, Stock, game.draw);
- } else {
- gameDo(stackFlipTo, Waste1, Stock, 1);
- }
- } else {
- gameDo(stackFlipTo, Stock, Waste1, stacks[Waste1].len);
- }
-}
-
-static bool gameFind(uint *stack, uint *index, Card card) {
- for (*stack = 0; *stack < StacksLen; ++*stack) {
- for (*index = 0; *index < stacks[*stack].len; ++*index) {
- if (stacks[*stack].cards[*index] == card) return true;
- }
- }
- return false;
-}
-
-static bool gameAvail(Card card) {
- uint stack, index;
- if (card < 0) return false;
- if (!gameFind(&stack, &index, card)) return false;
-
- bool top = (card == stackTop(&stacks[stack]));
- if (stack == Waste1) {
- return top && !stacks[Waste3].len;
- } else if (stack == Waste3) {
- return top;
- } else if (stack >= Foundation1 && stack <= Foundation4) {
- return top;
- } else if (stack >= Tableau1 && stack <= Tableau7) {
- return true;
- } else {
- return false;
- }
-}
-
-static bool gameReveal(Card card) {
- uint stack, index;
- if (!gameFind(&stack, &index, card)) return false;
- if (stack < Tableau1 || stack > Tableau7) return false;
- if (card != stackTop(&stacks[stack])) return false;
- if (card > 0) return false;
- gameDo(stackFlipTo, stack, stack, 1);
- return true;
-}
-
-static bool gameMove(uint dest, Card card) {
- uint source, index;
- if (!gameFind(&source, &index, card)) return false;
- if (source == dest) return false;
-
- uint count = stacks[source].len - index;
- Card destTop = stackTop(&stacks[dest]);
-
- if (dest >= Foundation1 && dest <= Foundation4) {
- if (count > 1) return false;
- if (!destTop && cardRank(card) != Cards_A) return false;
- if (destTop && cardSuit(card) != cardSuit(destTop)) return false;
- if (destTop && cardRank(card) != cardRank(destTop) + 1) return false;
- gameDo(stackMoveTo, dest, source, 1);
- return true;
- }
-
- if (dest >= Tableau1 && dest <= Tableau7) {
- if (!destTop && cardRank(card) != Cards_K) return false;
- if (destTop && cardColor(card) == cardColor(destTop)) return false;
- if (destTop && cardRank(card) != cardRank(destTop) - 1) return false;
- gameDo(stackMoveTo, dest, source, count);
- return true;
- }
-
- return false;
-}
-
-enum {
- CardWidth = Cards_CardWidth,
- CardHeight = Cards_CardHeight,
-
- StackMarginX = 11,
- StackMarginY = 6,
-
- StockX = StackMarginX,
- StockY = StackMarginY,
-
- WasteX = StockX + CardWidth + StackMarginX,
- WasteY = StackMarginY,
-
- FoundationX = WasteX + 2 * (CardWidth + StackMarginX),
- FoundationY = StackMarginY,
-
- TableauX = StackMarginX,
- TableauY = StockY + CardHeight + StackMarginY,
-
- FanRightDeltaX = 14,
- FanDownDeltaYBack = 3,
- FanDownDeltaYFront = 15,
-
- WindowWidth = 7 * CardWidth + 8 * StackMarginX,
- WindowHeight = 2 * (StackMarginY + CardHeight)
- + 6 * FanDownDeltaYBack
- + 12 * FanDownDeltaYFront
- + StackMarginY,
-};
-
-static const struct Style FanRight = {
- 1, 0, 0, FanRightDeltaX, SquareDeltaY
-};
-static const struct Style FanDown = {
- 1, 0, FanDownDeltaYBack, 0, FanDownDeltaYFront
-};
-
-static struct SDL_Rect stackRects[StacksLen];
-static struct Layout layout;
-static uint backTexture = Cards_Back1;
-
-static void updateLayout(void) {
- layoutClear(&layout);
-
- SDL_Rect stock = { StockX, StockY, CardWidth, CardHeight };
- stackRects[Stock] = stock;
- listPush(&layout.main, &stock, Cards_O);
- layoutStack(&layout, &stock, &stacks[Stock], &Square);
-
- SDL_Rect waste = { WasteX, WasteY, CardWidth, CardHeight };
- layoutStack(&layout, &waste, &stacks[Waste1], &Square);
- layoutStack(&layout, &waste, &stacks[Waste3], &FanRight);
-
- SDL_Rect found = { FoundationX, FoundationY, CardWidth, CardHeight };
- for (uint i = Foundation1; i <= Foundation4; ++i) {
- stackRects[i] = found;
- listPush(&layout.main, &found, Cards_Empty);
- SDL_Rect rect = found;
- layoutStack(&layout, &rect, &stacks[i], &Square);
- found.x += CardWidth + StackMarginX;
- }
-
- SDL_Rect table = { TableauX, TableauY, CardWidth, CardHeight };
- for (uint i = Tableau1; i <= Tableau7; ++i) {
- stackRects[i] = table;
- stackRects[i].h = WindowHeight;
- SDL_Rect rect = table;
- layoutStack(&layout, &rect, &stacks[i], &FanDown);
- table.x += CardWidth + StackMarginX;
- }
-}
-
-static bool keyDown(SDL_KeyboardEvent key) {
- switch (key.keysym.sym) {
- case SDLK_F2: gameDeal(); return true;
- case SDLK_BACKSPACE: return gameUndo();
- case SDLK_b: {
- backTexture -= Cards_Back1;
- backTexture += 1;
- backTexture %= 12;
- backTexture += Cards_Back1;
- return true;
- }
- case SDLK_d: {
- game.draw = (game.draw == 1 ? 3 : 1);
- gameDeal();
- return true;
- }
- default: return false;
- }
-}
-
-static bool mouseButtonDown(SDL_MouseButtonEvent button) {
- struct SDL_Point point = { button.x, button.y };
- if (SDL_PointInRect(&point, &stackRects[Stock])) {
- gameDraw();
- return true;
- }
- struct Item *item = listFind(&layout.main, &point);
- if (!item) return false;
- if (!gameAvail(item->card)) return gameReveal(item->card);
- if (button.clicks % 2 == 0) {
- for (uint dest = Foundation1; dest <= Foundation4; ++dest) {
- if (gameMove(dest, item->card)) return true;
- }
- return false;
- }
- layout.dragItem = *item;
- return true;
-}
-
-static bool mouseButtonUp(SDL_MouseButtonEvent button) {
- (void)button;
- if (!layout.dragItem.card) return false;
- for (uint dest = 0; dest < StacksLen; ++dest) {
- if (SDL_HasIntersection(&layout.dragItem.rect, &stackRects[dest])) {
- if (gameMove(dest, layout.dragItem.card)) break;
- }
- }
- layout.dragItem.card = 0;
- return true;
-}
-
-static bool mouseMotion(SDL_MouseMotionEvent motion) {
- if (!motion.state) return false;
- layout.dragItem.rect.x += motion.xrel;
- layout.dragItem.rect.y += motion.yrel;
- return true;
-}
-
-static SDL_Renderer *render;
-static SDL_Texture *textures[Cards_CardCount];
-
-static void renderList(const struct List *list) {
- for (uint i = 0; i < list->len; ++i) {
- int tex = list->items[i].card;
- if (tex < 0) tex = backTexture;
- SDL_RenderCopy(render, textures[tex], 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");
-
- SDL_RWops *rw = assetOpenCards(&paths);
- if (!rw) return EXIT_FAILURE;
-
- SDL_Surface *surfaces[Cards_CardCount];
- int error = Cards_LoadCards(
- surfaces, Cards_CardCount,
- rw, Cards_ColorKey | Cards_AlphaCorners | Cards_BlackBorders
- );
- if (error) err("Cards_LoadCards");
- 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, "Solitaire");
-
- SDL_RenderSetIntegerScale(render, SDL_TRUE);
- SDL_RenderSetLogicalSize(render, WindowWidth, WindowHeight);
-
- for (uint i = 0; i < Cards_CardCount; ++i) {
- textures[i] = NULL;
- if (!surfaces[i]) continue;
- textures[i] = SDL_CreateTextureFromSurface(render, surfaces[i]);
- if (!textures[i]) err("SDL_CreateTextureFromSurface");
- SDL_FreeSurface(surfaces[i]);
- }
-
- srand(time(NULL));
- backTexture = Cards_Back1 + randUniform(12);
- gameDeal();
-
- for (;;) {
- updateLayout();
-
- SDL_SetRenderDrawColor(render, 0x00, 0xAA, 0x55, 0xFF);
- SDL_RenderClear(render);
- renderList(&layout.main);
- renderList(&layout.drag);
- SDL_RenderPresent(render);
-
- 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_MOUSEBUTTONDOWN) {
- if (mouseButtonDown(event.button)) break;
- } else if (event.type == SDL_MOUSEBUTTONUP) {
- if (mouseButtonUp(event.button)) break;
- } else if (event.type == SDL_MOUSEMOTION) {
- if (mouseMotion(event.motion)) break;
- }
- }
- }
-
-quit:
- SDL_Quit();
-}
diff --git a/stack.h b/stack.h
deleted file mode 100644
index f5140df..0000000
--- a/stack.h
+++ /dev/null
@@ -1,108 +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/>.
- */
-
-#ifndef STACK_H
-#define STACK_H
-
-#include <SDL_stdinc.h>
-#include <assert.h>
-#include <stdlib.h>
-
-#include "cards.h"
-
-#ifndef STACK_CAP
-#define STACK_CAP 52
-#endif
-
-typedef Sint8 Card;
-
-static inline int cardSuit(Card card) {
- card = abs(card);
- if (card > Cards_Spade) {
- return Cards_Spade;
- } else if (card > Cards_Heart) {
- return Cards_Heart;
- } else if (card > Cards_Diamond) {
- return Cards_Diamond;
- } else {
- return Cards_Club;
- }
-}
-
-static inline int cardColor(Card card) {
- return cardSuit(card) == Cards_Diamond || cardSuit(card) == Cards_Heart;
-}
-
-static inline int cardRank(Card card) {
- return abs(card) - cardSuit(card);
-}
-
-struct Stack {
- Uint8 len;
- Card cards[STACK_CAP];
-};
-
-static inline void stackClear(struct Stack *stack) {
- stack->len = 0;
-}
-
-static inline void stackPush(struct Stack *stack, Card card) {
- assert(stack->len < STACK_CAP);
- stack->cards[stack->len++] = card;
-}
-
-static inline Card stackPop(struct Stack *stack) {
- if (!stack->len) return 0;
- return stack->cards[--stack->len];
-}
-
-static inline Card stackTop(const struct Stack *stack) {
- if (!stack->len) return 0;
- return stack->cards[stack->len - 1];
-}
-
-static inline void stackFlipTo(struct Stack *dst, struct Stack *src, Uint8 n) {
- if (n > src->len) n = src->len;
- for (Uint8 i = 0; i < n; ++i) {
- stackPush(dst, -stackPop(src));
- }
-}
-
-static inline void stackMoveTo(struct Stack *dst, struct Stack *src, Uint8 n) {
- if (n > src->len) n = src->len;
- for (Uint8 i = 0; i < n; ++i) {
- stackPush(dst, src->cards[src->len - n + i]);
- }
- src->len -= n;
-}
-
-static inline int randUniform(int bound) {
- for (;;) {
- int r = rand();
- if (r >= RAND_MAX % bound) return r % bound;
- }
-}
-
-static inline void stackShuffle(struct Stack *stack) {
- for (Uint8 i = stack->len - 1; i > 0; --i) {
- Uint8 j = randUniform(i + 1);
- Card x = stack->cards[i];
- stack->cards[i] = stack->cards[j];
- stack->cards[j] = x;
- }
-}
-
-#endif