// The deck, as the room draws it.
//
// A card is drawn, not typed. The first attempt set the pips as text — "♠" in a
// span — and at the size a card actually is, a suit character renders as a speck:
// the shape is whatever font happened to answer, it doesn't scale, and it can't
// be positioned to the half-row a real pip layout needs.
//
// So each face is one SVG on a 100×140 field (the proportions of a real card),
// with the suits as vector shapes. Everything below is coordinates on that field,
// which is why the pips land where a printed deck puts them instead of where a
// flexbox felt like putting them.
//
// This started life inside blackjack.js. It's out here because solitaire deals
// off the same deck, and the second table in a casino is exactly the moment a
// copied card renderer becomes two card renderers that drift.
//
// Exposed as window.PeteCards. Nothing in here knows what game is being played.
(function () {
"use strict";
var SUIT_ART = {
"♠": '',
"♥": '',
"♦": '',
"♣": '' +
'',
};
// Pip layouts, the way a real deck lays them out — which is not "N suits in a
// row". [x, y] on the 100×140 field. The seven canonical rows sit at y = 27,
// 41, 56, 70, 84, 99, 113; sevens, eights and tens carry a pip *between* two of
// them, which is the whole reason this is a table of coordinates and not a
// grid. Anything below the middle is printed upside down, so it is.
var R = [0, 27, 41.4, 55.7, 70, 84.3, 98.6, 113]; // 1-indexed, R[4] is the middle
var L = 30, C = 50, Rr = 70; // the three columns
var PIPS = {
"A": [[C, 70, 2.1]],
"2": [[C, R[1]], [C, R[7]]],
"3": [[C, R[1]], [C, R[4]], [C, R[7]]],
"4": [[L, R[1]], [Rr, R[1]], [L, R[7]], [Rr, R[7]]],
"5": [[L, R[1]], [Rr, R[1]], [C, R[4]], [L, R[7]], [Rr, R[7]]],
"6": [[L, R[1]], [Rr, R[1]], [L, R[4]], [Rr, R[4]], [L, R[7]], [Rr, R[7]]],
"7": [[L, R[1]], [Rr, R[1]], [C, 48.5], [L, R[4]], [Rr, R[4]], [L, R[7]], [Rr, R[7]]],
"8": [[L, R[1]], [Rr, R[1]], [C, 48.5], [L, R[4]], [Rr, R[4]], [C, 91.5], [L, R[7]], [Rr, R[7]]],
"9": [[L, R[1]], [Rr, R[1]], [L, R[3]], [Rr, R[3]], [C, R[4]], [L, R[5]], [Rr, R[5]], [L, R[7]], [Rr, R[7]]],
"10": [[L, R[1]], [Rr, R[1]], [C, 48.5], [L, R[3]], [Rr, R[3]], [L, R[5]], [Rr, R[5]], [C, 91.5], [L, R[7]], [Rr, R[7]]],
};
var COURT = { "J": "Jack", "Q": "Queen", "K": "King" };
var SUIT_NAMES = { "♠": "spades", "♥": "hearts", "♦": "diamonds", "♣": "clubs" };
// One pip: the suit art, scaled and dropped at [x, y], turned over if it sits
// below the middle of the card.
function pipAt(suit, x, y, scale) {
var s = (scale || 1) * 0.17;
var turn = y > 70 ? " rotate(180 50 50)" : "";
return '' +
SUIT_ART[suit] + "";
}
// The corner index: rank over suit. Printed in both corners, the second one
// upside down, which is what lets you read a card from a fanned hand — and in
// solitaire, from a column where all you can see is the top eighth of it.
function index(face) {
var g =
'' +
'' + face.rank + "" +
'' + SUIT_ART[face.suit] + "" +
"";
return g + '' + g + "";
}
// paint draws the face. Every table uses this one, because they all deal out of
// the same deck.
function paint(front, face) {
front.dataset.red = face.red ? "1" : "0";
var body = "";
if (COURT[face.rank]) {
// Court cards: a framed panel, the suit above the letter and again below it
// the other way up. A real court mirrors a *figure*; mirroring a letter just
// stacks two of them into a blob, which is exactly what the first attempt
// did. No portrait either — a drawn king would fight the room, and this
// reads instantly at the size a card actually is.
body =
'' +
pipAt(face.suit, 50, 38, 0.95) +
'' + face.rank + "" +
pipAt(face.suit, 50, 102, 0.95);
} else {
var spots = PIPS[face.rank] || [];
for (var i = 0; i < spots.length; i++) {
body += pipAt(face.suit, spots[i][0], spots[i][1], spots[i][2]);
}
}
front.innerHTML =
'";
}
// "A♠" is not something a screen reader should be asked to pronounce.
function aria(face) {
var name = COURT[face.rank] || (face.rank === "A" ? "Ace" : face.rank);
var suit = SUIT_NAMES[face.suit];
return suit ? name + " of " + suit : face.label;
}
var dealt = 0; // how many cards this page has put down, ever — the tilt seed
// el builds one card. face === null means face-down: the card is on the table,
// but this browser has not been told what it is.
//
// opts.deal — fly it in from the shoe (blackjack). Solitaire turns this off:
// it animates its own cards from wherever they actually came from,
// and a board that re-renders would otherwise re-deal itself out of
// the corner on every single move.
// opts.tilt — a degree or two of resting angle. A dealt hand wants it; a
// solitaire column does not, because thirteen tilted cards stacked
// an eighth of an inch apart read as a mistake rather than as a
// hand that was handled.
function el(face, opts) {
opts = opts || {};
var deal = opts.deal !== false;
var tilt = opts.tilt !== false;
var wrap = document.createElement("div");
wrap.className = "pete-card";
wrap.dataset.face = face ? "up" : "down";
if (face) wrap.dataset.key = face.label; // one deck, so the label is an id
if (deal) {
// Every card flies out of the shoe, which sits in the top-right of the felt.
wrap.style.setProperty("--deal-x", "14rem");
wrap.style.setProperty("--deal-y", "-6rem");
} else {
wrap.style.animation = "none";
}
wrap.style.setProperty("--tilt", tilt ? window.PeteFX.jitter(dealt++, 2.4).toFixed(2) + "deg" : "0deg");
var inner = document.createElement("div");
inner.className = "pete-card-inner";
var front = document.createElement("div");
front.className = "pete-card-front";
var back = document.createElement("div");
back.className = "pete-card-back";
inner.appendChild(front);
inner.appendChild(back);
wrap.appendChild(inner);
if (face) paint(front, face);
return wrap;
}
// turnOver flips a face-down card up, now that we've been told what it is.
//
// Unlike el(), this is only ever a *gesture* — a card that was down and is now
// up, because something happened. It never runs on a repaint, which is why the
// flip sound can live here and be right for every table at once, and why the
// deal sound cannot: el() runs every time a board is redrawn.
function turnOver(wrap, face) {
if (!wrap) return;
paint(wrap.querySelector(".pete-card-front"), face);
wrap.dataset.face = "up";
wrap.dataset.key = face.label;
if (window.PeteSFX) window.PeteSFX.play("flip");
}
window.PeteCards = {
el: el,
paint: paint,
turnOver: turnOver,
aria: aria,
art: SUIT_ART,
};
})();