feat(game): 添加麻将牌图片映射配置并优化成都麻将页面

- 新增 tileMap.ts 配置文件,定义麻将牌图片映射逻辑
- 实现根据花色和点数获取对应图片路径的功能
- 添加麻将牌验证和基础牌生成工具函数
- 在 ChengduGamePage.vue 中导入并使用 getTileImage 函数
- 添加服务器响应日志用于调试
- 优化玩家手牌显示区域的布局结构
This commit is contained in:
2026-03-27 14:16:04 +08:00
parent 921f47d916
commit b1e394d675
2 changed files with 114 additions and 1 deletions

111
src/config/tileMap.ts Normal file
View File

@@ -0,0 +1,111 @@
// src/config/tileMap.ts
export type Suit = 'W' | 'T' | 'B'
export interface Tile {
id: number
suit: Suit
value: number
}
export type TilePosition = 'bottom'
const SUIT_INDEX_MAP: Record<Suit, 1 | 2 | 3> = {
W: 1, // 万
T: 2, // 筒
B: 3, // 条
}
/**
* 当前目录结构:
* /src/assets/images/tiles/bottom/p4b1_1.png
* /src/assets/images/tiles/bottom/p4b2_1.png
* /src/assets/images/tiles/bottom/p4b3_1.png
*/
function buildTileImageKey(
suit: Suit,
value: number,
position: TilePosition = 'bottom',
): string {
const suitIndex = SUIT_INDEX_MAP[suit]
return `/src/assets/images/tiles/${position}/p4b${suitIndex}_${value}.png`
}
/**
* 通过 Vite 收集所有麻将牌资源
*/
const tileImageModules = import.meta.glob(
'/src/assets/images/tiles/bottom/*.png',
{
eager: true,
import: 'default',
},
) as Record<string, string>
/**
* 判断是否为合法花色
*/
export function isValidSuit(suit: string): suit is Suit {
return suit === 'W' || suit === 'T' || suit === 'B'
}
/**
* 判断是否为合法点数
*/
export function isValidTileValue(value: number): boolean {
return Number.isInteger(value) && value >= 1 && value <= 9
}
/**
* 判断是否为合法牌
*/
export function isValidTile(tile: { suit: string; value: number }): tile is Pick<Tile, 'suit' | 'value'> {
return isValidSuit(tile.suit) && isValidTileValue(tile.value)
}
/**
* 根据花色 + 点数获取图片路径
*/
export function getTileImageBySuitAndValue(
suit: Suit,
value: number,
position: TilePosition = 'bottom',
): string {
if (!isValidTileValue(value)) {
return ''
}
const key = buildTileImageKey(suit, value, position)
return tileImageModules[key] || ''
}
/**
* 根据 Tile 获取图片路径
*/
export function getTileImage(
tile: Pick<Tile, 'suit' | 'value'>,
position: TilePosition = 'bottom',
): string {
if (!isValidTile(tile)) {
return ''
}
const key = buildTileImageKey(tile.suit, tile.value, position)
return tileImageModules[key] || ''
}
/**
* 获取全部基础牌
*/
export function getAllTiles(): Array<Pick<Tile, 'suit' | 'value'>> {
const suits: Suit[] = ['W', 'T', 'B']
const result: Array<Pick<Tile, 'suit' | 'value'>> = []
for (const suit of suits) {
for (let value = 1; value <= 9; value++) {
result.push({suit, value})
}
}
return result
}

View File

@@ -37,6 +37,7 @@ import {useGameStore} from '../store/gameStore'
import {setActiveRoom, useActiveRoomState} from '../store'
import type {PlayerState} from '../types/state'
import type {Tile} from '../types/tile'
import {getTileImage} from "../config/tileMap.ts";
const gameStore = useGameStore()
const activeRoom = useActiveRoomState()
@@ -451,6 +452,7 @@ function handleRoomInfoResponse(message: unknown): void {
const summary = asRecord(payload.summary) ?? asRecord(payload.room_summary) ?? null
const publicState = asRecord(payload.public) ?? asRecord(payload.public_state) ?? null
const privateState = asRecord(payload.private) ?? asRecord(payload.private_state) ?? null
console.log("server response payload: " + payload)
const roomId =
readString(summary ?? {}, 'room_id', 'roomId') ||
readString(publicState ?? {}, 'room_id', 'roomId') ||
@@ -1455,8 +1457,8 @@ onBeforeUnmount(() => {
<span class="ready-toggle-label">开始游戏</span>
</button>
</div>
<div class="player-hand" v-if="myHandTiles.length > 0">
<button
v-for="tile in myHandTiles"
:key="tile.id"