// src/config/bottomTileMap.ts export type Suit = 'W' | 'T' | 'B' | 'F' | 'D' export interface Tile { id: number suit: Suit value: number } /** * 图片用途: * - hand: 手牌 * - exposed: 碰/杠/胡等明牌 * - covered: 盖住的牌 */ export type TileImageType = 'hand' | 'exposed' | 'covered' export type TilePosition = 'bottom' /** * 手牌图索引: * p4b1_x => 万 * p4b2_x => 筒 * p4b3_x => 条 * p4b4_x => 东南西北中发白 */ const HAND_SUIT_INDEX_MAP: Record = { W: 1, T: 2, B: 3, F: 4, D: 4, } /** * 明牌图索引: * p4s1_x => 万 * p4s2_x => 筒 * p4s3_x => 条 * p4s4_x => 东南西北中发白 */ const EXPOSED_SUIT_INDEX_MAP: Record = { W: 1, T: 2, B: 3, F: 4, D: 4, } /** * 字牌 value 映射: * 风牌 F: * 1=东 2=南 3=西 4=北 * * 箭牌 D: * 1=中 2=发 3=白 * * 在图片资源中: * 1=东 2=南 3=西 4=北 5=中 6=发 7=白 */ function getHonorImageValue(suit: Suit, value: number): number { if (suit === 'F') { return value } if (suit === 'D') { return value + 4 } return value } /** * 构建手牌图片 key * 例如: * /src/assets/images/tiles/bottom/p4b1_1.png * /src/assets/images/tiles/bottom/p4b4_5.png */ function buildHandTileImageKey( suit: Suit, value: number, position: TilePosition = 'bottom', ): string { const suitIndex = HAND_SUIT_INDEX_MAP[suit] const imageValue = suit === 'F' || suit === 'D' ? getHonorImageValue(suit, value) : value return `/src/assets/images/tiles/${position}/p4b${suitIndex}_${imageValue}.png` } /** * 构建明牌图片 key(碰/杠/胡漏出的牌) * 例如: * /src/assets/images/tiles/bottom/p4s1_1.png * /src/assets/images/tiles/bottom/p4s4_5.png */ function buildExposedTileImageKey( suit: Suit, value: number, position: TilePosition = 'bottom', ): string { const suitIndex = EXPOSED_SUIT_INDEX_MAP[suit] const imageValue = suit === 'F' || suit === 'D' ? getHonorImageValue(suit, value) : value return `/src/assets/images/tiles/${position}/p4s${suitIndex}_${imageValue}.png` } /** * 构建盖牌图片 key */ function buildCoveredTileImageKey(position: TilePosition = 'bottom'): string { return `/src/assets/images/tiles/${position}/tdbgs_4.png` } /** * 通过 Vite 收集所有麻将牌资源 */ const tileImageModules = import.meta.glob( '/src/assets/images/tiles/bottom/*.png', { eager: true, import: 'default', }, ) as Record /** * 判断是否为合法花色 */ export function isValidSuit(suit: string): suit is Suit { return suit === 'W' || suit === 'T' || suit === 'B' || suit === 'F' || suit === 'D' } /** * 判断是否为合法点数 * W/T/B => 1~9 * F => 1~4 * D => 1~3 */ export function isValidTileValueBySuit(suit: Suit, value: number): boolean { if (!Number.isInteger(value)) { return false } switch (suit) { case 'W': case 'T': case 'B': return value >= 1 && value <= 9 case 'F': return value >= 1 && value <= 4 case 'D': return value >= 1 && value <= 3 default: return false } } /** * 判断是否为合法牌 */ export function isValidTile(tile: { suit: string; value: number }): tile is Pick { if (!isValidSuit(tile.suit)) { return false } return isValidTileValueBySuit(tile.suit, tile.value) } /** * 获取手牌图片 */ export function getHandTileImage( tile: Pick, position: TilePosition = 'bottom', ): string { if (!isValidTile(tile)) { return '' } const key = buildHandTileImageKey(tile.suit, tile.value, position) return tileImageModules[key] || '' } /** * 获取碰/杠/胡漏出的明牌图片 */ export function getExposedTileImage( tile: Pick, position: TilePosition = 'bottom', ): string { if (!isValidTile(tile)) { return '' } const key = buildExposedTileImageKey(tile.suit, tile.value, position) return tileImageModules[key] || '' } /** * 获取盖住的牌图片 */ export function getCoveredTileImage(position: TilePosition = 'bottom'): string { const key = buildCoveredTileImageKey(position) return tileImageModules[key] || '' } /** * 统一获取牌图片 */ export function getTileImage( tile: Pick, imageType: TileImageType = 'hand', position: TilePosition = 'bottom', ): string { if (imageType === 'covered') { return getCoveredTileImage(position) } if (!isValidTile(tile)) { return '' } if (imageType === 'exposed') { return getExposedTileImage(tile, position) } return getHandTileImage(tile, position) } /** * 获取所有基础牌(不含重复) * 包含: * - 万 1~9 * - 筒 1~9 * - 条 1~9 * - 东南西北 * - 中发白 */ export function getAllTiles(): Array> { const result: Array> = [] // 万筒条 const numberSuits: Array<'W' | 'T' | 'B'> = ['W', 'T', 'B'] for (const suit of numberSuits) { for (let value = 1; value <= 9; value++) { result.push({ suit, value }) } } // 东南西北 for (let value = 1; value <= 4; value++) { result.push({ suit: 'F', value }) } // 中发白 for (let value = 1; value <= 3; value++) { result.push({ suit: 'D', value }) } return result } /** * 获取牌的中文名称 */ export function getTileLabel(tile: Pick): string { if (!isValidTile(tile)) { return '' } switch (tile.suit) { case 'W': return `${tile.value}万` case 'T': return `${tile.value}筒` case 'B': return `${tile.value}条` case 'F': { const map: Record = { 1: '东', 2: '南', 3: '西', 4: '北', } return map[tile.value] || '' } case 'D': { const map: Record = { 1: '中', 2: '发', 3: '白', } return map[tile.value] || '' } default: return '' } }