diff options
Diffstat (limited to '')
-rw-r--r-- | sol.c | 395 |
1 files changed, 395 insertions, 0 deletions
diff --git a/sol.c b/sol.c new file mode 100644 index 0000000..48a61a2 --- /dev/null +++ b/sol.c @@ -0,0 +1,395 @@ +/* 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 <stdbool.h> +#include <stdio.h> +#include <stdlib.h> +#include <time.h> + +#include <SDL.h> +#include <cards.h> + +#include "asset.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) { + clear(&stacks[i]); + } + stacks[Stock] = Deck; + shuffle(&stacks[Stock]); + for (uint i = Tableau1; i <= Tableau7; ++i) { + moveTo(&stacks[i], &stacks[Stock], i - Tableau1); + flipTo(&stacks[i], &stacks[Stock], 1); + } +} + +static void gameDraw(void) { + moveTo(&stacks[Waste1], &stacks[Waste3], stacks[Waste3].len); + if (stacks[Stock].len) { + if (game.draw > 1) { + gameDo(flipTo, Waste3, Stock, game.draw); + } else { + gameDo(flipTo, Waste1, Stock, 1); + } + } else { + gameDo(flipTo, 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 == peek(&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 != peek(&stacks[stack])) return false; + if (card > 0) return false; + gameDo(flipTo, 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 = peek(&stacks[dest]); + + if (dest >= Foundation1 && dest <= Foundation4) { + if (count > 1) return false; + if (!destTop && rank(card) != Cards_A) return false; + if (destTop && suit(card) != suit(destTop)) return false; + if (destTop && rank(card) != rank(destTop) + 1) return false; + gameDo(moveTo, dest, source, 1); + return true; + } + + if (dest >= Tableau1 && dest <= Tableau7) { + if (!destTop && rank(card) != Cards_K) return false; + if (destTop && color(card) == color(destTop)) return false; + if (destTop && rank(card) != rank(destTop) - 1) return false; + gameDo(moveTo, 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(int argc, char *argv[]) { + (void)argc; + (void)argv; + + 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(); +} |