summary refs log tree commit diff
diff options
context:
space:
mode:
authorJune McEnroe <june@causal.agency>2019-08-25 19:06:46 -0400
committerJune McEnroe <june@causal.agency>2019-08-25 19:06:46 -0400
commit20d98e508e1b2e847e6524652600f019365cfb52 (patch)
tree79ae7766f26501ef4e8565282b9af9c43705c97b
parentBuild with cards submodule (diff)
downloadwep-20d98e508e1b2e847e6524652600f019365cfb52.tar.gz
wep-20d98e508e1b2e847e6524652600f019365cfb52.zip
Rewrite FreeCell
With move sequencing!
-rw-r--r--Makefile4
-rw-r--r--freecell.c556
-rw-r--r--stack.h14
3 files changed, 365 insertions, 209 deletions
diff --git a/Makefile b/Makefile
index 90e71e5..03bb0ad 100644
--- a/Makefile
+++ b/Makefile
@@ -15,7 +15,9 @@ ${BINS}: cards/cards.o
 
 ${OBJS} cards/cards.o: cards/cards.h
 
-${OBJS}: asset.h layout.h stack.h
+${OBJS}: asset.h stack.h
+
+sol.o: layout.h
 
 cards/cards.h:
 	git submodule update --init
diff --git a/freecell.c b/freecell.c
index 652ce3d..332be63 100644
--- a/freecell.c
+++ b/freecell.c
@@ -23,9 +23,10 @@
 #include <cards.h>
 
 #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();
 }
diff --git a/stack.h b/stack.h
index 6daf9d0..3d3a43b 100644
--- a/stack.h
+++ b/stack.h
@@ -27,6 +27,10 @@
 #define STACK_CAP 52
 #endif
 
+#if STACK_CAP < 52
+#error "Stack capacity too small for deck"
+#endif
+
 typedef Sint8 Card;
 
 static inline int cardSuit(Card card) {
@@ -55,6 +59,16 @@ struct Stack {
 	Card cards[STACK_CAP];
 };
 
+static const struct Stack Deck = {
+	.len = 52,
+	.cards = {
+		-1, -2, -3, -4, -5, -6, -7, -8, -9, -10, -11, -12, -13,
+		-14, -15, -16, -17, -18, -19, -20, -21, -22, -23, -24, -25, -26,
+		-27, -28, -29, -30, -31, -32, -33, -34, -35, -36, -37, -38, -39,
+		-40, -41, -42, -43, -44, -45, -46, -47, -48, -49, -50, -51, -52,
+	},
+};
+
 static inline void stackClear(struct Stack *stack) {
 	stack->len = 0;
 }