feat(game): 更新游戏数据结构并优化历史记录显示
- 将动物图片资源配置重构为统一的花朵资源管理模块 - 添加奖励图片资源支持,实现动物和奖励图片的双重映射 - 在游戏历史记录中显示实际数字对应的奖励图标 - 修复历史记录中数字显示的空值处理逻辑 - 更新GitNexus项目索引统计信息 - 优化桌面游戏中花朵选择组件的渲染性能
@@ -1,7 +1,7 @@
|
||||
<!-- gitnexus:start -->
|
||||
# GitNexus — Code Intelligence
|
||||
|
||||
This project is indexed by GitNexus as **36-character-flower** (2505 symbols, 4694 relationships, 215 execution flows). Use the GitNexus MCP tools to understand code, assess impact, and navigate safely.
|
||||
This project is indexed by GitNexus as **36-character-flower** (2527 symbols, 4819 relationships, 217 execution flows). Use the GitNexus MCP tools to understand code, assess impact, and navigate safely.
|
||||
|
||||
> If any GitNexus tool warns the index is stale, run `npx gitnexus analyze` in terminal first.
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<!-- gitnexus:start -->
|
||||
# GitNexus — Code Intelligence
|
||||
|
||||
This project is indexed by GitNexus as **36-character-flower** (2505 symbols, 4694 relationships, 215 execution flows). Use the GitNexus MCP tools to understand code, assess impact, and navigate safely.
|
||||
This project is indexed by GitNexus as **36-character-flower** (2527 symbols, 4819 relationships, 217 execution flows). Use the GitNexus MCP tools to understand code, assess impact, and navigate safely.
|
||||
|
||||
> If any GitNexus tool warns the index is stale, run `npx gitnexus analyze` in terminal first.
|
||||
|
||||
|
||||
BIN
src/assets/reward/1.webp
Normal file
|
After Width: | Height: | Size: 18 KiB |
BIN
src/assets/reward/10.webp
Normal file
|
After Width: | Height: | Size: 18 KiB |
BIN
src/assets/reward/11.webp
Normal file
|
After Width: | Height: | Size: 17 KiB |
BIN
src/assets/reward/12.webp
Normal file
|
After Width: | Height: | Size: 17 KiB |
BIN
src/assets/reward/13.webp
Normal file
|
After Width: | Height: | Size: 18 KiB |
BIN
src/assets/reward/14.webp
Normal file
|
After Width: | Height: | Size: 17 KiB |
BIN
src/assets/reward/15.webp
Normal file
|
After Width: | Height: | Size: 18 KiB |
BIN
src/assets/reward/16.webp
Normal file
|
After Width: | Height: | Size: 17 KiB |
BIN
src/assets/reward/17.webp
Normal file
|
After Width: | Height: | Size: 17 KiB |
BIN
src/assets/reward/18.webp
Normal file
|
After Width: | Height: | Size: 18 KiB |
BIN
src/assets/reward/19.webp
Normal file
|
After Width: | Height: | Size: 17 KiB |
BIN
src/assets/reward/2.webp
Normal file
|
After Width: | Height: | Size: 19 KiB |
BIN
src/assets/reward/20.webp
Normal file
|
After Width: | Height: | Size: 16 KiB |
BIN
src/assets/reward/21.webp
Normal file
|
After Width: | Height: | Size: 17 KiB |
BIN
src/assets/reward/22.webp
Normal file
|
After Width: | Height: | Size: 18 KiB |
BIN
src/assets/reward/23.webp
Normal file
|
After Width: | Height: | Size: 18 KiB |
BIN
src/assets/reward/24.webp
Normal file
|
After Width: | Height: | Size: 16 KiB |
BIN
src/assets/reward/25.webp
Normal file
|
After Width: | Height: | Size: 18 KiB |
BIN
src/assets/reward/26.webp
Normal file
|
After Width: | Height: | Size: 19 KiB |
BIN
src/assets/reward/27.webp
Normal file
|
After Width: | Height: | Size: 17 KiB |
BIN
src/assets/reward/28.webp
Normal file
|
After Width: | Height: | Size: 18 KiB |
BIN
src/assets/reward/29.webp
Normal file
|
After Width: | Height: | Size: 18 KiB |
BIN
src/assets/reward/3.webp
Normal file
|
After Width: | Height: | Size: 19 KiB |
BIN
src/assets/reward/30.webp
Normal file
|
After Width: | Height: | Size: 18 KiB |
BIN
src/assets/reward/31.webp
Normal file
|
After Width: | Height: | Size: 18 KiB |
BIN
src/assets/reward/32.webp
Normal file
|
After Width: | Height: | Size: 17 KiB |
BIN
src/assets/reward/33.webp
Normal file
|
After Width: | Height: | Size: 18 KiB |
BIN
src/assets/reward/34.webp
Normal file
|
After Width: | Height: | Size: 17 KiB |
BIN
src/assets/reward/35.webp
Normal file
|
After Width: | Height: | Size: 18 KiB |
BIN
src/assets/reward/36.webp
Normal file
|
After Width: | Height: | Size: 18 KiB |
BIN
src/assets/reward/4.webp
Normal file
|
After Width: | Height: | Size: 18 KiB |
BIN
src/assets/reward/5.webp
Normal file
|
After Width: | Height: | Size: 20 KiB |
BIN
src/assets/reward/6.webp
Normal file
|
After Width: | Height: | Size: 19 KiB |
BIN
src/assets/reward/7.webp
Normal file
|
After Width: | Height: | Size: 18 KiB |
BIN
src/assets/reward/8.webp
Normal file
|
After Width: | Height: | Size: 19 KiB |
BIN
src/assets/reward/9.webp
Normal file
|
After Width: | Height: | Size: 18 KiB |
@@ -12,27 +12,11 @@ import refreshIcon from '@/assets/system/refresh.webp'
|
||||
import { SmartBackground } from '@/components/smart-background.tsx'
|
||||
import { SmartImage } from '@/components/smart-image'
|
||||
import { useAnimalVm } from '@/features/game/hooks/use-animal-vm'
|
||||
import { FLOWER_IMAGE_LIST } from '@/features/game/shared'
|
||||
import { cn } from '@/lib/utils'
|
||||
import { useAuthStore } from '@/store/auth'
|
||||
import { useGameAutoHostingStore, useGameRoundStore } from '@/store/game'
|
||||
|
||||
const animalModules = import.meta.glob('../../../../assets/animal/*.webp', {
|
||||
eager: true,
|
||||
import: 'default',
|
||||
}) as Record<string, string>
|
||||
|
||||
const animalImageList = Object.entries(animalModules)
|
||||
.map(([path, url]) => {
|
||||
const match = path.match(/\/(\d+)\.webp$/)
|
||||
|
||||
return {
|
||||
id: Number(match?.[1] ?? 0),
|
||||
url,
|
||||
}
|
||||
})
|
||||
.filter((item) => item.id > 0)
|
||||
.sort((left, right) => left.id - right.id)
|
||||
|
||||
const SETTLEMENT_REVEAL_RANDOM_DURATION_MS = 4_000
|
||||
const SETTLEMENT_REVEAL_RESULT_HOLD_MS = 1_000
|
||||
const SETTLEMENT_REVEAL_MIN_STEP_MS = 90
|
||||
@@ -82,7 +66,7 @@ export function DesktopAnimal({
|
||||
}: DesktopAnimalProps) {
|
||||
const { i18n, t } = useTranslation()
|
||||
const prefersReducedMotion = useReducedMotion()
|
||||
const animalIds = useMemo(() => animalImageList.map((item) => item.id), [])
|
||||
const animalIds = useMemo(() => FLOWER_IMAGE_LIST.map((item) => item.id), [])
|
||||
const containerRef = useRef<HTMLElement | null>(null)
|
||||
const cellRefs = useRef(new Map<number, HTMLButtonElement>())
|
||||
const [revealCellId, setRevealCellId] = useState<number | null>(null)
|
||||
@@ -241,7 +225,7 @@ export function DesktopAnimal({
|
||||
className,
|
||||
)}
|
||||
>
|
||||
{animalImageList.map((item) => {
|
||||
{FLOWER_IMAGE_LIST.map((item) => {
|
||||
const selectionMeta = selectionByCell[item.id]
|
||||
const hasPlacedSelection = Boolean(selectionMeta)
|
||||
const isMarqueeActive = showStandbyState && item.id === marqueeId
|
||||
@@ -355,7 +339,7 @@ export function DesktopAnimal({
|
||||
/>
|
||||
) : null}
|
||||
<SmartImage
|
||||
src={item.url}
|
||||
src={item.animalUrl}
|
||||
alt={`animal-${item.id}`}
|
||||
className={cn(
|
||||
'absolute left-[1.5%] right-[1.5%] top-[2.9%] bottom-[2.9%] z-10 overflow-hidden rounded-[calc(var(--design-unit)*14)]',
|
||||
|
||||
@@ -2,7 +2,39 @@ import { useCallback, useRef } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import historyBg from '@/assets/system/history-bg.png'
|
||||
import { SmartBackground } from '@/components/smart-background.tsx'
|
||||
import { SmartImage } from '@/components/smart-image'
|
||||
import { useGameHistoryVm } from '@/features/game/hooks/use-game-history-vm.ts'
|
||||
import { FLOWER_IMAGE_BY_ID } from '@/features/game/shared'
|
||||
|
||||
function HistoryRewardNumber({
|
||||
className,
|
||||
number,
|
||||
}: {
|
||||
className?: string
|
||||
number: number
|
||||
}) {
|
||||
const image = FLOWER_IMAGE_BY_ID[number]
|
||||
const label = String(number).padStart(2, '0')
|
||||
|
||||
if (!image?.rewardUrl) {
|
||||
return <span className={className}>{label}</span>
|
||||
}
|
||||
|
||||
return (
|
||||
<span
|
||||
className={`inline-flex h-design-36 w-design-36 shrink-0 items-center justify-center align-middle ${className ?? ''}`}
|
||||
title={label}
|
||||
>
|
||||
<SmartImage
|
||||
src={image.rewardUrl}
|
||||
alt={label}
|
||||
showSkeleton={false}
|
||||
className="h-full w-full overflow-visible"
|
||||
imgClassName="object-contain"
|
||||
/>
|
||||
</span>
|
||||
)
|
||||
}
|
||||
|
||||
export function DesktopGameHistory() {
|
||||
const { t } = useTranslation()
|
||||
@@ -126,7 +158,18 @@ export function DesktopGameHistory() {
|
||||
<span className={'text-[#84A2A2]'}>
|
||||
{t('gameDesktop.history.numbers')}:{' '}
|
||||
</span>
|
||||
<span>{item.numbersLabel}</span>
|
||||
{item.numbers.length === 0 ? (
|
||||
<span>{item.numbersLabel}</span>
|
||||
) : (
|
||||
<span className="inline-flex flex-wrap items-center gap-design-4 align-middle">
|
||||
{item.numbers.map((number) => (
|
||||
<HistoryRewardNumber
|
||||
key={`${item.id}-${number}`}
|
||||
number={number}
|
||||
/>
|
||||
))}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
<div>
|
||||
<span className={'text-[#84A2A2]'}>
|
||||
@@ -140,9 +183,16 @@ export function DesktopGameHistory() {
|
||||
<span className={'text-[#84A2A2]'}>
|
||||
{t('gameDesktop.history.winningResult')}:{' '}
|
||||
</span>
|
||||
<span className={'text-[#FF7575]'}>
|
||||
{item.resultNumberLabel}
|
||||
</span>
|
||||
{item.resultNumber === null ? (
|
||||
<span className={'text-[#FF7575]'}>
|
||||
{item.resultNumberLabel}
|
||||
</span>
|
||||
) : (
|
||||
<HistoryRewardNumber
|
||||
className="text-[#FF7575]"
|
||||
number={item.resultNumber}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -89,6 +89,7 @@ export function useGameHistoryVm() {
|
||||
numbers: entry.numbers,
|
||||
orderNo: entry.order_no,
|
||||
periodNo: entry.period_no,
|
||||
resultNumber,
|
||||
resultNumberLabel:
|
||||
resultNumber === null
|
||||
? '--'
|
||||
|
||||
35
src/features/game/shared/flower-assets.ts
Normal file
@@ -0,0 +1,35 @@
|
||||
const animalModules = import.meta.glob('../../../assets/animal/*.webp', {
|
||||
eager: true,
|
||||
import: 'default',
|
||||
}) as Record<string, string>
|
||||
|
||||
const rewardModules = import.meta.glob('../../../assets/reward/*.webp', {
|
||||
eager: true,
|
||||
import: 'default',
|
||||
}) as Record<string, string>
|
||||
|
||||
export interface FlowerImageAsset {
|
||||
animalUrl: string
|
||||
id: number
|
||||
rewardUrl: string
|
||||
}
|
||||
|
||||
export const FLOWER_IMAGE_LIST: FlowerImageAsset[] = Array.from(
|
||||
{ length: 36 },
|
||||
(_, index) => {
|
||||
const id = index + 1
|
||||
|
||||
return {
|
||||
animalUrl: animalModules[`../../../assets/animal/${id}.webp`] ?? '',
|
||||
id,
|
||||
rewardUrl: rewardModules[`../../../assets/reward/${id}.webp`] ?? '',
|
||||
}
|
||||
},
|
||||
).filter((item) => item.animalUrl && item.rewardUrl)
|
||||
|
||||
export const FLOWER_IMAGE_BY_ID = FLOWER_IMAGE_LIST.reduce<
|
||||
Record<number, FlowerImageAsset>
|
||||
>((map, item) => {
|
||||
map[item.id] = item
|
||||
return map
|
||||
}, {})
|
||||
@@ -1,4 +1,5 @@
|
||||
export * from './constants'
|
||||
export * from './flower-assets'
|
||||
export * from './initial-state'
|
||||
export * from './selectors'
|
||||
export * from './types'
|
||||
|
||||