title: Programming the Set Card Game date: 2025-08-25
description: Building the Set card game - deck generation, set detection, and SVG rendering
The game "Set" is a lovely game of pattern recognition and also teaches the player to be careful with their impulse to yell "set!" when they see one. Let's program the game.
Generate the Deck
A deck of Set cards have 4 properties, each with 3 values. So we store each card as an array with 4 elements, each element can be an integer from 0 to 2.
js
// Card as array: [number, symbol, shading, color]
function generateDeck() {
const deck = [];
for (let a = 0; a < 3; a++) {
for (let b = 0; b < 3; b++) {
for (let c = 0; c < 3; c++) {
for (let d = 0; d < 3; d++) {
deck.push([a, b, c, d]);
}
}
}
}
return deck; // Total of 81 cards
}
We also need a way to shuffle, we choose to do this in place, to save a little memory:
js
// Shuffle the deck using Fisher-Yates algorithm
function shuffle(deck) {
for (let i = deck.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1));
[deck[i], deck[j]] = [deck[j], deck[i]];
}
}
Finding a set
A set in Set is defined as 3 cards where every property is either the same or different. This implementation is somewhat clever since it uses JavaScripts Set to find a set, by using it to check when only 2 properties are the same, and so not a set:
js
function isSet(card1, card2, card3) {
for (let i = 0; i < 4; i++) {
const values = new Set([card1[i], card2[i], card3[i]]);
// either all same (size 1) or all different (size 3)
if (values.size === 2) {
return false;
}
}
return true;
}
Finding the sets on a board
A typical board will have 12 or 15 cards on it. When you play IRL these will be spread out as a grid; but for this program, we'll use a simple array representation. This implementation uses 3 loops, the first "primary" card, a "secondary" card later in the array, and a third card, later in the array still :
js
function findSetsOnBoard(board) {
const sets = [];
for (let i = 0; i < board.length; i++) {
for (let j = i + 1; j < board.length; j++) {
for (let k = j + 1; k < board.length; k++) {
if (isSet(board[i], board[j], board[k])) {
sets.push([board[i], board[j], board[k]]);
}
}
}
}
return sets;
}
Rendering a card
html
<svg xmlns="http://www.w3.org/2000/svg" width="120" height="180" viewBox="0 0 120 180">
<defs>
<pattern id="stripes" patternUnits="userSpaceOnUse" width="6" height="6">
<rect width="3" height="6" fill="#d22" opacity="0.45"/>
<rect x="3" width="3" height="6" fill="white" />
</pattern>
</defs>
<rect x="0" y="0" width="120" height="180" rx="18" fill="white" stroke="#aaa" stroke-width="4"/>
<path d="
M 38 64
Q 50 32, 70 44
Q 85 54, 82 42
Q 65 66, 38 44
Z"
stroke="#d22" stroke-width="4" fill="url(#stripes)" />
<path d="
M 38 100
Q 50 68, 70 80
Q 85 90, 82 78
Q 65 102, 38 80
Z"
stroke="#d22" stroke-width="4" fill="url(#stripes)" />
<path d="
M 38 136
Q 50 104, 70 116
Q 85 126, 82 114
Q 65 138, 38 116
Z"
stroke="#d22" stroke-width="4" fill="url(#stripes)" />
</svg>
js
function cardToSVG([number, symbol, shading, color], width = 120, height = 180) {
// Color palette: red, green, purple
const colors = ['#d22', '#292', '#828'];
// Y positions for symbols
const symbolYs = [
[height / 2],
[height / 2 - 24, height / 2 + 24],
[height / 2 - 36, height / 2, height / 2 + 36]
][number];
// Helper: Symbol shape SVG snippets
function renderShape(cx, cy) {
switch (symbol) {
// Oval
case 0: return `<ellipse cx="${cx}" cy="${cy}" rx="32" ry="16" stroke="${colors[color]}"
stroke-width="4" fill="${getFill(colors[color])}" />`;
// Diamond
case 1: return `<polygon points="${cx-28},${cy} ${cx},${cy-17} ${cx+28},${cy} ${cx},${cy+17}"
stroke="${colors[color]}" stroke-width="4" fill="${getFill(colors[color])}" />`;
// Squiggle (approximated as a ribbon curve)
case 2: return `<path d="
M ${cx-22} ${cy+10}
Q ${cx-10} ${cy-22}, ${cx+10} ${cy-10}
Q ${cx+25} ${cy}, ${cx+22} ${cy-12}
Q ${cx+5} ${cy+12}, ${cx-22} ${cy-10}
Z"
stroke="${colors[color]}" stroke-width="4" fill="${getFill(colors[color])}" />`;
}
}
// Helper: Shading fill (solid, striped, open)
function getFill(baseColor) {
if (shading === 0) return baseColor; // solid
if (shading === 1) return 'url(#stripes)'; // striped
return 'none'; // open
}
// Stripe pattern for striped shading
const stripePattern = `
<pattern id="stripes" patternUnits="userSpaceOnUse" width="6" height="6">
<rect width="3" height="6" fill="${colors[color]}" opacity="0.45"/>
<rect x="3" width="3" height="6" fill="white" />
</pattern>
`;
// Card symbols
const symbols = symbolYs.map(y => renderShape(width/2, y)).join('\n');
return `
<svg xmlns="http://www.w3.org/2000/svg" width="${width}" height="${height}" viewBox="0 0 ${width} ${height}">
<defs>
${shading === 1 ? stripePattern : ''}
</defs>
<rect x="0" y="0" width="${width}" height="${height}" rx="18" fill="white" stroke="#aaa" stroke-width="4"/>
${symbols}
</svg>
`.trim();
}
// Example usage:
const svgString = cardToSVG([2, 2, 1, 0]); // three diamonds, striped, red
console.log(svgString);
© 2026 simpatico