card game
This commit is contained in:
177
card-game/trox_card.js
Normal file
177
card-game/trox_card.js
Normal 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,
|
||||
};
|
||||
Reference in New Issue
Block a user