From 20d98e508e1b2e847e6524652600f019365cfb52 Mon Sep 17 00:00:00 2001 From: Curtis McEnroe Date: Sun, 25 Aug 2019 19:06:46 -0400 Subject: Rewrite FreeCell With move sequencing! --- freecell.c | 556 ++++++++++++++++++++++++++++++++++++++----------------------- 1 file changed, 348 insertions(+), 208 deletions(-) (limited to 'freecell.c') diff --git a/freecell.c b/freecell.c index 652ce3d..332be63 100644 --- a/freecell.c +++ b/freecell.c @@ -23,9 +23,10 @@ #include #include "asset.h" -#include "layout.h" #include "stack.h" +typedef unsigned uint; + enum { Foundation1, Foundation2, @@ -48,28 +49,58 @@ enum { static struct Stack stacks[StacksLen]; -static struct { - bool avail; +static uint kingIndex = Cards_KingRight; + +struct Move { uint dst; uint src; -} undo; +}; + +enum { QueueLen = 32 }; +static struct { + struct Move moves[QueueLen]; + uint r, w; +} queue; + +static uint undo; + +static void gameEnqueue(uint dst, uint src) { + queue.moves[queue.w % QueueLen].dst = dst; + queue.moves[queue.w % QueueLen].src = src; + queue.w++; +} + +static void gameDequeue(void) { + struct Move move = queue.moves[queue.r++ % QueueLen]; + stackPush(&stacks[move.dst], stackPop(&stacks[move.src])); + if (move.dst >= Foundation1 && move.dst <= Foundation4) { + kingIndex = Cards_KingRight; + } else if (move.dst >= Cell1 && move.dst <= Cell4) { + kingIndex = Cards_KingLeft; + } +} -static uint kingFace = Cards_KingRight; +static bool gameUndo(void) { + uint len = queue.w - undo; + if (!len || len > QueueLen) return false; + for (uint i = len - 1; i < len; --i) { + struct Move move = queue.moves[(undo + i) % QueueLen]; + stackPush(&stacks[move.src], stackPop(&stacks[move.dst])); + } + queue.r = queue.w = undo; + return true; +} 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); - } + struct Stack deck = Deck; stackShuffle(&deck); for (uint i = Tableau1; i <= Tableau8; ++i) { - stackMoveTo(&stacks[i], &deck, (i < Tableau5 ? 7 : 6)); + stackFlipTo(&stacks[i], &deck, (i < Tableau5 ? 7 : 6)); } - undo.avail = false; - kingFace = Cards_KingRight; + kingIndex = Cards_KingRight; } static bool gameWin(void) { @@ -79,59 +110,23 @@ static bool gameWin(void) { 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)); +static bool gameValid(uint dst, Card 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 (!top) return cardRank(card) == Cards_A; + return cardSuit(card) == cardSuit(top) + && cardRank(card) == cardRank(top) + 1; } + if (!top) return true; if (dst >= Tableau1 && dst <= Tableau8) { - if (top && cardColor(card) == cardColor(top)) return false; - if (top && cardRank(card) != cardRank(top) - 1) return false; + return cardColor(card) != cardColor(top) + && cardRank(card) == cardRank(top) - 1; } - - 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; + return false; } -static bool gameAuto(void) { - Card min[2] = { Cards_K, Cards_K }; +static void gameAuto(void) { + Card min[] = { 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]; @@ -140,17 +135,70 @@ static bool gameAuto(void) { } } } - for (uint i = Cell1; i <= Tableau8; ++i) { - Card card = stackTop(&stacks[i]); + + for (uint src = Cell1; src <= Tableau8; ++src) { + Card card = stackTop(&stacks[src]); 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; + if (gameValid(dst, card)) { + gameEnqueue(dst, src); + return; + } } } - return false; +} + +static uint gameFree(uint list[]) { + uint len = 0; + for (uint i = Cell1; i <= Tableau8; ++i) { + if (!stacks[i].len) list[len++] = i; + } + return len; +} + +static uint gameDepth(uint src) { + struct Stack stack = stacks[src]; + if (stack.len < 2) return stack.len; + uint n = 1; + for (uint i = stack.len - 2; i < stack.len; --i, ++n) { + if (cardColor(stack.cards[i]) == cardColor(stack.cards[i + 1])) break; + if (cardRank(stack.cards[i]) != cardRank(stack.cards[i + 1]) + 1) break; + } + return n; +} + +static void gameMoveSingle(uint dst, uint src) { + if (gameValid(dst, stackTop(&stacks[src]))) { + undo = queue.w; + gameEnqueue(dst, src); + } +} + +static void gameMoveColumn(uint dst, uint src) { + uint depth; + for (depth = gameDepth(src); depth; --depth) { + if (gameValid(dst, stacks[src].cards[stacks[src].len - depth])) break; + } + if (depth < 2 || dst <= Cell4) { + gameMoveSingle(dst, src); + return; + } + + uint list[StacksLen]; + uint free = gameFree(list); + if (free < depth - 1) return; + + undo = queue.w; + for (uint i = 0; i < depth - 1; ++i) { + gameEnqueue(list[i], src); + } + gameEnqueue(dst, src); + for (uint i = depth - 2; i < depth - 1; --i) { + gameEnqueue(dst, list[i]); + } } enum { @@ -173,10 +221,10 @@ enum { StackMarginX = 7, StackMarginY = 10, + StackDeltaY = 17, TableauX = StackMarginX, TableauY = CellY + CardHeight + StackMarginY, - FanDownDeltaY = 17, KingWinMarginX = 10, KingWinMarginY = 10, @@ -189,45 +237,111 @@ enum { 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); +static struct SDL_Rect rects[StacksLen]; - SDL_Rect cell = { CellX, CellY, CardWidth, CardHeight }; +static void initRects(void) { + SDL_Rect rect = { CellX, CellY, CardWidth, CardHeight }; for (uint i = Cell1; i <= Cell4; ++i) { - stackRects[i] = cell; - layoutStack(&layout, &cell, &stacks[i], &Flat); - cell.x += CardWidth; + rects[i] = rect; + rect.x += CardWidth; } - SDL_Rect found = { FoundationX, FoundationY, CardWidth, CardHeight }; + rect.x = FoundationX; + rect.y = FoundationY; for (uint i = Foundation1; i <= Foundation4; ++i) { - stackRects[i] = found; - layoutStack(&layout, &found, &stacks[i], &Flat); - found.x += CardWidth; + rects[i] = rect; + rect.x += CardWidth; } - SDL_Rect table = { TableauX, TableauY, CardWidth, CardHeight }; + rect.x = TableauX; + rect.y = TableauY; + rect.h = WindowHeight - TableauY; 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; + rects[i] = rect; + rect.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 uint pointStack(SDL_Point point) { + uint i; + for (i = 0; i < StacksLen; ++i) { + if (SDL_PointInRect(&point, &rects[i])) break; } + return i; +} + +static SDL_Window *window; + +static void err(const char *title) { + int error = SDL_ShowSimpleMessageBox( + SDL_MESSAGEBOX_ERROR, title, SDL_GetError(), window + ); + if (error) fprintf(stderr, "%s\n", SDL_GetError()); + exit(EXIT_FAILURE); +} + +static Card hiliteRank; +static SDL_Point revealPoint; +static uint sourceStack = StacksLen; + +enum Choice { + Cancel, + Column, + Single, +}; + +static enum Choice chooseMove(void) { + SDL_MessageBoxButtonData buttons[] = { + { + .buttonid = Column, + .text = "Move column", + .flags = SDL_MESSAGEBOX_BUTTON_RETURNKEY_DEFAULT, + }, + { + .buttonid = Single, + .text = "Move single card", + }, + { + .buttonid = Cancel, + .text = "Cancel", + .flags = SDL_MESSAGEBOX_BUTTON_ESCAPEKEY_DEFAULT, + }, + }; + SDL_MessageBoxData data = { + .window = window, + .title = "Move to Empty Column...", + .message = "", + .buttons = buttons, + .numbuttons = SDL_arraysize(buttons), + }; + int choice; + if (SDL_ShowMessageBox(&data, &choice) < 0) err("SDL_ShowMessageBox"); + return choice; +} + +static bool chooseAgain(void) { + SDL_MessageBoxButtonData buttons[] = { + { + .buttonid = true, + .text = "Yes", + .flags = SDL_MESSAGEBOX_BUTTON_RETURNKEY_DEFAULT, + }, + { + .buttonid = false, + .text = "No", + .flags = SDL_MESSAGEBOX_BUTTON_ESCAPEKEY_DEFAULT, + }, + }; + SDL_MessageBoxData data = { + .window = window, + .title = "Game Over", + .message = "Congratulations, you win!\n\nDo you want to play again?", + .buttons = buttons, + .numbuttons = SDL_arraysize(buttons), + }; + int choice; + if (SDL_ShowMessageBox(&data, &choice) < 0) err("SDL_ShowMessageBox"); + return choice; } static bool keyDown(SDL_KeyboardEvent key) { @@ -237,20 +351,20 @@ static bool keyDown(SDL_KeyboardEvent key) { } 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; case SDLK_a: hiliteRank = Cards_A; + break; case SDLK_2: hiliteRank = Cards_2; + break; case SDLK_3: hiliteRank = Cards_3; + break; case SDLK_4: hiliteRank = Cards_4; + break; case SDLK_5: hiliteRank = Cards_5; + break; case SDLK_6: hiliteRank = Cards_6; + break; case SDLK_7: hiliteRank = Cards_7; + break; case SDLK_8: hiliteRank = Cards_8; + break; case SDLK_9: hiliteRank = Cards_9; + break; case SDLK_1: hiliteRank = Cards_10; + break; case SDLK_0: hiliteRank = Cards_10; + break; case SDLK_j: hiliteRank = Cards_J; + break; case SDLK_q: hiliteRank = Cards_Q; + break; case SDLK_k: hiliteRank = Cards_K; break; default: return false; } return true; @@ -258,62 +372,76 @@ static bool keyDown(SDL_KeyboardEvent key) { static bool keyUp(SDL_KeyboardEvent key) { (void)key; - if (!reveal.len) return false; - listClear(&reveal); - return true; + if (hiliteRank) { + hiliteRank = 0; + return true; + } + return false; } 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; + if (button.button == SDL_BUTTON_RIGHT) { + revealPoint.x = button.x; + revealPoint.y = button.y; + return true; + } + return false; } static bool mouseButtonUp(SDL_MouseButtonEvent button) { - struct SDL_Point point = { button.x, button.y }; + if (gameWin()) { + if (chooseAgain()) { + gameDeal(); + return true; + } + return false; + } - if (button.button == SDL_BUTTON_RIGHT) { - if (!reveal.len) return false; - listClear(&reveal); + if (button.button == SDL_BUTTON_RIGHT && revealPoint.x) { + revealPoint.x = 0; + revealPoint.y = 0; return true; } + SDL_Point point = { button.x, button.y }; + uint stack = pointStack(point); 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; + for (stack = Cell1; stack <= Cell4; ++stack) { + if (!stacks[stack].len) 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; + if (sourceStack < StacksLen && stack < StacksLen) { + if (gameDepth(sourceStack) > 1 && stack > Cell4 && !stacks[stack].len) { + switch (chooseMove()) { + break; case Single: gameMoveSingle(stack, sourceStack); + break; case Column: gameMoveColumn(stack, sourceStack); + break; case Cancel: break; } + } else { + gameMoveColumn(stack, sourceStack); } - layout.dragItem.card = 0; + } + + if (sourceStack < StacksLen) { + sourceStack = StacksLen; return true; } - struct Item *item = listFind(&layout.main, &point); - if (!item) return false; - if (!gameAvail(item->card)) return false; - layout.dragItem = *item; - return true; + if (stack < StacksLen && stack > Foundation4 && stacks[stack].len) { + sourceStack = stack; + return true; + } + + return false; } static SDL_Renderer *render; +static struct { + SDL_Texture *cards[Cards_Empty]; + SDL_Texture *hilites[Cards_Empty]; + SDL_Texture *kings[Cards_FreeCellCount]; +} tex; static void renderOutline(SDL_Rect rect, bool out) { int right = rect.x + rect.w - 1; @@ -336,76 +464,96 @@ static void renderOutline(SDL_Rect rect, bool out) { static void renderOutlines(void) { for (uint i = Foundation1; i <= Cell4; ++i) { - renderOutline(stackRects[i], false); + renderOutline(rects[i], false); } } -static void renderKing(SDL_Texture *textures[]) { +static void renderKing(void) { 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); + SDL_RenderCopy(render, tex.kings[Cards_KingWin], NULL, &king); } else { SDL_Rect king = { KingX + KingPadX, KingY + KingPadY, Cards_KingWidth, Cards_KingHeight, }; - SDL_RenderCopy(render, textures[kingFace], NULL, &king); + SDL_RenderCopy(render, tex.kings[kingIndex], 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 renderCard(SDL_Rect rect, Card card, bool hilite) { + SDL_Texture *texture = (hilite ? tex.hilites : tex.cards)[card]; + SDL_RenderCopy(render, texture, NULL, &rect); +} + +static void renderStack(uint stack) { + Card revealCard = 0; + SDL_Rect revealRect = {0}; + SDL_Rect rect = { rects[stack].x, rects[stack].y, CardWidth, CardHeight }; + for (uint i = 0; i < stacks[stack].len; ++i) { + Card card = stacks[stack].cards[i]; + if (SDL_PointInRect(&revealPoint, &rect)) { + revealCard = card; + revealRect = rect; + } + bool hilite = (stack == sourceStack && i == stacks[stack].len - 1); + renderCard(rect, card, hilite || cardRank(card) == hiliteRank); + rect.y += StackDeltaY; + } + if (revealCard) { + renderCard(revealRect, revealCard, false); } } -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); +static void renderStacks(void) { + for (uint i = Foundation1; i <= Cell4; ++i) { + Card card = stackTop(&stacks[i]); + if (!card) continue; + bool hilite = (i == sourceStack || cardRank(card) == hiliteRank); + renderCard(rects[i], card, hilite); + } + for (uint i = Tableau1; i <= Tableau8; ++i) { + renderStack(i); + } } int main(void) { + int error; + 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; + bool haveFreeCell = false; struct { SDL_Surface *cards[Cards_Empty]; - SDL_Surface *kings[Cards_FreeCellCount]; - } surfaces; + SDL_Surface *freeCell[Cards_FreeCellCount]; + } res; SDL_RWops *rw = assetOpenCards(&paths); if (!rw) return EXIT_FAILURE; - int error = Cards_LoadCards( - surfaces.cards, Cards_Empty, - rw, Cards_AlphaCorners | Cards_BlackBorders + error = Cards_LoadCards( + res.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 + haveFreeCell = true; + error = Cards_LoadFreeCell( + res.freeCell, 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 @@ -416,70 +564,62 @@ int main(void) { 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; + if (!res.cards[i]) continue; - textures[i] = SDL_CreateTextureFromSurface(render, surfaces.cards[i]); - if (!textures[i]) err("SDL_CreateTextureFromSurface"); + tex.cards[i] = SDL_CreateTextureFromSurface(render, res.cards[i]); + if (!tex.cards[i]) err("SDL_CreateTextureFromSurface"); - error = Cards_InvertSurface(surfaces.cards[i]); - if (error) err("Cards_InvertSurface"); + if (Cards_InvertSurface(res.cards[i]) < 0) err("Cards_hilitesurface"); + tex.hilites[i] = SDL_CreateTextureFromSurface(render, res.cards[i]); + if (!tex.hilites[i]) err("SDL_CreateTextureFromSurface"); - inverted[i] = SDL_CreateTextureFromSurface(render, surfaces.cards[i]); - if (!inverted[i]) err("SDL_CreateTextureFromSurface"); - - SDL_FreeSurface(surfaces.cards[i]); + SDL_FreeSurface(res.cards[i]); } - if (kings) { + if (haveFreeCell) { 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]); + if (!res.freeCell[i]) continue; + tex.kings[i] = SDL_CreateTextureFromSurface(render, res.freeCell[i]); + if (!tex.kings[i]) err("SDL_CreateTextureFromSurface"); + SDL_FreeSurface(res.freeCell[i]); } } srand(time(NULL)); gameDeal(); - + + initRects(); 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); + renderStacks(); + if (haveFreeCell) renderKing(); SDL_RenderPresent(render); - // TODO: Add more than a frame delay between automatic moves? - if (gameAuto()) continue; + if (queue.r < queue.w) { + gameDequeue(); + if (queue.r == queue.w) gameAuto(); + if (queue.r < queue.w) SDL_Delay(50); + continue; + } - SDL_Event event; - for (;;) { + bool update = false; + while (!update) { + SDL_Event event; 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; + switch (event.type) { + break; case SDL_QUIT: return EXIT_SUCCESS; + break; case SDL_KEYDOWN: update = keyDown(event.key); + break; case SDL_KEYUP: update = keyUp(event.key); + break; case SDL_MOUSEBUTTONDOWN: { + update = mouseButtonDown(event.button); + } + break; case SDL_MOUSEBUTTONUP: { + update = mouseButtonUp(event.button); + } } } } - -quit: - SDL_Quit(); } -- cgit 1.4.1