Added set_solver, for fun
Added set_solver, for fun

file:b/set_solver.cpp (new)
--- /dev/null
+++ b/set_solver.cpp
@@ -1,1 +1,428 @@
-
+#include <stdio.h>
+#include <stdlib.h>
+#include <assert.h>
+#include <chrono>
+
+typedef unsigned short	u16;
+typedef int				s32;
+
+const u16 TABLEAU_SIZE = 12;
+const u16 DECK_SIZE = 81;
+
+enum Axis {
+	NUMBER,
+	FACE,
+	COLOR,
+	SHADING
+};
+
+enum Number {
+	One,
+	Two,
+	Three
+};
+
+enum Face {
+	Squiggle,
+	Diamond,
+	Oval
+};
+
+enum Color {
+	Red,
+	Green,
+	Blue
+};
+
+enum Shading {
+	Hollow,
+	Shaded,
+	Solid
+};
+
+struct Card {
+	Number number;
+	Face face;
+	Color color;
+	Shading shading;
+};
+
+struct Set {
+	Card one;
+	Card two;
+	Card three;
+};
+
+const char* number_names [] = {
+	"1",
+	"2",
+	"3"
+};
+
+const char* face_names [] = {
+	"S",
+	"D",
+	"O"
+};
+
+const char* color_names [] = {
+	"R",
+	"G",
+	"B"
+};
+
+const char* shading_names [] = {
+	"E",
+	"H",
+	"F"
+};
+
+u16 sprint_card(char* buffer, Card card) {
+	return sprintf(buffer, "(%s %s %s %s)", number_names[card.number], face_names[card.face], color_names[card.color], shading_names[card.shading]);
+}
+
+void print_card(Card card) {
+	char tmpstring [64];
+	sprint_card(tmpstring, card);
+	printf("%s\n", tmpstring);
+}
+
+u16 sprint_set(char* buffer, Set set) {
+	u16 len = 0;
+
+	len += sprint_card(buffer + len, set.one);
+	buffer[len++] = ' ';
+
+	len += sprint_card(buffer + len, set.two);
+	buffer[len++] = ' ';
+
+	len += sprint_card(buffer + len, set.three);
+
+	return len;
+}
+
+void print_set(Set set) {
+	char tmpstring [512];
+	sprint_set(tmpstring, set);
+	printf("%s\n", tmpstring);
+}
+
+u16 make_deck(Card* deck) {
+	u16 idx = 0;
+	for (u16 n = 0; n < 3; n++) {
+		for (u16 f = 0; f < 3; f++) {
+			for (u16 c = 0; c < 3; c++) {
+				for (u16 s = 0; s < 3; s++) {
+					Card card = Card {(Number)n, (Face)f, (Color)c, (Shading)s};
+					deck[idx] = card;
+					idx++;
+				}
+			}
+		}
+	}
+
+	return idx;
+}
+
+bool cards_are_equal(Card lhs, Card rhs) {
+	return lhs.number == rhs.number && lhs.face == rhs.face && lhs.color == rhs.color && lhs.shading == rhs.shading;
+}
+
+bool in_array(u16 count, Card* array, Card card) {
+	for (u16 i = 0; i < count; i++) {
+		if (cards_are_equal(array[i], card)) return true;
+	}
+	return false;
+}
+
+bool in_array(u16 count, u16* haystack, u16 needle) {
+	for (u16 i = 0; i < count; i++) {
+		if (haystack[i] == needle) return true;
+	}
+	return false;
+}
+
+bool remove_at_index(u16 length, Card* haystack, u16 index) {
+	for (u16 i = index; i < length - 1; i++) {
+		haystack[i] = haystack[i + 1];
+	}
+}
+
+u16 rand_in_range(u16 start, u16 end) {
+	assert(end > start);
+
+	float f = (float)rand() / (float)RAND_MAX;
+	float range = (float)(end - start);
+
+	return start + (u16)(range * f);
+}
+
+Card draw_unique_card(Card* deck, bool* available_cards, u16 *num_available) {
+	Card card;
+
+	u16 tries = 0;
+	while (1) {
+		u16 idx = rand_in_range(0, 80);
+		
+		if (available_cards[idx]) {
+			card = deck[idx];
+			available_cards[idx] = false;
+			*num_available -= 1;
+			break;
+		}
+		tries++;
+
+		if (tries > 100) {
+			for (idx = 0; idx < DECK_SIZE; idx++) {
+				if (available_cards[idx]) {
+					card = deck[idx];
+					available_cards[idx] = false;
+					*num_available -= 1;
+					return card;
+				}
+			}
+		}
+	}
+
+	return card;
+}
+
+u16 fill_tableau_from_index(u16 index, Card* tableau, Card* deck, bool* available_cards, u16 *num_available) {
+	u16 count = 0;
+	for (u16 i = index; i < TABLEAU_SIZE; i++) {
+		tableau[i] = draw_unique_card(deck, available_cards, num_available);
+		count++;
+	}
+
+	return count;
+}
+
+u16 fill_tableau(Card* tableau, Card* deck, bool* available_cards, u16 *num_available) {
+	return fill_tableau_from_index(0, tableau, deck, available_cards, num_available);
+}
+
+bool axis_is_equal(Axis axis, Card a, Card b, Card c) {
+	switch (axis) {
+		case NUMBER: return a.number == b.number && b.number == c.number;
+		case FACE: return a.face == b.face && b.face == c.face;
+		case COLOR: return a.color == b.color && b.color == c.color;
+		case SHADING: return a.shading == b.shading && b.shading == c.shading;
+		default: return false;
+	}
+}
+
+bool axis_is_unequal(Axis axis, Card a, Card b, Card c) {
+	switch (axis) {
+		case NUMBER: return a.number != b.number && b.number != c.number && a.number != c.number;
+		case FACE: return a.face != b.face && b.face != c.face && a.number != c.number;
+		case COLOR: return a.color != b.color && b.color != c.color && a.color != c.color;
+		case SHADING: return a.shading != b.shading && b.shading != c.shading && a.shading != c.shading;
+		default: return false;
+	}
+}
+
+bool cards_are_set(Card a, Card b, Card c) {
+	bool result = 1;
+
+	for (u16 axis = 0; axis < 4; axis++) {
+		result &= axis_is_equal((Axis)axis, a, b, c) || axis_is_unequal((Axis)axis, a, b, c);
+	}
+
+	return result;
+}
+
+u16 find_sets_in_tableau(Set* sets, Card* tableau) {
+	u16 reserved_cards [TABLEAU_SIZE];
+	u16 reserved = 0;
+
+	Card a, b, c;
+	u16 found = 0;
+
+	for (u16 i = 0; i < TABLEAU_SIZE; i++) {
+		a = tableau[i];
+
+		for (u16 j = 0; j < TABLEAU_SIZE; j++) {
+			if (j == i) continue;
+			if (in_array(reserved, reserved_cards, i)) break;
+
+			b = tableau[j];
+
+			for (u16 k = 0; k < TABLEAU_SIZE; k++) {
+				if (k == j || k == i) continue;
+				if (in_array(reserved, reserved_cards, j)) break;
+
+				c = tableau[k];
+
+				if (in_array(reserved, reserved_cards, k)) continue;
+
+				if (cards_are_set(a, b, c)) {
+					sets[found++] = Set {a, b, c};
+					reserved_cards[reserved++] = i;
+					reserved_cards[reserved++] = j;
+					reserved_cards[reserved++] = k;
+				}
+			}
+		}
+	}
+
+	return found;
+}
+
+bool find_first_set_in_tableau(Set* set, Card* tableau) {
+	Card a, b, c;
+
+	for (u16 i = 0; i < TABLEAU_SIZE; i++) {
+		a = tableau[i];
+
+		for (u16 j = 0; j < TABLEAU_SIZE; j++) {
+			if (j == i) continue;
+			
+			b = tableau[j];
+
+			for (u16 k = 0; k < TABLEAU_SIZE; k++) {
+				if (k == j || k == i) continue;
+			
+				c = tableau[k];
+
+				if (cards_are_set(a, b, c)) {
+					set->one = a;
+					set->two = b;
+					set->three = c;
+					remove_at_index(TABLEAU_SIZE, tableau, i);
+					u16 j_offset = 0;
+					u16 k_offset = 0;
+
+					if (j > i) {
+						j_offset += 1;
+					}
+
+					if (k > i) {
+						k_offset += 1;
+					}
+
+					if (k > j) {
+						k_offset += 1;
+					}
+
+					remove_at_index(TABLEAU_SIZE, tableau, j - j_offset);
+					remove_at_index(TABLEAU_SIZE, tableau, k - k_offset);
+					return true;
+				}
+			}
+		}
+	}
+}
+
+void print_tableau(Card* tableau) {
+	char tmpstring[32];
+
+	for (u16 i = 0; i < TABLEAU_SIZE; i++) {
+		sprint_card(tmpstring, tableau[i]);
+		printf("%s ", tmpstring);
+
+		if ((i + 1) % 4 == 0) printf("\n");
+	}
+
+	printf("\n");
+}
+
+void test_all_sets_in_tableau(Card* deck) {
+	bool available_cards [DECK_SIZE];
+	u16 num_available = DECK_SIZE;
+
+	for (u16 i = 0; i < DECK_SIZE; i++) {
+		available_cards[i] = true;
+	}
+
+	Card tableau[TABLEAU_SIZE];
+
+	fill_tableau(tableau, deck, available_cards, &num_available);
+
+	printf("==============Single Tableau Test=============\nTableau filled with %d cards:\n", DECK_SIZE - num_available);
+
+	print_tableau(tableau);
+
+	Set sets [4];
+
+	u16 final_sets = find_sets_in_tableau(sets, tableau);
+
+	printf("Found %d sets in above tableau:\n", final_sets);
+	for (u16 i = 0; i < final_sets; i++) {
+		Set set = sets[i];
+		print_set(set);
+	}
+}
+
+void test_full_game(Card* deck) {
+	bool available_cards [DECK_SIZE];
+	u16 num_available = DECK_SIZE;
+
+	for (u16 i = 0; i < DECK_SIZE; i++) {
+		available_cards[i] = true;
+	}
+
+	Card tableau[TABLEAU_SIZE];
+
+	u16 cards_in_tableau;
+	u16 drawn_current;
+	u16 drawn_total;
+	u16 hands_played;
+	u16 sets_found = 0;
+
+	drawn_current = drawn_total = cards_in_tableau = fill_tableau_from_index(0, tableau, deck, available_cards, &num_available);
+	printf("\n\n==============Full Game Test=============\n%d cards drawn to fill tableau (%d left available)\n", drawn_current, num_available);
+	print_tableau(tableau);
+
+	while (drawn_total <= DECK_SIZE - 3) {
+		Set set;
+		bool success = find_first_set_in_tableau(&set, tableau);
+
+		if (success) {
+			sets_found++;
+			printf("Found set: ");
+			print_set(set);
+			printf("\n\n");
+		} else {
+			printf("No sets in this tableau!");
+		}
+
+		drawn_current = fill_tableau_from_index(9, tableau, deck, available_cards, &num_available);
+		printf("%d cards drawn to fill tableau (%d left available)\n", drawn_current, num_available);
+		print_tableau(tableau);
+
+		drawn_total += drawn_current;
+		hands_played++;
+	}
+
+	Set sets [4];
+
+	u16 final_sets = find_sets_in_tableau(sets, tableau);
+
+	sets_found += final_sets;
+
+	printf("\nFound %d sets in final tableau:\n", final_sets);
+	for (u16 i = 0; i < final_sets; i++) {
+		Set set = sets[i];
+		print_set(set);
+	}
+
+	printf("\nFound %d sets total.\n", sets_found);
+	printf("Played %d cards in %d hands.\n", drawn_total, hands_played);
+}
+
+s32 main() {
+	auto now = std::chrono::system_clock::now().time_since_epoch();
+	auto ms = std::chrono::duration_cast<std::chrono::milliseconds>(now).count();
+
+	srand(ms);
+
+	Card deck[DECK_SIZE];
+	
+	u16 count = make_deck(deck);
+
+	test_all_sets_in_tableau(deck);
+	test_full_game(deck);
+
+	return 0;
+}
+