Skip to content

Commit

Permalink
Cleanup and speed-up findSet implementation (#82)
Browse files Browse the repository at this point in the history
  • Loading branch information
eltoder authored Dec 29, 2024
1 parent c7f9204 commit 0ac7876
Show file tree
Hide file tree
Showing 3 changed files with 131 additions and 77 deletions.
77 changes: 42 additions & 35 deletions functions/src/game.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,9 +44,9 @@ function generateDeck(gameMode: GameMode) {
export function checkSet(a: string, b: string, c: string) {
for (let i = 0; i < a.length; i++) {
if ((a.charCodeAt(i) + b.charCodeAt(i) + c.charCodeAt(i)) % 3 !== 0)
return false;
return null;
}
return true;
return [a, b, c];
}

/** Returns the unique card c such that {a, b, c} form a set. */
Expand Down Expand Up @@ -87,52 +87,53 @@ export function checkSetGhost(
d.charCodeAt(i) +
e.charCodeAt(i) +
f.charCodeAt(i);
if (sum % 3 !== 0) return false;
if (sum % 3 !== 0) return null;
}
return true;
return [a, b, c, d, e, f];
}

/** Find a set in an unordered collection of cards, if any, depending on mode. */
export function findSet(deck: string[], gameMode: GameMode, old?: string[]) {
const deckSet = new Set(deck);
const ultraConjugates: Record<string, [string, string]> = {};
for (let i = 0; i < deck.length; i++) {
for (let j = i + 1; j < deck.length; j++) {
const c = conjugateCard(deck[i], deck[j]);
if (
gameMode === "normal" ||
gameMode === "junior" ||
(gameMode === "setchain" && old!.length === 0)
) {
const setType = modes[gameMode].setType;
if (setType === "Set") {
const deckSet = new Set(
gameMode === "setchain" && old!.length > 0 ? old : deck
);
for (let i = 0; i < deck.length; i++) {
for (let j = i + 1; j < deck.length; j++) {
const c = conjugateCard(deck[i], deck[j]);
if (deckSet.has(c)) {
return [deck[i], deck[j], c];
}
} else if (gameMode === "setchain") {
if (old!.includes(c)) {
return [c, deck[i], deck[j]];
}
} else if (gameMode === "ultraset" || gameMode === "ultra9") {
if (c in ultraConjugates) {
return [...ultraConjugates[c], deck[i], deck[j]];
}
}
} else if (setType === "UltraSet") {
const conjugates: Map<string, [string, string]> = new Map();
for (let i = 0; i < deck.length; i++) {
for (let j = i + 1; j < deck.length; j++) {
const c = conjugateCard(deck[i], deck[j]);
if (conjugates.has(c)) {
return [...conjugates.get(c)!, deck[i], deck[j]];
}
ultraConjugates[c] = [deck[i], deck[j]];
} else if (gameMode === "ghostset") {
conjugates.set(c, [deck[i], deck[j]]);
}
}
} else if (setType === "GhostSet") {
for (let i = 0; i < deck.length; i++) {
for (let j = i + 1; j < deck.length; j++) {
for (let k = j + 1; k < deck.length; k++) {
for (let l = k + 1; l < deck.length; l++) {
for (let m = l + 1; m < deck.length; m++) {
for (let n = m + 1; n < deck.length; n++) {
if (
checkSetGhost(
deck[i],
deck[j],
deck[k],
deck[l],
deck[m],
deck[n]
)
) {
return [deck[i], deck[j], deck[k], deck[l], deck[m], deck[n]];
}
const cand = checkSetGhost(
deck[i],
deck[j],
deck[k],
deck[l],
deck[m],
deck[n]
);
if (cand) return cand;
}
}
}
Expand Down Expand Up @@ -199,26 +200,32 @@ function replayEventChain(

const modes = {
normal: {
setType: "Set",
traits: 4,
replayFn: replayEventCommon,
},
junior: {
setType: "Set",
traits: 3,
replayFn: replayEventCommon,
},
setchain: {
setType: "Set",
traits: 4,
replayFn: replayEventChain,
},
ultraset: {
setType: "UltraSet",
traits: 4,
replayFn: replayEventCommon,
},
ultra9: {
setType: "UltraSet",
traits: 4,
replayFn: replayEventCommon,
},
ghostset: {
setType: "GhostSet",
traits: 4,
replayFn: replayEventCommon,
},
Expand Down
80 changes: 41 additions & 39 deletions src/game.js
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@ export function checkSetGhost(a, b, c, d, e, f) {
}

export function addCard(deck, card, gameMode, lastSet) {
const setSize = setTypes[modes[gameMode].setType].size;
let res;
if (gameMode === "setchain" && lastSet.includes(card)) {
// Move the card from lastSet to the front and remove one if it was already there
Expand All @@ -102,7 +103,7 @@ export function addCard(deck, card, gameMode, lastSet) {
} else {
res = [...deck, card];
}
return [res, res.length === modes[gameMode].cardsInSet];
return [res, res.length === setSize];
}

export function removeCard(deck, card) {
Expand All @@ -122,45 +123,46 @@ export function cardTraits(card) {
}

export function findSet(deck, gameMode = "normal", old) {
const deckSet = new Set(deck);
const ultraConjugates = {};
for (let i = 0; i < deck.length; i++) {
for (let j = i + 1; j < deck.length; j++) {
const c = conjugateCard(deck[i], deck[j]);
if (
gameMode === "normal" ||
gameMode === "junior" ||
(gameMode === "setchain" && old.length === 0)
) {
const setType = modes[gameMode].setType;
if (setType === "Set") {
const deckSet = new Set(
gameMode === "setchain" && old.length > 0 ? old : deck
);
for (let i = 0; i < deck.length; i++) {
for (let j = i + 1; j < deck.length; j++) {
const c = conjugateCard(deck[i], deck[j]);
if (deckSet.has(c)) {
return [deck[i], deck[j], c];
}
} else if (gameMode === "setchain") {
if (old.includes(c)) {
return [c, deck[i], deck[j]];
}
} else if (gameMode === "ultraset" || gameMode === "ultra9") {
if (c in ultraConjugates) {
return [...ultraConjugates[c], deck[i], deck[j]];
}
}
} else if (setType === "UltraSet") {
const conjugates = new Map();
for (let i = 0; i < deck.length; i++) {
for (let j = i + 1; j < deck.length; j++) {
const c = conjugateCard(deck[i], deck[j]);
if (conjugates.has(c)) {
return [...conjugates.get(c), deck[i], deck[j]];
}
ultraConjugates[c] = [deck[i], deck[j]];
} else if (gameMode === "ghostset") {
conjugates.set(c, [deck[i], deck[j]]);
}
}
} else if (setType === "GhostSet") {
for (let i = 0; i < deck.length; i++) {
for (let j = i + 1; j < deck.length; j++) {
for (let k = j + 1; k < deck.length; k++) {
for (let l = k + 1; l < deck.length; l++) {
for (let m = l + 1; m < deck.length; m++) {
for (let n = m + 1; n < deck.length; n++) {
if (
checkSetGhost(
deck[i],
deck[j],
deck[k],
deck[l],
deck[m],
deck[n]
)
) {
return [deck[i], deck[j], deck[k], deck[l], deck[m], deck[n]];
}
const cand = checkSetGhost(
deck[i],
deck[j],
deck[k],
deck[l],
deck[m],
deck[n]
);
if (cand) return cand;
}
}
}
Expand Down Expand Up @@ -309,13 +311,18 @@ export function hasHint(game) {
);
}

const setTypes = {
Set: { size: 3 },
UltraSet: { size: 4 },
GhostSet: { size: 6 },
};

export const modes = {
normal: {
name: "Normal",
color: "purple",
description: "Find 3 cards that form a Set.",
setType: "Set",
cardsInSet: 3,
traits: 4,
minBoardSize: 12,
checkFn: checkSet,
Expand All @@ -327,7 +334,6 @@ export const modes = {
description:
"A simplified version that only uses cards with solid shading.",
setType: "Set",
cardsInSet: 3,
traits: 3,
minBoardSize: 9,
checkFn: checkSet,
Expand All @@ -338,7 +344,6 @@ export const modes = {
color: "teal",
description: "In every Set, you have to use 1 card from the previous Set.",
setType: "Set",
cardsInSet: 3,
traits: 4,
minBoardSize: 12,
checkFn: checkSet,
Expand All @@ -350,7 +355,6 @@ export const modes = {
description:
"Find 4 cards such that the first pair and the second pair form a Set with the same additional card.",
setType: "UltraSet",
cardsInSet: 4,
traits: 4,
minBoardSize: 12,
checkFn: checkSetUltra,
Expand All @@ -362,7 +366,6 @@ export const modes = {
description:
"Same as UltraSet, but only 9 cards are dealt at a time, unless they don't contain any sets.",
setType: "UltraSet",
cardsInSet: 4,
traits: 4,
minBoardSize: 9,
checkFn: checkSetUltra,
Expand All @@ -372,9 +375,8 @@ export const modes = {
name: "GhostSet",
color: "lightBlue",
description:
"Find 3 disjoint pairs of cards in which the cards needed to complete each Set also form a Set.",
"Find 3 disjoint pairs of cards such that the cards that complete them to Sets themselves form a Set.",
setType: "GhostSet",
cardsInSet: 6,
traits: 4,
minBoardSize: 10,
checkFn: checkSetGhost,
Expand Down
51 changes: 48 additions & 3 deletions src/game.test.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,10 @@
import { conjugateCard, checkSet, checkSetUltra, findSet } from "./game";
import {
checkSet,
checkSetGhost,
checkSetUltra,
conjugateCard,
findSet,
} from "./game";

it("computes conjugate cards", () => {
expect(conjugateCard("0001", "0002")).toBe("0000");
Expand Down Expand Up @@ -38,15 +44,37 @@ it("checks ultrasets", () => {
verifyUltra(checkSetUltra("1001", "1221", "1010", "1212"));
});

const verifyGhost = (cards) => {
expect(cards).toBeTruthy();
const [a, b, c, d, e, f] = cards;
verifySet([conjugateCard(a, b), conjugateCard(c, d), conjugateCard(e, f)]);
};

it("checks ghostsets", () => {
verifyGhost(checkSetGhost("0001", "0002", "0000", "1201", "1002", "1100"));
verifyGhost(checkSetGhost("1020", "0011", "0020", "0021", "0201", "2120"));
expect(checkSetGhost("1020", "0011", "1020", "0021", "0201", "2120")).toBe(
null
);
verifyGhost(checkSetGhost("1111", "1021", "0102", "2001", "2100", "0001"));
expect(checkSetGhost("1111", "1021", "0102", "2001", "2100", "0201")).toBe(
null
);
verifyGhost(checkSetGhost("0120", "1022", "1012", "0110", "2102", "2000"));
expect(checkSetGhost("0120", "1022", "1012", "0110", "2102", "2020")).toBe(
null
);
});

describe("findSet()", () => {
it("can find normal sets", () => {
for (const deck of [
["0112", "0112", "0112"],
["2012", "0112", "0112", "2011", "0112"],
["1111", "2222", "1010", "2021", "0201", "1021", "1022", "0112"],
]) {
expect(findSet(deck, "normal")).toBeTruthy();
expect(findSet(deck, "setchain", [])).toBeTruthy();
verifySet(findSet(deck, "normal"));
verifySet(findSet(deck, "setchain", []));
}

for (const deck of [
Expand All @@ -73,4 +101,21 @@ describe("findSet()", () => {
verifyUltra(findSet(["1202", "0000", "0001", "0002", "2101"], "ultraset"));
expect(findSet(["1202", "0000", "0001", "0002"], "ultraset")).toBe(null);
});

it("can find ghostsets", () => {
for (const deck of [
["0001", "0002", "0000", "1201", "1002", "1100", "1111"],
["1111", "1021", "0102", "2001", "2100", "0001", "0002", "2222"],
["0001", "0002", "0010", "0020", "0100", "0200", "1111", "2222"],
]) {
verifyGhost(findSet(deck, "ghostset"));
}

for (const deck of [
["0001", "0002", "0000", "1201", "1002"],
["1111", "1021", "0102", "2001", "2100", "1001", "0002"],
]) {
expect(findSet(deck, "ghostset")).toBe(null);
}
});
});

0 comments on commit 0ac7876

Please sign in to comment.