Files
troxler-skat/card-game/trox_card.js
2026-05-31 14:39:45 -05:00

178 lines
8.8 KiB
JavaScript
Raw Permalink 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.
/* ============================================================
TROX Gemeinsames Karten-Zeichenmodul
------------------------------------------------------------
Eine einzige Quelle für das Kartendesign.
Wird von generate_cards.js (ganze Karten) UND
generate_box.js (skalierte Mini-Karten im Fächer) genutzt,
damit die Box-Karten exakt den echten Karten entsprechen.
Bezugssystem: Alle Zeichenfunktionen arbeiten in den
Koordinaten der ORIGINAL-Karte (694 x 1069). Für andere
Maße/Skalierungen wird außen herum transformiert.
============================================================ */
// Referenzmaße der Original-Karte (Designraster)
const REF_W = 694;
const REF_H = 1069;
// Farben + Codes (Y = Yellow, G = Green englische Logik)
const SUITS = {
Rot: { hex: '#E53935', code: 'R', name: 'Red' }, // Trumpf
Gelb: { hex: '#FDD835', code: 'Y', name: 'Yellow' },
Gruen: { hex: '#43A047', code: 'G', name: 'Green' },
Schwarz: { hex: '#212121', code: 'B', name: 'Black' },
};
// ------------------------------------------------------------
// Symbolzentrum (rotationssymmetrisches Paar) Koordinaten
// relativ zum Kartenmittelpunkt (0,0).
// ------------------------------------------------------------
function centerIcons(colorName, hex) {
if (colorName === 'Rot') {
const crown = `
<path d="M -20 12 L -24 -8 L -11 2 L 0 -15 L 11 2 L 24 -8 L 20 12 Z" fill="${hex}" />
<rect x="-20" y="14" width="40" height="4" fill="${hex}" rx="1" />`;
return `
<g transform="translate(0, -22)">${crown}</g>
<g transform="rotate(180) translate(0, -22)">${crown}</g>`;
}
if (colorName === 'Gruen') {
const leaf = `
<path d="M 0 12 Q -16 2 -14 -12 Q -9 -22 0 -25 Q 9 -22 14 -12 Q 16 2 0 12" fill="${hex}" />
<path d="M 0 12 Q -5 16 -10 20" fill="none" stroke="${hex}" stroke-width="3" stroke-linecap="round" />`;
return `
<g transform="translate(0, -22)">${leaf}</g>
<g transform="rotate(180) translate(0, -22)">${leaf}</g>`;
}
if (colorName === 'Gelb') {
const sunRay = `<path d="M 0 -12 L 2.5 -19 L 0 -22 L -2.5 -19 Z" fill="${hex}" />`;
const singleSun = `
<circle cx="0" cy="0" r="8" fill="${hex}" />
${sunRay}
<g transform="rotate(45)">${sunRay}</g>
<g transform="rotate(90)">${sunRay}</g>
<g transform="rotate(135)">${sunRay}</g>
<g transform="rotate(180)">${sunRay}</g>
<g transform="rotate(225)">${sunRay}</g>
<g transform="rotate(270)">${sunRay}</g>
<g transform="rotate(315)">${sunRay}</g>`;
return `
<g transform="translate(0, -22)">${singleSun}</g>
<g transform="rotate(180) translate(0, -22)">${singleSun}</g>`;
}
// Schwarz Zahnrad-Paar
const singleGear = `
<rect x="-2.5" y="-14" width="5" height="28" fill="${hex}" rx="1"/>
<rect x="-2.5" y="-14" width="5" height="28" fill="${hex}" rx="1" transform="rotate(60)"/>
<rect x="-2.5" y="-14" width="5" height="28" fill="${hex}" rx="1" transform="rotate(120)"/>
<circle cx="0" cy="0" r="10" fill="${hex}"/>
<circle cx="0" cy="0" r="5" fill="#ffffff"/>
<circle cx="0" cy="0" r="1.5" fill="${hex}"/>`;
return `
<g transform="translate(0, -22)">${singleGear}</g>
<g transform="rotate(180) translate(0, -22)">${singleGear}</g>`;
}
// ------------------------------------------------------------
// cardArtwork: der gesamte Karteninhalt OHNE den weißen
// Kartenkörper als <g>, gezeichnet im Raster width x height.
// safe = Abstand für Eckelemente & Zierrand
// drawBorder = Zierrand zeichnen (ja bei echten Karten)
// ------------------------------------------------------------
function cardArtwork(colorName, num, { width = REF_W, height = REF_H, safe = 35, drawBorder = true, cornerRadius = 30 } = {}) {
const suit = SUITS[colorName];
const hex = suit.hex;
const cardCode = `TK-${suit.code}${num}`;
const cornerX = safe + 20;
const cornerNumY = safe + 55;
const cornerCodeY = cornerNumY + 22;
const centerGap = 70;
const border = drawBorder
? `<rect x="${safe}" y="${safe}" width="${width - 2 * safe}" height="${height - 2 * safe}" rx="${Math.max(0, cornerRadius - 5)}" fill="none" stroke="${hex}" stroke-width="2" opacity="0.4" />`
: '';
return `
${border}
<!-- Ecken oben -->
<text x="${cornerX}" y="${cornerNumY}" class="trox-cnum" fill="${hex}">${num}</text>
<text x="${cornerX}" y="${cornerCodeY}" class="trox-code">${cardCode}</text>
<text x="${width - cornerX}" y="${cornerNumY}" class="trox-cnum" fill="${hex}" text-anchor="end">${num}</text>
<text x="${width - cornerX}" y="${cornerCodeY}" class="trox-code" text-anchor="end">${cardCode}</text>
<!-- Ecken unten (180°) -->
<g transform="translate(${width}, ${height}) rotate(180)">
<text x="${cornerX}" y="${cornerNumY}" class="trox-cnum" fill="${hex}">${num}</text>
<text x="${cornerX}" y="${cornerCodeY}" class="trox-code">${cardCode}</text>
<text x="${width - cornerX}" y="${cornerNumY}" class="trox-cnum" fill="${hex}" text-anchor="end">${num}</text>
<text x="${width - cornerX}" y="${cornerCodeY}" class="trox-code" text-anchor="end">${cardCode}</text>
</g>
<!-- Große Mittelzahl OBEN -->
<text x="${width / 2}" y="${height / 2 - centerGap}" class="trox-bignum" fill="${hex}" text-anchor="middle">${num}</text>
<!-- Mittellinie + Symbolzentrum -->
<line x1="${safe + 5}" y1="${height / 2}" x2="${width / 2 - 50}" y2="${height / 2}" stroke="${hex}" stroke-width="4" stroke-linecap="round" />
<line x1="${width / 2 + 50}" y1="${height / 2}" x2="${width - safe - 5}" y2="${height / 2}" stroke="${hex}" stroke-width="4" stroke-linecap="round" />
<g transform="translate(${width / 2}, ${height / 2})">
${centerIcons(colorName, hex)}
</g>
<!-- Große Mittelzahl UNTEN (180°) -->
<g transform="translate(${width}, ${height}) rotate(180)">
<text x="${width / 2}" y="${height / 2 - centerGap}" class="trox-bignum" fill="${hex}" text-anchor="middle">${num}</text>
</g>`;
}
// CSS-Klassen, die cardArtwork voraussetzt (font-size SKALIERBAR über fontScale)
function cardStyles(hex, fontScale = 1) {
return `
.trox-cnum { font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-weight: bold; font-size: ${65 * fontScale}px; }
.trox-code { font-family: 'Helvetica Neue', Arial, sans-serif; font-weight: bold; font-size: ${14 * fontScale}px; fill: #888888; letter-spacing: 1px; }
.trox-bignum { font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-weight: bold; font-size: ${320 * fontScale}px; letter-spacing: -10px; }`;
}
// ------------------------------------------------------------
// buildCardSVG: vollständige, eigenständige Karten-SVG.
// ------------------------------------------------------------
function buildCardSVG(colorName, num, { width = REF_W, height = REF_H, safe = 35, cornerRadius = 35 } = {}) {
const suit = SUITS[colorName];
return `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 ${width} ${height}" width="${width}" height="${height}">
<defs>
<style>${cardStyles(suit.hex, 1)}</style>
</defs>
<rect width="${width}" height="${height}" ${cornerRadius ? `rx="${cornerRadius}"` : ''} fill="#ffffff" />
${cardArtwork(colorName, num, { width, height, safe, drawBorder: true, cornerRadius })}
</svg>`;
}
// ------------------------------------------------------------
// miniCardGroup: eine VOLLSTÄNDIGE Karte als skalierte <g>,
// zentriert auf (cx,cy), gedreht um rot Grad. Für den Box-Fächer.
// Zeichnet Schatten + weißen Körper + komplettes Artwork,
// alles im Referenzraster und dann sauber herunterskaliert.
// ------------------------------------------------------------
function miniCardGroup(colorName, num, cx, cy, targetWidth, rot = 0) {
const suit = SUITS[colorName];
const scale = targetWidth / REF_W; // ein einziger Skalenfaktor → 1:1 Optik
const w = REF_W, h = REF_H;
return `
<g transform="translate(${cx}, ${cy}) rotate(${rot}) scale(${scale}) translate(${-w / 2}, ${-h / 2})">
<style>${cardStyles(suit.hex, 1)}</style>
<!-- Schlagschatten -->
<rect x="14" y="20" width="${w}" height="${h}" rx="30" fill="#000000" opacity="0.28" />
<!-- weißer Kartenkörper -->
<rect x="0" y="0" width="${w}" height="${h}" rx="30" fill="#ffffff" stroke="${suit.hex}" stroke-width="3" />
<!-- exakt dasselbe Artwork wie auf der echten Karte -->
${cardArtwork(colorName, num, { width: w, height: h, safe: 35, drawBorder: true, cornerRadius: 30 })}
</g>`;
}
module.exports = {
REF_W, REF_H, SUITS,
centerIcons, cardArtwork, cardStyles, buildCardSVG, miniCardGroup,
};