Files
game-cards-poker-design/frontend/src/utils/cardLayout.js

139 lines
4.7 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.
/**
* 扑克牌布局数据 & 通用工具函数
*
* 这里和后端 apps/exports/utils.py 保持一致。
* 实际渲染在前端用 canvasdrawCard后端用 PILgenerate_card_png
*/
// 数字牌 1-10 的花色位置(相对坐标 0~1
export const LAYOUT_POSITIONS = {
1: [{ x: 0.50, y: 0.50 }],
2: [{ x: 0.50, y: 0.25 }, { x: 0.50, y: 0.75 }],
3: [{ x: 0.50, y: 0.20 }, { x: 0.50, y: 0.50 }, { x: 0.50, y: 0.80 }],
4: [{ x: 0.30, y: 0.25 }, { x: 0.70, y: 0.25 }, { x: 0.30, y: 0.75 }, { x: 0.70, y: 0.75 }],
5: [{ x: 0.30, y: 0.20 }, { x: 0.70, y: 0.20 }, { x: 0.50, y: 0.50 }, { x: 0.30, y: 0.80 }, { x: 0.70, y: 0.80 }],
6: [{ x: 0.30, y: 0.20 }, { x: 0.70, y: 0.20 }, { x: 0.30, y: 0.50 }, { x: 0.70, y: 0.50 }, { x: 0.30, y: 0.80 }, { x: 0.70, y: 0.80 }],
7: [{ x: 0.30, y: 0.15 }, { x: 0.70, y: 0.15 }, { x: 0.50, y: 0.35 }, { x: 0.30, y: 0.55 }, { x: 0.70, y: 0.55 }, { x: 0.30, y: 0.85 }, { x: 0.70, y: 0.85 }],
8: [{ x: 0.30, y: 0.15 }, { x: 0.70, y: 0.15 }, { x: 0.50, y: 0.32 }, { x: 0.30, y: 0.50 }, { x: 0.70, y: 0.50 }, { x: 0.50, y: 0.68 }, { x: 0.30, y: 0.85 }, { x: 0.70, y: 0.85 }],
9: [{ x: 0.30, y: 0.15 }, { x: 0.70, y: 0.15 }, { x: 0.50, y: 0.30 }, { x: 0.22, y: 0.50 }, { x: 0.50, y: 0.50 }, { x: 0.78, y: 0.50 }, { x: 0.50, y: 0.70 }, { x: 0.30, y: 0.85 }, { x: 0.70, y: 0.85 }],
10: [{ x: 0.30, y: 0.15 }, { x: 0.70, y: 0.15 }, { x: 0.30, y: 0.35 }, { x: 0.70, y: 0.35 }, { x: 0.50, y: 0.45 }, { x: 0.50, y: 0.55 }, { x: 0.30, y: 0.65 }, { x: 0.70, y: 0.65 }, { x: 0.30, y: 0.85 }, { x: 0.70, y: 0.85 }],
}
export const SUIT_TEXT = {
spade: '♠',
heart: '♥',
club: '♣',
diamond: '♦',
}
export const SUIT_LABELS = {
spade: '♠ 黑桃',
heart: '♥ 红桃',
club: '♣ 梅花',
diamond: '♦ 方块',
}
export const SUIT_COLORS = {
spade: '#000000',
heart: '#E53935',
club: '#000000',
diamond: '#E53935',
}
export const RANKS = ['A', '2', '3', '4', '5', '6', '7', '8', '9', '10', 'J', 'Q', 'K']
export const SUITS = ['spade', 'heart', 'club', 'diamond']
export const JOKERS = [
{ key: 'joker-big', label: '大王', defaultColor: '#1B5E20' },
{ key: 'joker-small', label: '小王', defaultColor: '#B71C1C' },
]
/**
* 合并项目级 design 与单牌覆盖
*/
export function getEffectiveDesign(project, cardKey) {
const base = JSON.parse(JSON.stringify(project?.design || {}))
const overrides = (project?.card_overrides || {})[cardKey] || {}
return { ...base, ...overrides }
}
/**
* 计算数字牌 (1-10) 实际的花色位置(绝对像素)
* - 默认按 LAYOUT_POSITIONS
* - 项目可保存 number_layout 做微调
*/
export function computeNumberPipPositions(rank, cardW, cardH, pipSize, numberLayout) {
const rankInt = parseInt(rank, 10) || 1
const defaults = LAYOUT_POSITIONS[rankInt] || LAYOUT_POSITIONS[1]
const userOverrides = (numberLayout || {})[String(rankInt)] || []
return defaults.map((p, i) => {
const o = userOverrides[i] || {}
const dx = Number(o.dx) || 0
const dy = Number(o.dy) || 0
const scale = Number(o.scale) || 1
return {
x: (p.x + dx) * cardW,
y: (p.y + dy) * cardH,
size: Math.max(20, pipSize * scale),
}
})
}
/**
* 解析一个花色 key 是否为红色系
*/
export function isRedSuit(suit) {
return suit === 'heart' || suit === 'diamond'
}
export function isBlackSuit(suit) {
return suit === 'spade' || suit === 'club'
}
export function isFace(rank) {
return rank === 'J' || rank === 'Q' || rank === 'K'
}
export function isJoker(cardKey) {
return typeof cardKey === 'string' && cardKey.startsWith('joker-')
}
export function isNumber(rank) {
return RANKS.indexOf(rank) >= 0 && !isFace(rank)
}
/**
* 列出全部 54 张牌 + 背面
*/
export function listAllCards() {
const out = []
for (const s of SUITS) {
for (const r of RANKS) {
out.push({ key: `${s}-${r}`, suit: s, rank: r, type: isFace(r) ? 'face' : 'number' })
}
}
out.push({ key: 'joker-big', suit: null, rank: null, type: 'joker' })
out.push({ key: 'joker-small', suit: null, rank: null, type: 'joker' })
out.push({ key: 'back', suit: null, rank: null, type: 'back' })
return out
}
export const DEFAULT_DESIGN = {
background_color: '#FFFFFF',
background_image: null,
border_color: '#333333',
border_width: 2,
suit_symbols: {
spade: { type: 'text', value: '♠', asset_id: null, color: '#000000' },
heart: { type: 'text', value: '♥', asset_id: null, color: '#E53935' },
club: { type: 'text', value: '♣', asset_id: null, color: '#000000' },
diamond: { type: 'text', value: '♦', asset_id: null, color: '#E53935' },
},
corner_size_ratio: 0.13,
pip_size_ratio: 0.16,
font_family: 'Times New Roman',
font_color: '#000000',
corner_offset: { x: 0, y: 0 },
}