Browse Source

Remove games

master
Causal Agent 2 months ago
parent
commit
beba76119f
Signed by: Curtis McEnroe <june@causal.agency> GPG Key ID: CEA2F97ADCFCD77C
7 changed files with 7 additions and 1177 deletions
  1. 1
    2
      .gitignore
  2. 6
    8
      Makefile
  3. 0
    72
      asset.h
  4. 0
    484
      freecell.c
  5. 0
    110
      layout.h
  6. 0
    393
      sol.c
  7. 0
    108
      stack.h

+ 1
- 2
.gitignore View File

@@ -5,5 +5,4 @@ FREECELL.EXE
SOL.EXE
config.mk
dump
freecell
sol
example

+ 6
- 8
Makefile View File

@@ -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

+ 0
- 72
asset.h View File

@@ -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

+ 0
- 484
freecell.c View File

@@ -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();
}

+ 0
- 110
layout.h View File

@@ -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

+ 0
- 393
sol.c View File

@@ -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();
}

+ 0
- 108
stack.h View File

@@ -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

Loading…
Cancel
Save