fertig-classic-games/src/games/slots/SlotsSymbols.js

598 lines
25 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// Procedural reel-symbol painters for Slot Machines.
//
// Every symbol is painted once into a 200×200 baked texture
// ('slots-sym-{machineId}-{symbolId}') via a RenderTexture, so reels and
// tumbles animate plain Images. If the user has supplied drop-in art
// ('slots-{machineId}-{symbolId}', loaded by PreloadScene from
// slots-artwork.json), textureKeyFor() prefers it — zero code changes.
const S = 200; // texture size
const C = S / 2; // center
export function dropInKey(machineId, symbolId) { return `slots-${machineId}-${symbolId}`; }
export function bakedKey(machineId, symbolId) { return `slots-sym-${machineId}-${symbolId}`; }
export function textureKeyFor(scene, machineId, symbolId) {
const k = dropInKey(machineId, symbolId);
return scene.textures.exists(k) ? k : bakedKey(machineId, symbolId);
}
export function allSymbolIds(machine) {
const ids = new Set();
for (const strip of machine.strips) for (const s of strip) ids.add(s);
if (machine.wild) ids.add(machine.wild.symbol);
if (machine.features.rainbow) ids.add(machine.features.rainbow.symbol);
return [...ids];
}
// ── Drawing primitives ────────────────────────────────────────────────────────
const P = (arr) => arr.map(([x, y]) => ({ x, y }));
function starPts(cx, cy, spikes, outer, inner, rot = -Math.PI / 2) {
const out = [];
for (let i = 0; i < spikes * 2; i++) {
const r = i % 2 === 0 ? outer : inner;
const a = rot + (i * Math.PI) / spikes;
out.push({ x: cx + Math.cos(a) * r, y: cy + Math.sin(a) * r });
}
return out;
}
function heartPath(g, cx, cy, size, color) {
g.fillStyle(color, 1);
g.fillCircle(cx - size * 0.35, cy - size * 0.25, size * 0.4);
g.fillCircle(cx + size * 0.35, cy - size * 0.25, size * 0.4);
g.fillPoints(P([
[cx - size * 0.72, cy - size * 0.08],
[cx + size * 0.72, cy - size * 0.08],
[cx, cy + size * 0.75],
]), true);
}
function barShape(g, cy, label, addText, color = 0x14100a, textColor = '#f6efe0') {
g.fillStyle(color, 1);
g.fillRoundedRect(C - 72, cy - 22, 144, 44, 10);
g.lineStyle(4, 0xc8a84b, 1);
g.strokeRoundedRect(C - 72, cy - 22, 144, 44, 10);
addText(label, { x: C, y: cy, size: 30, color: textColor, font: 'Righteous' });
}
// ── Painters ─────────────────────────────────────────────────────────────────
// Keyed '{machineId}/{symbolId}' with a '{symbolId}' fallback for shared art.
// Each receives ({ g, text, theme }).
const PAINTERS = {
// ··· shared card royals ···
ace: cardLetter('A'), king: cardLetter('K'), queen: cardLetter('Q'), jack: cardLetter('J'),
// ··· Liberty Belle ···
bell({ g }) {
g.fillStyle(0xd9a93b, 1);
g.fillCircle(C, 64, 16);
g.fillPoints(P([[C - 14, 64], [C + 14, 64], [C + 52, 138], [C - 52, 138]]), true);
g.fillCircle(C, 96, 44);
g.fillStyle(0xb07f1f, 1);
g.fillRoundedRect(C - 62, 132, 124, 16, 7);
g.fillStyle(0x7a5410, 1);
g.fillCircle(C, 158, 12);
g.fillStyle(0xf7e3a1, 0.8);
g.fillEllipse(C - 22, 86, 14, 34);
},
horseshoe({ g }) {
g.lineStyle(26, 0x9aa3ad, 1);
g.beginPath();
g.arc(C, 92, 56, Math.PI * 0.95, Math.PI * 2.05, false);
g.strokePath();
g.lineStyle(26, 0x9aa3ad, 1);
g.beginPath(); g.moveTo(C - 54, 76); g.lineTo(C - 60, 142); g.strokePath();
g.beginPath(); g.moveTo(C + 54, 76); g.lineTo(C + 60, 142); g.strokePath();
g.fillStyle(0x5d666f, 1);
for (const [x, y] of [[C - 58, 138], [C + 58, 138], [C - 50, 60], [C + 50, 60], [C, 36]]) g.fillCircle(x, y, 6);
},
star({ g }) {
g.fillStyle(0xe8b733, 1);
g.fillPoints(starPts(C, C + 6, 5, 74, 30), true);
g.fillStyle(0xfbe8a6, 0.85);
g.fillPoints(starPts(C, C + 6, 5, 36, 15), true);
},
heart({ g }) { heartPath(g, C, C + 4, 92, 0xc0392b); g.fillStyle(0xf2b8ad, 0.7); g.fillCircle(C - 30, 72, 13); },
diamond({ g }) {
g.fillStyle(0xd35400, 1);
g.fillPoints(P([[C, 26], [C + 64, C + 4], [C, 178], [C - 64, C + 4]]), true);
g.fillStyle(0xf8c471, 0.75);
g.fillPoints(P([[C, 52], [C + 34, C + 4], [C, 152], [C - 34, C + 4]]), true);
},
spade({ g }) {
heartPath(g, C, 84, 84, 0x232b36);
// flip illusion: heart drawn upward + stem
g.fillStyle(0x232b36, 1);
g.fillPoints(P([[C - 7, 120], [C + 7, 120], [C + 22, 168], [C - 22, 168]]), true);
},
// ··· Fruit Frenzy ···
cherry({ g }) {
g.lineStyle(8, 0x3f7a2a, 1);
g.beginPath(); g.moveTo(C - 26, 132); g.lineTo(C + 2, 44); g.strokePath();
g.beginPath(); g.moveTo(C + 38, 124); g.lineTo(C + 2, 44); g.strokePath();
g.fillStyle(0x4f9a34, 1);
g.fillEllipse(C + 26, 48, 44, 20);
g.fillStyle(0xc62b33, 1);
g.fillCircle(C - 30, 142, 30);
g.fillCircle(C + 42, 134, 30);
g.fillStyle(0xf0888d, 0.8);
g.fillCircle(C - 40, 132, 9); g.fillCircle(C + 32, 124, 9);
},
lemon({ g }) {
g.fillStyle(0xeed02a, 1);
g.fillEllipse(C, C + 8, 132, 92);
g.fillCircle(C - 70, C + 8, 14); g.fillCircle(C + 70, C + 8, 14);
g.fillStyle(0xfbf0a0, 0.7); g.fillEllipse(C - 26, C - 8, 38, 20);
},
orange({ g }) {
g.fillStyle(0xe87f1e, 1); g.fillCircle(C, C + 10, 62);
g.fillStyle(0x4f9a34, 1); g.fillEllipse(C + 24, 48, 40, 18);
g.fillStyle(0xf6b66e, 0.7); g.fillCircle(C - 22, C - 8, 16);
},
plum({ g }) {
g.fillStyle(0x6e3a8c, 1); g.fillCircle(C, C + 10, 60);
g.lineStyle(5, 0x4d2363, 1);
g.beginPath(); g.arc(C - 6, C + 10, 52, -Math.PI * 0.45, Math.PI * 0.45); g.strokePath();
g.fillStyle(0x4f9a34, 1); g.fillEllipse(C + 22, 46, 38, 16);
g.fillStyle(0xb88ed1, 0.7); g.fillCircle(C - 24, C - 6, 14);
},
melon({ g }) {
g.fillStyle(0x2f7a33, 1);
g.slice(C, 64, 92, 0, Math.PI, false); g.fillPath();
g.fillStyle(0xe8536a, 1);
g.slice(C, 64, 76, 0, Math.PI, false); g.fillPath();
g.fillStyle(0x21262b, 1);
for (const [x, y] of [[C - 36, 96], [C, 116], [C + 36, 96], [C - 14, 84], [C + 16, 88]]) {
g.fillEllipse(x, y, 8, 13);
}
},
bar({ g, text }) { barShape(g, C - 34, 'BAR', text); barShape(g, C + 34, 'BAR', text); },
// ··· Lucky Sevens ···
'lucky-sevens/wild': ({ g, text }) => {
g.fillStyle(0x101319, 1);
g.fillPoints(P([[C, 30], [C + 70, C], [C, 170], [C - 70, C]]), true);
g.lineStyle(6, 0x8fd8ff, 1);
g.strokePoints(P([[C, 42], [C + 56, C], [C, 158], [C - 56, C], [C, 42]]), true);
g.lineStyle(3, 0x8fd8ff, 0.7);
g.strokePoints(P([[C, 66], [C + 32, C], [C, 134], [C - 32, C], [C, 66]]), true);
text('WILD', { x: C, y: C, size: 26, color: '#8fd8ff', font: 'Righteous' });
},
'seven-red': seven('#d4313f'),
'seven-white': seven('#f4f1e8'),
'seven-blue': seven('#4f8fe0'),
bar1({ g, text }) { barShape(g, C, 'BAR', text); },
bar2({ g, text }) { barShape(g, C - 28, 'BAR', text); barShape(g, C + 28, 'BAR', text); },
bar3({ g, text }) { barShape(g, C - 52, 'BAR', text); barShape(g, C, 'BAR', text); barShape(g, C + 52, 'BAR', text); },
blank({ g }) { g.fillStyle(0x000000, 0.06); g.fillRoundedRect(C - 50, C - 6, 100, 12, 6); },
// ··· Pharaoh's Fortune ···
pharaoh({ g, text }) {
g.fillStyle(0xe6b94d, 1);
g.fillRoundedRect(C - 44, 40, 88, 96, 18);
g.fillPoints(P([[C - 44, 60], [C - 72, 130], [C - 44, 122]]), true);
g.fillPoints(P([[C + 44, 60], [C + 72, 130], [C + 44, 122]]), true);
g.fillStyle(0x1c2c4e, 1);
for (let i = 0; i < 3; i++) g.fillRect(C - 44 + 10, 48 + i * 16, 68, 7);
g.fillStyle(0x18120a, 1);
g.fillEllipse(C - 18, 104, 18, 9); g.fillEllipse(C + 18, 104, 18, 9);
g.fillStyle(0xe6b94d, 1);
g.fillRoundedRect(C - 12, 130, 24, 34, 8);
text('WILD', { x: C, y: 182, size: 22, color: '#ffd86b', font: 'Righteous' });
},
pyramid({ g, text }) {
g.fillStyle(0xc99b3f, 1);
g.fillPoints(P([[C, 34], [C + 78, 148], [C - 78, 148]]), true);
g.fillStyle(0xf7df9a, 1);
g.fillPoints(P([[C, 34], [C + 22, 66], [C - 22, 66]]), true);
g.lineStyle(3, 0x8a6722, 0.8);
for (let i = 1; i < 4; i++) {
const y = 66 + i * 22; const w = 22 + i * 19;
g.beginPath(); g.moveTo(C - w, y); g.lineTo(C + w, y); g.strokePath();
}
text('BONUS', { x: C, y: 174, size: 22, color: '#ffd86b', font: 'Righteous' });
},
sphinx({ g }) {
g.fillStyle(0xd2a648, 1);
g.fillPoints(P([[40, 150], [40, 120], [78, 112], [86, 70], [122, 70], [128, 112], [164, 118], [164, 150]]), true);
g.fillRoundedRect(78, 52, 48, 52, 10);
g.fillStyle(0x1c2c4e, 1);
g.fillRect(82, 60, 8, 36); g.fillRect(114, 60, 8, 36);
g.fillStyle(0x18120a, 1);
g.fillEllipse(96, 78, 7, 4); g.fillEllipse(110, 78, 7, 4);
},
ankh({ g }) {
g.lineStyle(20, 0xe6b94d, 1);
g.strokeEllipse(C, 64, 52, 64);
g.beginPath(); g.moveTo(C, 96); g.lineTo(C, 170); g.strokePath();
g.beginPath(); g.moveTo(C - 44, 118); g.lineTo(C + 44, 118); g.strokePath();
},
eye({ g }) {
g.lineStyle(11, 0x3f6fc4, 1);
g.beginPath(); g.arc(C, C - 4, 56, Math.PI * 1.15, Math.PI * 1.85); g.strokePath();
g.beginPath(); g.arc(C, C - 30, 60, Math.PI * 0.12, Math.PI * 0.88); g.strokePath();
g.fillStyle(0x18120a, 1); g.fillCircle(C, C - 8, 20);
g.fillStyle(0x3f6fc4, 1); g.fillCircle(C, C - 8, 11);
g.lineStyle(9, 0x3f6fc4, 1);
g.beginPath(); g.moveTo(C - 34, C + 28); g.lineTo(C - 48, C + 64); g.strokePath();
g.beginPath(); g.moveTo(C + 30, C + 26); g.lineTo(C + 34, C + 58); g.strokePath();
g.beginPath(); g.arc(C + 18, C + 58, 16, Math.PI * 1.5, Math.PI * 0.8); g.strokePath();
},
cat({ g }) {
g.fillStyle(0x1b1410, 1);
g.fillPoints(P([[C - 26, 56], [C - 32, 24], [C - 8, 44]]), true);
g.fillPoints(P([[C + 26, 56], [C + 32, 24], [C + 8, 44]]), true);
g.fillCircle(C, 64, 30);
g.fillEllipse(C, 130, 64, 76);
g.fillStyle(0xe6b94d, 1);
g.fillRoundedRect(C - 26, 88, 52, 10, 5);
g.fillStyle(0x57f287, 1);
g.fillEllipse(C - 12, 60, 8, 11); g.fillEllipse(C + 12, 60, 8, 11);
},
// ··· Abyssal Treasures ···
pearl({ g, text }) {
g.fillStyle(0x2b5e6e, 1);
g.slice(C, 128, 74, Math.PI, Math.PI * 2, false); g.fillPath();
g.lineStyle(5, 0x59e7ff, 0.8);
for (let i = -2; i <= 2; i++) {
g.beginPath(); g.moveTo(C, 128); g.lineTo(C + i * 30, 62); g.strokePath();
}
g.fillStyle(0xf2f6f4, 1); g.fillCircle(C, 108, 30);
g.fillStyle(0xffffff, 0.9); g.fillCircle(C - 10, 98, 9);
text('WILD', { x: C, y: 176, size: 22, color: '#59e7ff', font: 'Righteous' });
},
mermaid({ g }) {
g.fillStyle(0x2fae9b, 1);
g.fillPoints(P([[C - 8, 30], [C + 26, 78], [C + 10, 128], [C - 26, 96]]), true);
g.fillPoints(P([[C - 2, 110], [C - 44, 168], [C - 6, 156], [C + 6, 176], [C + 26, 142]]), true);
g.lineStyle(4, 0x9af0e2, 0.8);
for (let i = 0; i < 3; i++) {
g.beginPath(); g.arc(C - 2 + i * 4, 66 + i * 22, 16, Math.PI * 0.15, Math.PI * 0.85); g.strokePath();
}
},
octopus({ g }) {
g.fillStyle(0x8857b8, 1);
g.slice(C, 90, 50, Math.PI, Math.PI * 2, false); g.fillPath();
g.fillRect(C - 50, 88, 100, 14);
g.lineStyle(14, 0x8857b8, 1);
for (const [dx, sw] of [[-38, 1], [-13, -1], [13, 1], [38, -1]]) {
g.beginPath();
g.moveTo(C + dx, 100);
g.lineTo(C + dx + sw * 4, 130);
g.arc(C + dx + sw * 18, 134, 15, Math.PI, Math.PI * (sw > 0 ? 1.9 : 0.1), sw < 0);
g.strokePath();
}
g.fillStyle(0xf2f6f4, 1); g.fillCircle(C - 18, 76, 11); g.fillCircle(C + 18, 76, 11);
g.fillStyle(0x18120a, 1); g.fillCircle(C - 16, 78, 5); g.fillCircle(C + 20, 78, 5);
},
seahorse({ g }) {
g.lineStyle(22, 0xe8913e, 1);
g.beginPath(); g.arc(C + 6, 70, 30, Math.PI * 1.1, Math.PI * 2.2); g.strokePath();
g.beginPath(); g.arc(C - 4, 124, 28, Math.PI * 0.3, Math.PI * 1.35, true); g.strokePath();
g.lineStyle(14, 0xe8913e, 1);
g.beginPath(); g.arc(C + 18, 152, 18, Math.PI * 1.2, Math.PI * 0.5); g.strokePath();
g.fillStyle(0xe8913e, 1);
g.fillPoints(P([[C + 28, 46], [C + 58, 38], [C + 36, 62]]), true);
g.fillStyle(0x18120a, 1); g.fillCircle(C + 16, 56, 6);
},
angler({ g }) {
g.fillStyle(0x27445c, 1);
g.fillEllipse(C, 118, 120, 84);
g.fillPoints(P([[C + 52, 118], [C + 88, 92], [C + 88, 144]]), true);
g.fillStyle(0xf2f6f4, 1);
g.fillPoints(P([[C - 52, 112], [C - 34, 112], [C - 43, 130]]), true);
g.fillPoints(P([[C - 28, 114], [C - 10, 114], [C - 19, 132]]), true);
g.lineStyle(5, 0xbfe85a, 1);
g.beginPath(); g.moveTo(C - 30, 84); g.arc(C - 52, 62, 26, Math.PI * 0.3, Math.PI * 1.2, true); g.strokePath();
g.fillStyle(0xeaff6e, 1); g.fillCircle(C - 74, 52, 12);
g.fillStyle(0xeaff6e, 0.25); g.fillCircle(C - 74, 52, 24);
g.fillStyle(0xf2f6f4, 1); g.fillCircle(C + 6, 96, 10);
g.fillStyle(0x18120a, 1); g.fillCircle(C + 8, 98, 5);
},
starfish({ g }) {
g.fillStyle(0xee7d68, 1);
g.fillPoints(starPts(C, C, 5, 80, 34), true);
g.fillStyle(0xf8c5ae, 0.9);
for (const { x, y } of starPts(C, C, 5, 48, 48)) g.fillCircle(x, y, 6);
g.fillCircle(C, C, 8);
},
shell({ g }) {
g.fillStyle(0xefb9a2, 1);
g.slice(C, 150, 86, Math.PI * 1.08, Math.PI * 1.92, false); g.fillPath();
g.lineStyle(5, 0xc4815f, 1);
for (let i = -2; i <= 2; i++) {
g.beginPath(); g.moveTo(C, 150); g.lineTo(C + i * 32, 70 + Math.abs(i) * 12); g.strokePath();
}
g.fillStyle(0xc4815f, 1); g.fillRoundedRect(C - 18, 146, 36, 18, 8);
},
coral({ g }) {
g.lineStyle(16, 0xd64f8e, 1);
g.beginPath(); g.moveTo(C, 170); g.lineTo(C, 110); g.lineTo(C - 30, 70); g.strokePath();
g.beginPath(); g.moveTo(C, 122); g.lineTo(C + 36, 80); g.lineTo(C + 36, 48); g.strokePath();
g.beginPath(); g.moveTo(C - 30, 70); g.lineTo(C - 54, 44); g.strokePath();
g.beginPath(); g.moveTo(C - 30, 70); g.lineTo(C - 6, 38); g.strokePath();
g.fillStyle(0xf08ab6, 1);
for (const [x, y] of [[C - 54, 44], [C - 6, 38], [C + 36, 48]]) g.fillCircle(x, y, 10);
},
bubble({ g }) {
g.fillStyle(0x6fc4e8, 0.25); g.fillCircle(C, C, 62);
g.lineStyle(7, 0x9fdcf2, 1); g.strokeCircle(C, C, 62);
g.fillStyle(0xffffff, 0.85); g.fillEllipse(C - 24, C - 26, 18, 12);
g.fillStyle(0x6fc4e8, 0.4); g.fillCircle(C + 38, C + 44, 12);
},
// ··· Dragon's Hoard ···
dragon({ g, text }) {
g.fillStyle(0xb83232, 1);
g.fillPoints(P([[C - 70, 96], [C - 4, 58], [C + 64, 76], [C + 64, 108], [C - 2, 132], [C - 36, 120]]), true);
g.fillPoints(P([[C - 4, 58], [C + 10, 24], [C + 30, 60]]), true); // horn
g.fillPoints(P([[C + 28, 60], [C + 52, 36], [C + 56, 70]]), true); // horn 2
g.fillStyle(0xe8483f, 1);
g.fillPoints(P([[C - 70, 96], [C - 36, 120], [C - 58, 128]]), true); // jaw
g.fillStyle(0xffd34d, 1); g.fillEllipse(C + 18, 84, 18, 12);
g.fillStyle(0x18120a, 1); g.fillEllipse(C + 18, 84, 5, 10);
text('WILD', { x: C, y: 168, size: 24, color: '#ff9d4d', font: 'Righteous' });
},
coin({ g }) {
g.fillStyle(0x8a5e12, 1); g.fillCircle(C, C, 70);
g.fillStyle(0xf0c441, 1); g.fillCircle(C, C - 4, 66);
g.lineStyle(5, 0xb8860b, 1); g.strokeCircle(C, C - 4, 50);
g.fillStyle(0xb8860b, 1);
g.fillPoints(starPts(C, C - 4, 4, 34, 12, 0), true);
g.fillStyle(0xfbe8a6, 0.9); g.fillEllipse(C - 26, C - 30, 16, 9);
},
knight({ g }) {
g.fillStyle(0x8d99ae, 1);
g.slice(C, 96, 52, Math.PI, Math.PI * 2, false); g.fillPath();
g.fillRect(C - 52, 94, 104, 38);
g.fillStyle(0x18120a, 1);
g.fillRect(C - 40, 100, 80, 9);
for (let i = -1; i <= 1; i++) g.fillRect(C + i * 22 - 3, 112, 6, 16);
g.fillStyle(0xe8483f, 1);
g.fillPoints(P([[C - 4, 44], [C + 4, 44], [C + 18, 14], [C - 18, 14]]), true);
},
castle({ g }) {
g.fillStyle(0x9aa3ad, 1);
g.fillRect(C - 64, 84, 128, 80);
g.fillRect(C - 78, 64, 30, 100); g.fillRect(C + 48, 64, 30, 100);
for (const x of [C - 78, C - 64, C - 24, C + 8, C + 48, C + 64]) g.fillRect(x, 48, 16, 18);
g.fillStyle(0x3b3f46, 1);
g.slice(C, 164, 26, Math.PI, Math.PI * 2, false); g.fillPath();
g.fillRect(C - 26, 162, 52, 4);
g.fillStyle(0x18120a, 0.8);
g.fillRect(C - 66, 96, 10, 16); g.fillRect(C + 56, 96, 10, 16);
},
chest({ g }) {
g.fillStyle(0x7a4a22, 1);
g.fillRoundedRect(C - 64, 92, 128, 70, 10);
g.slice(C, 92, 64, Math.PI, Math.PI * 2, false); g.fillPath();
g.fillStyle(0xf0c441, 1);
g.fillRect(C - 64, 86, 128, 12);
g.fillRect(C - 10, 40, 20, 122);
g.fillRoundedRect(C - 16, 100, 32, 30, 6);
g.fillStyle(0x18120a, 1); g.fillCircle(C, 112, 7);
g.fillStyle(0xfbe8a6, 1);
for (const [x, y] of [[C - 40, 38], [C + 38, 42], [C - 6, 26]]) g.fillCircle(x, y, 7);
},
gem({ g }) {
g.fillStyle(0x2e9e63, 1);
g.fillPoints(P([[C - 56, 78], [C - 28, 48], [C + 28, 48], [C + 56, 78], [C, 156]]), true);
g.lineStyle(4, 0x9ff2c6, 0.9);
g.strokePoints(P([[C - 56, 78], [C + 56, 78]]), false);
g.strokePoints(P([[C - 28, 48], [C - 18, 78], [C, 156]]), false);
g.strokePoints(P([[C + 28, 48], [C + 18, 78], [C, 156]]), false);
},
// ··· Gold Rush Gulch ···
bandit({ g, text }) {
g.fillStyle(0x2b211a, 1);
g.fillEllipse(C, 70, 124, 20);
g.fillRoundedRect(C - 38, 30, 76, 44, 14);
g.fillStyle(0xd9b48a, 1);
g.slice(C, 76, 38, 0, Math.PI, false); g.fillPath();
g.fillStyle(0xb8392e, 1);
g.fillPoints(P([[C - 38, 92], [C + 38, 92], [C + 20, 146], [C - 20, 146]]), true);
g.fillStyle(0xf2e3c8, 1);
for (const [x, y] of [[C - 16, 104], [C + 14, 104], [C - 2, 122]]) g.fillCircle(x, y, 4);
g.fillStyle(0x18120a, 1);
g.fillEllipse(C - 15, 82, 12, 7); g.fillEllipse(C + 15, 82, 12, 7);
text('WILD', { x: C, y: 176, size: 24, color: '#ffe27a', font: 'Righteous' });
},
dynamite({ g, text }) {
g.fillStyle(0xb83232, 1);
for (const dx of [-30, 0, 30]) g.fillRoundedRect(C + dx - 13, 64, 26, 96, 9);
g.lineStyle(4, 0x7a1c1c, 1);
for (const dx of [-30, 0, 30]) g.strokeRoundedRect(C + dx - 13, 64, 26, 96, 9);
g.lineStyle(5, 0xc8a84b, 1);
g.beginPath(); g.moveTo(C, 64); g.arc(C + 22, 44, 22, Math.PI, Math.PI * 2.4); g.strokePath();
g.fillStyle(0xffe27a, 1);
g.fillPoints(starPts(C + 44, 36, 4, 16, 6, 0), true);
text('BONUS', { x: C, y: 182, size: 20, color: '#ffe27a', font: 'Righteous' });
},
sheriff({ g }) {
g.fillStyle(0xf0c441, 1);
g.fillPoints(starPts(C, C, 6, 78, 46), true);
for (const { x, y } of starPts(C, C, 6, 78, 78)) g.fillCircle(x, y, 9);
g.lineStyle(5, 0xa87f17, 1); g.strokeCircle(C, C, 34);
},
saloon({ g }) {
g.fillStyle(0x6e4c24, 1);
g.fillRect(C - 70, 44, 140, 22);
g.fillStyle(0x8a6132, 1);
for (const side of [-1, 1]) {
g.fillRoundedRect(C + (side === -1 ? -62 : 10), 78, 52, 84, 8);
}
g.fillStyle(0x55391a, 1);
for (const side of [-1, 1]) {
const x = C + (side === -1 ? -54 : 18);
g.fillRoundedRect(x, 88, 36, 28, 5);
g.fillRoundedRect(x, 124, 36, 28, 5);
}
g.lineStyle(6, 0x55391a, 1);
g.beginPath(); g.moveTo(C - 78, 66); g.lineTo(C - 78, 170); g.strokePath();
g.beginPath(); g.moveTo(C + 78, 66); g.lineTo(C + 78, 170); g.strokePath();
},
wagon({ g }) {
g.fillStyle(0x5d4324, 1);
g.fillPoints(P([[C - 68, 78], [C + 68, 78], [C + 46, 138], [C - 46, 138]]), true);
g.fillStyle(0xf0c441, 1);
for (const [x, y] of [[C - 30, 72], [C, 64], [C + 30, 72], [C - 14, 60], [C + 16, 58]]) g.fillCircle(x, y, 13);
g.fillStyle(0x2b211a, 1);
g.fillCircle(C - 34, 152, 16); g.fillCircle(C + 34, 152, 16);
g.fillStyle(0x9aa3ad, 1);
g.fillCircle(C - 34, 152, 6); g.fillCircle(C + 34, 152, 6);
},
pickaxe({ g }) {
g.lineStyle(14, 0x8a6132, 1);
g.beginPath(); g.moveTo(C - 36, 160); g.lineTo(C + 36, 48); g.strokePath();
g.lineStyle(18, 0x9aa3ad, 1);
g.beginPath(); g.arc(C + 12, 92, 62, Math.PI * 1.25, Math.PI * 1.95); g.strokePath();
},
// ··· Sugar Spin ···
rainbow({ g }) {
const cols = [0xe8483f, 0xf0a541, 0x57c46a, 0x4f8fe0, 0x8857b8];
cols.forEach((col, i) => {
g.lineStyle(13, col, 1);
g.beginPath(); g.arc(C, 128, 78 - i * 13, Math.PI, Math.PI * 2); g.strokePath();
});
g.fillStyle(0xffffff, 1);
g.fillPoints(starPts(C - 58, 146, 4, 16, 6, 0), true);
g.fillPoints(starPts(C + 58, 146, 4, 16, 6, 0), true);
},
chocolate({ g }) {
g.fillStyle(0x5d3a1e, 1);
g.fillRoundedRect(C - 58, C - 58, 116, 116, 14);
g.fillStyle(0x7a4f2a, 1);
for (const dx of [-29, 29]) for (const dy of [-29, 29]) {
g.fillRoundedRect(C + dx - 22, C + dy - 22, 44, 44, 8);
}
g.fillStyle(0xa5764a, 0.7); g.fillRoundedRect(C - 51, C - 51, 30, 14, 7);
},
'candy-cane': ({ g }) => {
g.lineStyle(30, 0xf2ece2, 1);
g.beginPath(); g.moveTo(C - 18, 168); g.lineTo(C - 18, 78); g.arc(C + 12, 78, 30, Math.PI, 0); g.lineTo(C + 42, 100); g.strokePath();
g.lineStyle(30, 0xd4313f, 1);
for (const [y1, y2] of [[150, 130], [110, 92]]) {
g.beginPath(); g.moveTo(C - 18, y1); g.lineTo(C - 18, y2); g.strokePath();
}
g.beginPath(); g.arc(C + 12, 78, 30, Math.PI * 1.4, Math.PI * 1.75); g.strokePath();
},
gummy({ g }) {
g.fillStyle(0x57c46a, 0.92);
g.fillCircle(C - 26, 70, 16); g.fillCircle(C + 26, 70, 16);
g.fillCircle(C, 84, 36);
g.fillEllipse(C, 138, 76, 64);
g.fillEllipse(C - 38, 124, 22, 36); g.fillEllipse(C + 38, 124, 22, 36);
g.fillStyle(0xa6e8b2, 0.8); g.fillCircle(C - 12, 74, 9);
g.fillStyle(0x2c7a3c, 1); g.fillCircle(C - 12, 86, 5); g.fillCircle(C + 12, 86, 5);
},
lollipop({ g }) {
g.fillStyle(0xf2ece2, 1); g.fillRoundedRect(C - 6, C, 12, 84, 6);
g.fillStyle(0xe06a9f, 1); g.fillCircle(C, 76, 56);
g.lineStyle(9, 0xf2ece2, 1);
g.beginPath(); g.arc(C, 76, 40, 0, Math.PI * 1.4); g.strokePath();
g.beginPath(); g.arc(C, 76, 22, Math.PI, Math.PI * 2.4); g.strokePath();
g.fillStyle(0xf2ece2, 1); g.fillCircle(C, 76, 7);
},
jelly({ g }) {
g.fillStyle(0xd4313f, 0.95);
g.slice(C, 132, 62, Math.PI, Math.PI * 2, false); g.fillPath();
g.fillEllipse(C, 132, 124, 26);
g.fillStyle(0xf2ece2, 1); g.fillEllipse(C, 148, 150, 18);
g.fillStyle(0xf6979f, 0.85); g.fillEllipse(C - 22, 96, 18, 12);
},
mint({ g }) {
for (let i = 0; i < 8; i++) {
g.fillStyle(i % 2 === 0 ? 0x4fb877 : 0xf2ece2, 1);
g.slice(C, C, 64, (i * Math.PI) / 4, ((i + 1) * Math.PI) / 4, false);
g.fillPath();
}
g.lineStyle(5, 0x2c7a3c, 1); g.strokeCircle(C, C, 64);
},
berry({ g }) {
g.fillStyle(0xb83a6e, 1);
const cells = [[0, -22], [-22, -8], [22, -8], [-30, 16], [0, 8], [30, 16], [-14, 38], [14, 38], [0, 58]];
for (const [dx, dy] of cells) g.fillCircle(C + dx, C + dy - 4, 17);
g.fillStyle(0xe87fa8, 0.8);
for (const [dx, dy] of cells.slice(0, 4)) g.fillCircle(C + dx - 4, C + dy - 8, 5);
g.fillStyle(0x4f9a34, 1);
g.fillEllipse(C - 14, C - 44, 30, 13); g.fillEllipse(C + 14, C - 44, 30, 13);
},
sugar({ g }) {
g.fillStyle(0xf6f3ea, 1);
g.fillPoints(P([[C - 44, 70], [C + 18, 56], [C + 52, 84], [C - 10, 100]]), true); // top
g.fillStyle(0xddd6c4, 1);
g.fillPoints(P([[C - 44, 70], [C - 10, 100], [C - 10, 158], [C - 44, 128]]), true); // left
g.fillStyle(0xc9c0aa, 1);
g.fillPoints(P([[C - 10, 100], [C + 52, 84], [C + 52, 142], [C - 10, 158]]), true); // right
g.fillStyle(0xffffff, 1);
g.fillPoints(starPts(C + 56, 52, 4, 14, 5, 0), true);
},
};
function cardLetter(letter) {
return ({ g, text, theme }) => {
g.lineStyle(6, theme.trim, 0.9);
g.strokeRoundedRect(C - 52, C - 64, 104, 128, 16);
text(letter, { x: C, y: C, size: 96, color: theme.accentHex, font: 'Righteous' });
};
}
function seven(colorHex) {
return ({ text }) => {
text('7', {
x: C, y: C, size: 150, color: colorHex, font: 'Righteous',
stroke: '#14100a', strokeThickness: 10,
});
};
}
// ── Baking ────────────────────────────────────────────────────────────────────
export function bakeMachineSymbols(scene, machine) {
for (const id of allSymbolIds(machine)) {
const key = bakedKey(machine.id, id);
if (scene.textures.exists(key)) continue;
const g = scene.make.graphics({ add: false });
const extras = [];
const text = (str, { x = C, y = C, size = 32, color = '#ffffff', font = 'Righteous', stroke, strokeThickness } = {}) => {
const style = { fontFamily: font, fontSize: `${size}px`, color };
if (stroke) { style.stroke = stroke; style.strokeThickness = strokeThickness ?? 6; }
const t = scene.make.text({ x, y, text: str, style, add: false }).setOrigin(0.5);
extras.push(t);
return t;
};
const painter = PAINTERS[`${machine.id}/${id}`] ?? PAINTERS[id] ?? cardLetter('?');
painter({ g, text, theme: machine.theme });
const rt = scene.make.renderTexture({ width: S, height: S, add: false });
rt.draw(g, 0, 0);
for (const t of extras) rt.draw(t, t.x, t.y);
rt.saveTexture(key);
g.destroy();
for (const t of extras) t.destroy();
}
}
// Small particle textures shared by all machines.
export function bakeParticleTextures(scene) {
if (!scene.textures.exists('slots-coin-p')) {
const g = scene.make.graphics({ add: false });
g.fillStyle(0x8a5e12, 1); g.fillCircle(14, 14, 13);
g.fillStyle(0xf0c441, 1); g.fillCircle(14, 13, 11);
g.fillStyle(0xfbe8a6, 0.95); g.fillEllipse(10, 9, 7, 4);
g.generateTexture('slots-coin-p', 28, 28);
g.destroy();
}
if (!scene.textures.exists('slots-spark')) {
const g = scene.make.graphics({ add: false });
g.fillStyle(0xffffff, 1); g.fillCircle(5, 5, 5);
g.generateTexture('slots-spark', 10, 10);
g.destroy();
}
}