about summary refs log tree commit diff
path: root/sol.c
diff options
context:
space:
mode:
authorJune McEnroe <june@causal.agency>2019-03-22 15:22:10 -0400
committerJune McEnroe <june@causal.agency>2019-03-22 15:22:10 -0400
commitc4e4d7c76563216b598feba7abc6b9b72df4724b (patch)
tree85d353f26963a702c17d07f8432db814df4df001 /sol.c
parentRefactor layout code slightly (diff)
downloadcards-c4e4d7c76563216b598feba7abc6b9b72df4724b.tar.gz
cards-c4e4d7c76563216b598feba7abc6b9b72df4724b.zip
Implement the game
Diffstat (limited to 'sol.c')
-rw-r--r--sol.c247
1 files changed, 181 insertions, 66 deletions
diff --git a/sol.c b/sol.c
index 8ab89d5..ed5981d 100644
--- a/sol.c
+++ b/sol.c
@@ -16,6 +16,7 @@
 
 #include <SDL.h>
 #include <assert.h>
+#include <stdbool.h>
 #include <stdio.h>
 #include <stdlib.h>
 
@@ -24,6 +25,27 @@
 
 typedef unsigned uint;
 
+static Sint8 cardSuit(Sint8 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 Sint8 cardColor(Sint8 card) {
+	return cardSuit(card) == Cards_Diamond || cardSuit(card) == Cards_Heart;
+}
+
+static Sint8 cardRank(Sint8 card) {
+	return abs(card) - cardSuit(card);
+}
+
 enum {
 	Stock,
 	Waste1,
@@ -44,35 +66,104 @@ enum {
 
 static struct Stack stacks[StacksLen];
 
-static void restock(void) {
+// TODO: Scoring method, score, time.
+static struct {
+	uint draw;
+} game = {
+	.draw = 3,
+};
+
+static void gameDeal(void) {
 	for (uint i = 0; i < StacksLen; ++i) {
 		stackClear(&stacks[i]);
 	}
 	stackDeck(&stacks[Stock]);
-}
-
-static void reveal(uint i) {
-	if (!stacks[i].len) return;
-	stackPush(&stacks[i], abs(stackPop(&stacks[i])));
-}
-
-static void deal(void) {
 	stackShuffle(&stacks[Stock]);
 	for (uint i = Tableau1; i <= Tableau7; ++i) {
-		stackMoveTo(&stacks[i], &stacks[Stock], 1 + i - Tableau1);
-		reveal(i);
+		stackMoveTo(&stacks[i], &stacks[Stock], i - Tableau1);
+		stackFlipTo(&stacks[i], &stacks[Stock], 1);
 	}
 }
 
-static void draw(void) {
+static void gameDraw(void) {
 	stackMoveTo(&stacks[Waste1], &stacks[Waste3], stacks[Waste3].len);
 	if (stacks[Stock].len) {
-		stackFlipTo(&stacks[Waste3], &stacks[Stock], 3);
+		if (game.draw == 1) {
+			stackFlipTo(&stacks[Waste1], &stacks[Stock], 1);
+		} else {
+			stackFlipTo(&stacks[Waste3], &stacks[Stock], 3);
+		}
 	} else {
 		stackFlipTo(&stacks[Stock], &stacks[Waste1], stacks[Waste1].len);
 	}
 }
 
+static bool gameFind(uint *stack, uint *index, Sint8 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(Sint8 card) {
+	uint stack, index;
+	if (card < 0) return false;
+	if (!gameFind(&stack, &index, card)) return false;
+
+	bool top = (index == stacks[stack].len - 1);
+	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(Sint8 card) {
+	uint stack, index;
+	if (!gameFind(&stack, &index, card)) return false;
+	if (stack < Tableau1 || stack > Tableau7) return false;
+	if (index != stacks[stack].len - 1) return false;
+	if (card > 0) return false;
+	stackFlipTo(&stacks[stack], &stacks[stack], 1);
+	return true;
+}
+
+static bool gameMove(uint dest, Sint8 card) {
+	uint source, index;
+	if (!gameFind(&source, &index, card)) return false;
+	if (source == dest) return false;
+
+	uint count = stacks[source].len - index;
+	Sint8 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;
+		stackMoveTo(&stacks[dest], &stacks[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;
+		stackMoveTo(&stacks[dest], &stacks[source], count);
+		return true;
+	}
+
+	return false;
+}
+
 enum {
 	StackMarginX = 11,
 	StackMarginY = 6,
@@ -108,7 +199,7 @@ enum {
 
 struct Item {
 	SDL_Rect rect;
-	uint tex;
+	uint texture;
 	Sint8 card;
 };
 
@@ -123,15 +214,25 @@ static void listPush(struct List *list, struct Item item) {
 }
 
 static struct {
+	SDL_Rect stacks[StacksLen];
 	struct List base;
 	struct List main;
 	struct List drag;
 	struct Item dragItem;
-	uint cardBack;
+	uint backTexture;
 } layout = {
-	.cardBack = Cards_Back1,
+	.backTexture = Cards_Back1,
 };
 
+static struct Item *layoutFind(const SDL_Point *point) {
+	for (uint i = layout.main.len - 1; i < layout.main.len; --i) {
+		if (SDL_PointInRect(point, &layout.main.items[i].rect)) {
+			return &layout.main.items[i];
+		}
+	}
+	return NULL;
+}
+
 static void layoutClear(void) {
 	layout.base.len = 0;
 	layout.main.len = 0;
@@ -143,7 +244,7 @@ static void layoutCard(struct List **list, SDL_Rect *rect, Sint8 card) {
 		*list = &layout.drag;
 		*rect = layout.dragItem.rect;
 	}
-	struct Item item = { *rect, (card > 0 ? card : layout.cardBack), card };
+	struct Item item = { *rect, (card > 0 ? card : layout.backTexture), card };
 	listPush(*list, item);
 }
 
@@ -162,6 +263,7 @@ static void layoutStock(void) {
 	SDL_Rect rect = { StockX, StockY, Cards_Width, Cards_Height };
 	struct Item item = { rect, Cards_O, 0 };
 	listPush(&layout.base, item);
+	layout.stacks[Stock] = rect;
 	layoutStack(&rect, &stacks[Stock], 24);
 }
 
@@ -181,6 +283,7 @@ static void layoutFoundations(void) {
 	for (uint i = Foundation1; i <= Foundation4; ++i) {
 		struct Item item = { base, Cards_Empty, 0 };
 		listPush(&layout.base, item);
+		layout.stacks[i] = base;
 		SDL_Rect rect = base;
 		layoutStack(&rect, &stacks[i], 13);
 		base.x += Cards_Width + StackMarginX;
@@ -190,6 +293,8 @@ static void layoutFoundations(void) {
 static void layoutTableau(void) {
 	SDL_Rect base = { TableauX, TableauY, Cards_Width, Cards_Height };
 	for (uint i = Tableau1; i <= Tableau7; ++i) {
+		SDL_Rect stack = { base.x, base.y, base.w, WindowHeight };
+		layout.stacks[i] = stack;
 		struct List *list = &layout.main;
 		SDL_Rect rect = base;
 		for (uint j = 0; j < stacks[i].len; ++j) {
@@ -202,6 +307,44 @@ static void layoutTableau(void) {
 	}
 }
 
+static bool mouseButtonDown(SDL_MouseButtonEvent button) {
+	struct SDL_Point point = { button.x, button.y };
+	if (SDL_PointInRect(&point, &layout.stacks[Stock])) {
+		gameDraw();
+		return true;
+	}
+	struct Item *item = layoutFind(&point);
+	if (!item) return false;
+	if (!gameAvail(item->card)) return gameReveal(item->card);
+	if (button.clicks == 2) {
+		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, &layout.stacks[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_Count];
 
@@ -209,12 +352,13 @@ static void renderList(const struct List *list) {
 	for (uint i = 0; i < list->len; ++i) {
 		SDL_RenderCopy(
 			render,
-			textures[list->items[i].tex],
+			textures[list->items[i].texture],
 			NULL, &list->items[i].rect
 		);
 	}
 }
 
+// TODO: Use SDL_ShowMessageBox.
 static void err(const char *prefix) {
 	fprintf(stderr, "%s: %s\n", prefix, SDL_GetError());
 	exit(EXIT_FAILURE);
@@ -244,7 +388,7 @@ int main(void) {
 		);
 	}
 
-	// TODO: Path search/option.
+	// TODO: Path search.
 	SDL_RWops *rw = SDL_RWFromFile("CARDS.DLL", "rb");
 	if (!rw) err("CARDS.DLL");
 
@@ -262,53 +406,12 @@ int main(void) {
 	}
 	Cards_Free(cards);
 
-	restock();
-	deal();
-
-	for (;;) {
-		SDL_Event event;
-		SDL_WaitEvent(&event);
-		if (event.type == SDL_QUIT) goto quit;
-
-		if (event.type == SDL_KEYDOWN) {
-			switch (event.key.keysym.sym) {
-				break; case SDLK_q: goto quit;
-				break; case SDLK_r: restock(); deal();
-				break; case SDLK_SPACE: draw();
-				break; case SDLK_f: {
-					stackFlipTo(&stacks[Foundation1], &stacks[Stock], 1);
-				}
-				break; case SDLK_t: {
-					stackFlipTo(&stacks[Tableau7], &stacks[Stock], 1);
-				}
-				break; case SDLK_b: {
-					layout.cardBack++;
-					layout.cardBack -= Cards_Back1;
-					layout.cardBack %= 12;
-					layout.cardBack += Cards_Back1;
-				}
-			}
-		}
-
-		if (event.type == SDL_MOUSEBUTTONDOWN) {
-			struct SDL_Point point = { event.button.x, event.button.y };
-			for (uint i = layout.main.len - 1; i < layout.main.len; --i) {
-				if (SDL_PointInRect(&point, &layout.main.items[i].rect)) {
-					layout.dragItem = layout.main.items[i];
-					break;
-				}
-			}
-		}
-
-		if (event.type == SDL_MOUSEBUTTONUP) {
-			layout.dragItem.card = 0;
-		}
+	gameDeal();
 
-		if (event.type == SDL_MOUSEMOTION && event.motion.state) {
-			layout.dragItem.rect.x += event.motion.xrel;
-			layout.dragItem.rect.y += event.motion.yrel;
-		}
+	// FIXME: Use a portable random.
+	layout.backTexture = Cards_Back1 + arc4random_uniform(12);
 
+	for (;;) {
 		layoutClear();
 		layoutStock();
 		layoutWaste();
@@ -317,12 +420,24 @@ int main(void) {
 
 		SDL_SetRenderDrawColor(render, 0x00, 0xAA, 0x55, 0xFF);
 		SDL_RenderClear(render);
-
 		renderList(&layout.base);
 		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_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: