card game

This commit is contained in:
2026-05-31 14:39:45 -05:00
parent 5cc62433bd
commit 6abe253547
18 changed files with 2713 additions and 20 deletions

177
card-game/trox_card.js Normal file
View File

@@ -0,0 +1,177 @@
/* ============================================================
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,
};