feat(game): 更新成都麻将游戏页面功能实现
- 移除静态背景图片导入,改为动态获取牌面图片 - 添加 MeldState 类型定义,支持副露状态管理 - 重构牌面图片获取逻辑,为不同座位创建独立配置文件 - 定义 TableTileImageType、WallTileItem 和 WallSeatState 接口 - 移除 selectedTile 响应式变量,优化手牌显示逻辑 - 创建 sortedVisibleHandTiles 计算属性替代原 visibleHandTiles - 添加 normalizeMeldType 和 normalizeMelds 函数处理副露数据标准化 - 在 PlayerState 中新增 handCount 和 hasHu 属性 - 更新房间玩家数据结构,同步处理手牌计数和胡牌状态 - 重构牌墙显示逻辑,实现动态渲染各座位手牌和副露 - 添加胡牌标识显示功能,改进牌面分组展示效果 - 优化 CSS 样式,调整牌墙布局和间距设置
This commit is contained in:
@@ -513,6 +513,11 @@
|
||||
object-fit: contain;
|
||||
}
|
||||
|
||||
.wall-live .wall-live-tile {
|
||||
display: block;
|
||||
object-fit: contain;
|
||||
}
|
||||
|
||||
.wall-top,
|
||||
.wall-bottom {
|
||||
left: 50%;
|
||||
@@ -565,6 +570,83 @@
|
||||
bottom: 108px;
|
||||
}
|
||||
|
||||
.wall-top.wall-live .wall-live-tile,
|
||||
.wall-bottom.wall-live .wall-live-tile {
|
||||
width: 36px;
|
||||
height: 54px;
|
||||
}
|
||||
|
||||
.wall-left.wall-live .wall-live-tile {
|
||||
width: 60px;
|
||||
height: 40px;
|
||||
}
|
||||
|
||||
.wall-right.wall-live .wall-live-tile {
|
||||
width: 60px;
|
||||
height: 40px;
|
||||
}
|
||||
|
||||
.wall-top.wall-live .wall-live-tile + .wall-live-tile,
|
||||
.wall-bottom.wall-live .wall-live-tile + .wall-live-tile {
|
||||
margin-left: -4px;
|
||||
}
|
||||
|
||||
.wall-left.wall-live .wall-live-tile + .wall-live-tile,
|
||||
.wall-right.wall-live .wall-live-tile + .wall-live-tile {
|
||||
margin-top: -22px;
|
||||
}
|
||||
|
||||
.wall-left.wall-live .wall-live-tile + .wall-live-tile{
|
||||
margin-top: -24px;
|
||||
}
|
||||
|
||||
.wall-live .wall-live-tile.is-group-start {
|
||||
margin-left: 10px;
|
||||
}
|
||||
|
||||
.wall-left.wall-live .wall-live-tile.is-group-start,
|
||||
.wall-right.wall-live .wall-live-tile.is-group-start {
|
||||
margin-left: 0;
|
||||
margin-top: 8px;
|
||||
}
|
||||
|
||||
.wall-bottom.wall-live {
|
||||
--wall-bottom-live-scale: 1;
|
||||
--wall-bottom-live-width: calc(60px * var(--wall-bottom-live-scale));
|
||||
--wall-bottom-live-height: calc(86px * var(--wall-bottom-live-scale));
|
||||
gap: 0;
|
||||
}
|
||||
|
||||
.wall-bottom.wall-live .wall-live-tile {
|
||||
width: var(--wall-bottom-live-width);
|
||||
height: var(--wall-bottom-live-height);
|
||||
filter: drop-shadow(0 6px 12px rgba(0, 0, 0, 0.18));
|
||||
}
|
||||
|
||||
.wall-bottom.wall-live .wall-live-tile + .wall-live-tile {
|
||||
margin-left: -4px;
|
||||
}
|
||||
|
||||
.wall-bottom.wall-live .wall-live-tile.is-group-start {
|
||||
margin-left: 12px;
|
||||
}
|
||||
|
||||
.wall-hu-flag {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
min-width: 24px;
|
||||
height: 24px;
|
||||
margin-left: 8px;
|
||||
padding: 0 7px;
|
||||
border-radius: 999px;
|
||||
color: #fff3da;
|
||||
font-size: 12px;
|
||||
font-weight: 800;
|
||||
background: linear-gradient(180deg, rgba(219, 81, 56, 0.92), rgba(146, 32, 20, 0.96));
|
||||
box-shadow: 0 4px 10px rgba(0, 0, 0, 0.22);
|
||||
}
|
||||
|
||||
/* 提高特异性,保证本页使用 room.css 配置值 */
|
||||
.picture-scene .wall-top {
|
||||
top: 120px;
|
||||
@@ -575,7 +657,7 @@
|
||||
}
|
||||
|
||||
.picture-scene .wall-bottom {
|
||||
bottom: 160px;
|
||||
bottom: 124px;
|
||||
}
|
||||
|
||||
.picture-scene .wall-left {
|
||||
|
||||
@@ -43,6 +43,7 @@ export const useGameStore = defineStore('game', {
|
||||
if (player.playerId === this.getMyPlayerId()) {
|
||||
player.handTiles.push(data.tile)
|
||||
}
|
||||
player.handCount += 1
|
||||
|
||||
// 剩余牌数减少
|
||||
this.remainingTiles = Math.max(0, this.remainingTiles - 1)
|
||||
@@ -75,6 +76,7 @@ export const useGameStore = defineStore('game', {
|
||||
player.handTiles.splice(index, 1)
|
||||
}
|
||||
}
|
||||
player.handCount = Math.max(0, player.handCount - 1)
|
||||
|
||||
// 加入出牌区
|
||||
player.discardTiles.push(data.tile)
|
||||
@@ -152,8 +154,10 @@ export const useGameStore = defineStore('game', {
|
||||
? missingSuitRaw
|
||||
: previous?.missingSuit,
|
||||
handTiles: previous?.handTiles ?? [],
|
||||
handCount: previous?.handCount ?? 0,
|
||||
melds: previous?.melds ?? [],
|
||||
discardTiles: previous?.discardTiles ?? [],
|
||||
hasHu: previous?.hasHu ?? false,
|
||||
score: previous?.score ?? 0,
|
||||
isReady:
|
||||
typeof readyRaw === 'boolean'
|
||||
@@ -175,8 +179,10 @@ export const useGameStore = defineStore('game', {
|
||||
avatarURL: previous?.avatarURL,
|
||||
missingSuit: previous?.missingSuit,
|
||||
handTiles: previous?.handTiles ?? [],
|
||||
handCount: previous?.handCount ?? 0,
|
||||
melds: previous?.melds ?? [],
|
||||
discardTiles: previous?.discardTiles ?? [],
|
||||
hasHu: previous?.hasHu ?? false,
|
||||
score: previous?.score ?? 0,
|
||||
isReady: previous?.isReady ?? false,
|
||||
}
|
||||
|
||||
@@ -10,12 +10,14 @@ export interface PlayerState {
|
||||
|
||||
// 手牌(只有自己有完整数据,后端可控制)
|
||||
handTiles: Tile[]
|
||||
handCount: number
|
||||
|
||||
// 副露(碰/杠)
|
||||
melds: MeldState[]
|
||||
|
||||
// 出牌区
|
||||
discardTiles: Tile[]
|
||||
hasHu: boolean
|
||||
|
||||
// 分数
|
||||
score: number
|
||||
|
||||
@@ -8,10 +8,6 @@ import tiaoIcon from '../assets/images/flowerClolor/tiao.png'
|
||||
import robotIcon from '../assets/images/icons/robot.svg'
|
||||
import exitIcon from '../assets/images/icons/exit.svg'
|
||||
import '../assets/styles/room.css'
|
||||
import topBackImage from '../assets/images/tiles/top/tbgs_2.png'
|
||||
import rightBackImage from '../assets/images/tiles/right/tbgs_1.png'
|
||||
import bottomBackImage from '../assets/images/tiles/bottom/tdbgs_4.png'
|
||||
import leftBackImage from '../assets/images/tiles/left/tbgs_3.png'
|
||||
import TopPlayerCard from '../components/game/TopPlayerCard.vue'
|
||||
import RightPlayerCard from '../components/game/RightPlayerCard.vue'
|
||||
import BottomPlayerCard from '../components/game/BottomPlayerCard.vue'
|
||||
@@ -35,9 +31,12 @@ import {sendWsMessage} from '../ws/sender'
|
||||
import {buildWsUrl} from '../ws/url'
|
||||
import {useGameStore} from '../store/gameStore'
|
||||
import {setActiveRoom, useActiveRoomState} from '../store'
|
||||
import type {PlayerState} from '../types/state'
|
||||
import type {MeldState, PlayerState} from '../types/state'
|
||||
import type {Tile} from '../types/tile'
|
||||
import {getTileImage} from "../config/bottomTileMap.ts";
|
||||
import {getTileImage as getBottomTileImage} from '../config/bottomTileMap.ts'
|
||||
import {getTileImage as getTopTileImage} from '../config/topTileMap.ts'
|
||||
import {getTileImage as getRightTileImage} from '../config/rightTileMap.ts'
|
||||
import {getTileImage as getLeftTileImage} from '../config/leftTileMap.ts'
|
||||
|
||||
const gameStore = useGameStore()
|
||||
const activeRoom = useActiveRoomState()
|
||||
@@ -52,6 +51,20 @@ type DisplayPlayer = PlayerState & {
|
||||
|
||||
type GameActionPayload<TType extends GameAction['type']> = Extract<GameAction, { type: TType }>['payload']
|
||||
type HandSuitLabel = '万' | '筒' | '条'
|
||||
type TableTileImageType = 'hand' | 'exposed' | 'covered'
|
||||
|
||||
interface WallTileItem {
|
||||
key: string
|
||||
src: string
|
||||
alt: string
|
||||
imageType: TableTileImageType
|
||||
suit?: Tile['suit']
|
||||
}
|
||||
|
||||
interface WallSeatState {
|
||||
tiles: WallTileItem[]
|
||||
hasHu: boolean
|
||||
}
|
||||
|
||||
interface SeatViewModel {
|
||||
key: SeatKey
|
||||
@@ -64,7 +77,6 @@ const now = ref(Date.now())
|
||||
const wsStatus = ref<WsStatus>('idle')
|
||||
const wsMessages = ref<string[]>([])
|
||||
const wsError = ref('')
|
||||
const selectedTile = ref<string | null>(null)
|
||||
const leaveRoomPending = ref(false)
|
||||
const readyTogglePending = ref(false)
|
||||
const startGamePending = ref(false)
|
||||
@@ -178,12 +190,8 @@ const visibleHandTileGroups = computed(() => {
|
||||
.filter((group) => group.tiles.length > 0)
|
||||
})
|
||||
|
||||
const visibleHandTiles = computed(() => {
|
||||
if (gameStore.phase === 'waiting') {
|
||||
return []
|
||||
}
|
||||
|
||||
return myHandTiles.value
|
||||
const sortedVisibleHandTiles = computed(() => {
|
||||
return visibleHandTileGroups.value.flatMap((group) => group.tiles)
|
||||
})
|
||||
|
||||
const remainingTiles = computed(() => {
|
||||
@@ -465,6 +473,83 @@ function normalizeTiles(value: unknown): Tile[] {
|
||||
.filter((item): item is Tile => Boolean(item))
|
||||
}
|
||||
|
||||
function normalizeMeldType(value: unknown, concealed = false): MeldState['type'] | null {
|
||||
if (typeof value !== 'string') {
|
||||
return concealed ? 'an_gang' : null
|
||||
}
|
||||
|
||||
const normalized = value.replace(/[-\s]/g, '_').toLowerCase()
|
||||
if (normalized === 'peng') {
|
||||
return 'peng'
|
||||
}
|
||||
if (normalized === 'ming_gang' || normalized === 'gang' || normalized === 'gang_open') {
|
||||
return concealed ? 'an_gang' : 'ming_gang'
|
||||
}
|
||||
if (normalized === 'an_gang' || normalized === 'angang' || normalized === 'concealed_gang') {
|
||||
return 'an_gang'
|
||||
}
|
||||
|
||||
return concealed ? 'an_gang' : null
|
||||
}
|
||||
|
||||
function normalizeMelds(value: unknown): PlayerState['melds'] {
|
||||
if (!Array.isArray(value)) {
|
||||
return []
|
||||
}
|
||||
|
||||
return value
|
||||
.map((item) => {
|
||||
const source = asRecord(item)
|
||||
if (!source) {
|
||||
return null
|
||||
}
|
||||
|
||||
const tiles = normalizeTiles(
|
||||
source.tiles ??
|
||||
source.meld_tiles ??
|
||||
source.meldTiles ??
|
||||
source.cards ??
|
||||
source.card_list,
|
||||
)
|
||||
if (tiles.length === 0) {
|
||||
return null
|
||||
}
|
||||
|
||||
const concealed =
|
||||
readBoolean(source, 'concealed', 'is_concealed', 'isConcealed', 'hidden', 'is_hidden') ?? false
|
||||
const type = normalizeMeldType(
|
||||
source.type ?? source.meld_type ?? source.meldType ?? source.kind,
|
||||
concealed,
|
||||
)
|
||||
|
||||
if (type === 'peng') {
|
||||
return {
|
||||
type,
|
||||
tiles,
|
||||
fromPlayerId: readString(source, 'from_player_id', 'fromPlayerId'),
|
||||
} satisfies MeldState
|
||||
}
|
||||
|
||||
if (type === 'ming_gang') {
|
||||
return {
|
||||
type,
|
||||
tiles,
|
||||
fromPlayerId: readString(source, 'from_player_id', 'fromPlayerId'),
|
||||
} satisfies MeldState
|
||||
}
|
||||
|
||||
if (type === 'an_gang') {
|
||||
return {
|
||||
type,
|
||||
tiles,
|
||||
} satisfies MeldState
|
||||
}
|
||||
|
||||
return null
|
||||
})
|
||||
.filter((item): item is MeldState => Boolean(item))
|
||||
}
|
||||
|
||||
function requestRoomInfo(): void {
|
||||
const routeRoomId = typeof route.params.roomId === 'string' ? route.params.roomId : ''
|
||||
const roomId = routeRoomId || gameStore.roomId || activeRoom.value?.roomId || ''
|
||||
@@ -538,8 +623,10 @@ function handleRoomInfoResponse(message: unknown): void {
|
||||
missingSuit?: string | null
|
||||
isReady: boolean
|
||||
handTiles: Tile[]
|
||||
handCount: number
|
||||
melds: PlayerState['melds']
|
||||
discardTiles: Tile[]
|
||||
hasHu: boolean
|
||||
score: number
|
||||
}
|
||||
}>()
|
||||
@@ -582,8 +669,10 @@ function handleRoomInfoResponse(message: unknown): void {
|
||||
missingSuit,
|
||||
isReady: ready,
|
||||
handTiles: [],
|
||||
handCount: 0,
|
||||
melds: [],
|
||||
discardTiles: [],
|
||||
hasHu: false,
|
||||
score: 0,
|
||||
},
|
||||
})
|
||||
@@ -609,6 +698,13 @@ function handleRoomInfoResponse(message: unknown): void {
|
||||
const missingSuit = readString(player, 'missing_suit', 'MissingSuit') || existing?.gamePlayer.missingSuit || null
|
||||
const handCount = readNumber(player, 'hand_count', 'handCount') ?? 0
|
||||
const outTiles = normalizeTiles(player.out_tiles ?? player.outTiles)
|
||||
const melds = normalizeMelds(
|
||||
player.melds ??
|
||||
player.exposed_melds ??
|
||||
player.exposedMelds ??
|
||||
player.claims,
|
||||
)
|
||||
const hasHu = Boolean(player.has_hu ?? player.hasHu)
|
||||
|
||||
playerMap.set(playerId, {
|
||||
roomPlayer: {
|
||||
@@ -618,9 +714,9 @@ function handleRoomInfoResponse(message: unknown): void {
|
||||
missingSuit,
|
||||
ready: existing?.roomPlayer.ready ?? false,
|
||||
hand: Array.from({length: handCount}, () => ''),
|
||||
melds: [],
|
||||
melds: melds.map((meld) => meld.type),
|
||||
outTiles: outTiles.map((tile) => tileToText(tile)),
|
||||
hasHu: Boolean(player.has_hu ?? player.hasHu),
|
||||
hasHu,
|
||||
},
|
||||
gamePlayer: {
|
||||
playerId,
|
||||
@@ -630,8 +726,10 @@ function handleRoomInfoResponse(message: unknown): void {
|
||||
missingSuit,
|
||||
isReady: existing?.gamePlayer.isReady ?? false,
|
||||
handTiles: existing?.gamePlayer.handTiles ?? [],
|
||||
melds: existing?.gamePlayer.melds ?? [],
|
||||
handCount,
|
||||
melds: melds.length > 0 ? melds : existing?.gamePlayer.melds ?? [],
|
||||
discardTiles: outTiles,
|
||||
hasHu,
|
||||
score: existing?.gamePlayer.score ?? 0,
|
||||
},
|
||||
})
|
||||
@@ -643,6 +741,7 @@ function handleRoomInfoResponse(message: unknown): void {
|
||||
if (current) {
|
||||
current.roomPlayer.hand = privateHand.map((tile) => tileToText(tile))
|
||||
current.gamePlayer.handTiles = privateHand
|
||||
current.gamePlayer.handCount = privateHand.length
|
||||
}
|
||||
}
|
||||
|
||||
@@ -662,8 +761,14 @@ function handleRoomInfoResponse(message: unknown): void {
|
||||
avatarURL: gamePlayer.avatarURL ?? previous?.avatarURL,
|
||||
missingSuit: gamePlayer.missingSuit ?? previous?.missingSuit,
|
||||
handTiles: gamePlayer.handTiles.length > 0 ? gamePlayer.handTiles : previous?.handTiles ?? [],
|
||||
handCount: gamePlayer.handCount > 0
|
||||
? gamePlayer.handCount
|
||||
: gamePlayer.handTiles.length > 0
|
||||
? gamePlayer.handTiles.length
|
||||
: (previous?.handCount ?? 0),
|
||||
melds: gamePlayer.melds.length > 0 ? gamePlayer.melds : previous?.melds ?? [],
|
||||
discardTiles: gamePlayer.discardTiles.length > 0 ? gamePlayer.discardTiles : previous?.discardTiles ?? [],
|
||||
hasHu: gamePlayer.hasHu || previous?.hasHu || false,
|
||||
score: typeof score === 'number' ? score : previous?.score ?? gamePlayer.score ?? 0,
|
||||
isReady: gamePlayer.isReady,
|
||||
}
|
||||
@@ -764,25 +869,110 @@ const formattedClock = computed(() => {
|
||||
})
|
||||
})
|
||||
|
||||
const wallBacks = computed<Record<SeatKey, string[]>>(() => {
|
||||
if (gameStore.phase === 'waiting' || remainingTiles.value <= 0) {
|
||||
return {
|
||||
top: [],
|
||||
right: [],
|
||||
bottom: [],
|
||||
left: [],
|
||||
function buildWallTileImage(
|
||||
seat: SeatKey,
|
||||
tile: Tile | undefined,
|
||||
imageType: TableTileImageType,
|
||||
): string {
|
||||
switch (seat) {
|
||||
case 'top':
|
||||
return getTopTileImage(tile, imageType, 'top')
|
||||
case 'right':
|
||||
return getRightTileImage(tile, imageType, 'right')
|
||||
case 'left':
|
||||
return getLeftTileImage(tile, imageType, 'left')
|
||||
case 'bottom':
|
||||
default:
|
||||
if (!tile) {
|
||||
return ''
|
||||
}
|
||||
return getBottomTileImage(tile, imageType, 'bottom')
|
||||
}
|
||||
}
|
||||
|
||||
function emptyWallSeat(): WallSeatState {
|
||||
return {
|
||||
tiles: [],
|
||||
hasHu: false,
|
||||
}
|
||||
}
|
||||
|
||||
const wallSeats = computed<Record<SeatKey, WallSeatState>>(() => {
|
||||
const emptyState: Record<SeatKey, WallSeatState> = {
|
||||
top: emptyWallSeat(),
|
||||
right: emptyWallSeat(),
|
||||
bottom: emptyWallSeat(),
|
||||
left: emptyWallSeat(),
|
||||
}
|
||||
|
||||
if (gameStore.phase === 'waiting') {
|
||||
return emptyState
|
||||
}
|
||||
|
||||
for (const seat of seatViews.value) {
|
||||
if (!seat.player) {
|
||||
continue
|
||||
}
|
||||
|
||||
const seatTiles: WallTileItem[] = []
|
||||
const targetSeat = seat.key
|
||||
|
||||
if (seat.isSelf) {
|
||||
sortedVisibleHandTiles.value.forEach((tile, index) => {
|
||||
const src = buildWallTileImage(targetSeat, tile, 'hand')
|
||||
if (!src) {
|
||||
return
|
||||
}
|
||||
|
||||
seatTiles.push({
|
||||
key: `hand-${tile.id}-${index}`,
|
||||
src,
|
||||
alt: formatTile(tile),
|
||||
imageType: 'hand',
|
||||
suit: tile.suit,
|
||||
})
|
||||
})
|
||||
} else {
|
||||
for (let index = 0; index < seat.player.handCount; index += 1) {
|
||||
const src = buildWallTileImage(targetSeat, undefined, 'hand')
|
||||
if (!src) {
|
||||
continue
|
||||
}
|
||||
|
||||
seatTiles.push({
|
||||
key: `concealed-${index}`,
|
||||
src,
|
||||
alt: '手牌背面',
|
||||
imageType: 'hand',
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
seat.player.melds.forEach((meld, meldIndex) => {
|
||||
meld.tiles.forEach((tile, tileIndex) => {
|
||||
const imageType: TableTileImageType = meld.type === 'an_gang' ? 'covered' : 'exposed'
|
||||
const src = buildWallTileImage(targetSeat, tile, imageType)
|
||||
if (!src) {
|
||||
return
|
||||
}
|
||||
|
||||
seatTiles.push({
|
||||
key: `${meld.type}-${meldIndex}-${tile.id}-${tileIndex}`,
|
||||
src,
|
||||
alt: formatTile(tile),
|
||||
imageType,
|
||||
suit: tile.suit,
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
emptyState[targetSeat] = {
|
||||
tiles: seatTiles,
|
||||
hasHu: seat.player.hasHu,
|
||||
}
|
||||
}
|
||||
|
||||
const wallSize = remainingTiles.value
|
||||
const perSide = Math.max(6, Math.ceil((wallSize || 48) / 4 / 2))
|
||||
|
||||
return {
|
||||
top: Array.from({length: perSide}, (_, index) => `top-${index}`),
|
||||
right: Array.from({length: perSide}, (_, index) => `right-${index}`),
|
||||
bottom: Array.from({length: perSide}, (_, index) => `bottom-${index}`),
|
||||
left: Array.from({length: perSide}, (_, index) => `left-${index}`),
|
||||
}
|
||||
return emptyState
|
||||
})
|
||||
|
||||
const seatDecor = computed<Record<SeatKey, SeatPlayerCardModel>>(() => {
|
||||
@@ -860,17 +1050,6 @@ function missingSuitLabel(value: string | null | undefined): string {
|
||||
return suitMap[value] ?? value
|
||||
}
|
||||
|
||||
function getBackImage(seat: SeatKey): string {
|
||||
const imageMap: Record<SeatKey, string> = {
|
||||
top: topBackImage,
|
||||
right: rightBackImage,
|
||||
bottom: bottomBackImage,
|
||||
left: leftBackImage,
|
||||
}
|
||||
|
||||
return imageMap[seat]
|
||||
}
|
||||
|
||||
function toggleMenu(): void {
|
||||
menuTriggerActive.value = true
|
||||
if (menuTriggerTimer !== null) {
|
||||
@@ -900,10 +1079,6 @@ function toggleTrustMode(): void {
|
||||
menuOpen.value = false
|
||||
}
|
||||
|
||||
function selectTile(tile: string): void {
|
||||
selectedTile.value = selectedTile.value === tile ? null : tile
|
||||
}
|
||||
|
||||
function formatTile(tile: Tile): string {
|
||||
return `${tile.suit}${tile.value}`
|
||||
}
|
||||
@@ -938,6 +1113,7 @@ function handlePlayerHandResponse(message: unknown): void {
|
||||
const existingPlayer = gameStore.players[loggedInUserId.value]
|
||||
if (existingPlayer) {
|
||||
existingPlayer.handTiles = handTiles
|
||||
existingPlayer.handCount = handTiles.length
|
||||
}
|
||||
|
||||
const room = activeRoom.value
|
||||
@@ -1338,8 +1514,10 @@ function hydrateFromActiveRoom(routeRoomId: string): void {
|
||||
missingSuit: player.missingSuit ?? previous?.missingSuit,
|
||||
isReady: player.ready,
|
||||
handTiles: previous?.handTiles ?? [],
|
||||
handCount: previous?.handCount ?? 0,
|
||||
melds: previous?.melds ?? [],
|
||||
discardTiles: previous?.discardTiles ?? [],
|
||||
hasHu: previous?.hasHu ?? false,
|
||||
score: previous?.score ?? 0,
|
||||
}
|
||||
}
|
||||
@@ -1507,17 +1685,61 @@ onBeforeUnmount(() => {
|
||||
<BottomPlayerCard :player="seatDecor.bottom"/>
|
||||
<LeftPlayerCard :player="seatDecor.left"/>
|
||||
|
||||
<div class="wall wall-top">
|
||||
<img v-for="key in wallBacks.top" :key="key" :src="getBackImage('top')" alt=""/>
|
||||
<div v-if="wallSeats.top.tiles.length > 0 || wallSeats.top.hasHu" class="wall wall-top wall-live">
|
||||
<img
|
||||
v-for="(tile, index) in wallSeats.top.tiles"
|
||||
:key="tile.key"
|
||||
class="wall-live-tile"
|
||||
:class="{
|
||||
'is-group-start': index > 0 && tile.suit && wallSeats.top.tiles[index - 1]?.suit !== tile.suit,
|
||||
'is-exposed': tile.imageType !== 'hand',
|
||||
}"
|
||||
:src="tile.src"
|
||||
:alt="tile.alt"
|
||||
/>
|
||||
<span v-if="wallSeats.top.hasHu" class="wall-hu-flag">胡</span>
|
||||
</div>
|
||||
<div class="wall wall-right">
|
||||
<img v-for="key in wallBacks.right" :key="key" :src="getBackImage('right')" alt=""/>
|
||||
<div v-if="wallSeats.right.tiles.length > 0 || wallSeats.right.hasHu" class="wall wall-right wall-live">
|
||||
<img
|
||||
v-for="(tile, index) in wallSeats.right.tiles"
|
||||
:key="tile.key"
|
||||
class="wall-live-tile"
|
||||
:class="{
|
||||
'is-group-start': index > 0 && tile.suit && wallSeats.right.tiles[index - 1]?.suit !== tile.suit,
|
||||
'is-exposed': tile.imageType !== 'hand',
|
||||
}"
|
||||
:src="tile.src"
|
||||
:alt="tile.alt"
|
||||
/>
|
||||
<span v-if="wallSeats.right.hasHu" class="wall-hu-flag">胡</span>
|
||||
</div>
|
||||
<div class="wall wall-bottom">
|
||||
<img v-for="key in wallBacks.bottom" :key="key" :src="getBackImage('bottom')" alt=""/>
|
||||
<div v-if="wallSeats.bottom.tiles.length > 0 || wallSeats.bottom.hasHu" class="wall wall-bottom wall-live">
|
||||
<img
|
||||
v-for="(tile, index) in wallSeats.bottom.tiles"
|
||||
:key="tile.key"
|
||||
class="wall-live-tile"
|
||||
:class="{
|
||||
'is-group-start': index > 0 && tile.suit && wallSeats.bottom.tiles[index - 1]?.suit !== tile.suit,
|
||||
'is-exposed': tile.imageType !== 'hand',
|
||||
}"
|
||||
:src="tile.src"
|
||||
:alt="tile.alt"
|
||||
/>
|
||||
<span v-if="wallSeats.bottom.hasHu" class="wall-hu-flag">胡</span>
|
||||
</div>
|
||||
<div class="wall wall-left">
|
||||
<img v-for="key in wallBacks.left" :key="key" :src="getBackImage('left')" alt=""/>
|
||||
<div v-if="wallSeats.left.tiles.length > 0 || wallSeats.left.hasHu" class="wall wall-left wall-live">
|
||||
<img
|
||||
v-for="(tile, index) in wallSeats.left.tiles"
|
||||
:key="tile.key"
|
||||
class="wall-live-tile"
|
||||
:class="{
|
||||
'is-group-start': index > 0 && tile.suit && wallSeats.left.tiles[index - 1]?.suit !== tile.suit,
|
||||
'is-exposed': tile.imageType !== 'hand',
|
||||
}"
|
||||
:src="tile.src"
|
||||
:alt="tile.alt"
|
||||
/>
|
||||
<span v-if="wallSeats.left.hasHu" class="wall-hu-flag">胡</span>
|
||||
</div>
|
||||
|
||||
<div class="floating-status top">
|
||||
@@ -1562,29 +1784,6 @@ onBeforeUnmount(() => {
|
||||
<span class="ready-toggle-label">开始游戏</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="player-hand" v-if="visibleHandTiles.length > 0">
|
||||
<div
|
||||
v-for="group in visibleHandTileGroups"
|
||||
:key="group.suit"
|
||||
class="player-hand-group"
|
||||
:data-suit="group.suit"
|
||||
>
|
||||
<button
|
||||
v-for="tile in group.tiles"
|
||||
:key="tile.id"
|
||||
class="tile-chip"
|
||||
:class="{ selected: selectedTile === formatTile(tile) }"
|
||||
type="button"
|
||||
@click="selectTile(formatTile(tile))"
|
||||
>
|
||||
<img
|
||||
class="tile-chip-image"
|
||||
:src="getTileImage(tile)"
|
||||
:alt="formatTile(tile)"
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
Reference in New Issue
Block a user