Skip to content

Commit 42d4833

Browse files
committed
update chessclock
1 parent b7095e7 commit 42d4833

5 files changed

Lines changed: 144 additions & 13 deletions

File tree

src/assets/resourcePaths.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,4 +40,5 @@ export const soundCapture = new URL('./sound/Capture.ogg', import.meta.url).href
4040
export const soundCheck = new URL('./sound/Check.ogg', import.meta.url).href
4141
export const soundVictory = new URL('./sound/Victory.ogg', import.meta.url).href
4242
export const soundDefeat = new URL('./sound/Defeat.ogg', import.meta.url).href
43-
export const soundDraw = new URL('./sound/Draw.ogg', import.meta.url).href
43+
export const soundDraw = new URL('./sound/Draw.ogg', import.meta.url).href
44+
export const soundLowTime = new URL('./sound/LowTime.ogg', import.meta.url).href

src/assets/sound/LowTime.ogg

19.7 KB
Binary file not shown.

src/components/ChessClock.vue

Lines changed: 124 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,20 @@
11
<template>
2-
<div v-if="isClockEnabled" class="clock-grid " :data-testid="testId">
3-
<div class="clock-side side-white" :class="{ 'is-active': activeColor === 'white' }">
4-
<span class="clock-time">{{ formattedWhiteTime }}</span>
2+
<div v-if="isClockEnabled" class="clock-grid" :data-testid="testId">
3+
<div class="clock-side side-white" :class="{
4+
'is-active': activeColor === 'white',
5+
'is-low-time': isLowTime(whiteTimeSeconds) && activeColor === 'white',
6+
}">
7+
<span class="clock-time" :class="{ 'text-low-time': isLowTime(whiteTimeSeconds) }">
8+
{{ formatWhiteTime }}
9+
</span>
510
</div>
6-
<div class="clock-side side-black" :class="{ 'is-active': activeColor === 'black' }">
7-
<span class="clock-time">{{ formattedBlackTime }}</span>
11+
<div class="clock-side side-black" :class="{
12+
'is-active': activeColor === 'black',
13+
'is-low-time': isLowTime(blackTimeSeconds) && activeColor === 'black',
14+
}">
15+
<span class="clock-time" :class="{ 'text-low-time': isLowTime(blackTimeSeconds) }">
16+
{{ formatBlackTime }}
17+
</span>
818
</div>
919
</div>
1020
</template>
@@ -18,6 +28,7 @@ interface Props {
1828
whiteTimeSeconds?: number | null
1929
blackTimeSeconds?: number | null
2030
activeColor?: Color | null
31+
hasGameStarted?: boolean
2132
testId?: string
2233
}
2334
@@ -26,17 +37,30 @@ const props = withDefaults(defineProps<Props>(), {
2637
whiteTimeSeconds: null,
2738
blackTimeSeconds: null,
2839
activeColor: null,
40+
hasGameStarted: false,
2941
testId: 'chess-clock',
3042
})
3143
44+
// 判断是否 ≤10 秒(包括已超时为 0 的情况,保留红色样式)
45+
const isLowTime = (value: number | null | undefined) => {
46+
if (value === null || value === undefined) return false
47+
return value <= 10
48+
}
49+
50+
// 根据时间值的分数部分推导冒号可见性(与倒计时同步,0.5s 亮 / 0.5s 暗)
51+
const colonVisible = (value: number) => {
52+
return (value % 1) < 0.5
53+
}
54+
55+
// 基础格式化(无冒号闪烁)
3256
const formatTime = (value: number | null | undefined) => {
3357
if (value === null || value === undefined || value < 0) {
3458
return '--:--'
3559
}
3660
3761
const hours = Math.floor(value / 3600)
3862
const minutes = Math.floor((value % 3600) / 60)
39-
const seconds = value % 60
63+
const seconds = Math.floor(value % 60)
4064
4165
if (hours > 0) {
4266
return [hours, minutes, seconds].map((unit) => String(unit).padStart(2, '0')).join(':')
@@ -45,8 +69,88 @@ const formatTime = (value: number | null | undefined) => {
4569
return `${String(minutes).padStart(2, '0')}:${String(seconds).padStart(2, '0')}`
4670
}
4771
48-
const formattedWhiteTime = computed(() => formatTime(props.whiteTimeSeconds))
49-
const formattedBlackTime = computed(() => formatTime(props.blackTimeSeconds))
72+
// 带冒号闪烁的格式化
73+
const formatTimeWithBlink = (value: number) => {
74+
if (value < 0) return '--:--'
75+
76+
const hours = Math.floor(value / 3600)
77+
const minutes = Math.floor((value % 3600) / 60)
78+
const seconds = Math.floor(value % 60)
79+
const formatUnit = (num: number) => String(num).padStart(2, '0')
80+
const c = colonVisible(value) ? ':' : ' '
81+
82+
if (hours > 0) {
83+
return `${formatUnit(hours)}${c}${formatUnit(minutes)}${c}${formatUnit(seconds)}`
84+
}
85+
86+
return `${formatUnit(minutes)}${c}${formatUnit(seconds)}`
87+
}
88+
89+
// 带 0.1 秒精度 + 冒号闪烁的格式化
90+
const formatTimeWithTenthsAndBlink = (value: number) => {
91+
if (value < 0) return '--:--'
92+
93+
const hours = Math.floor(value / 3600)
94+
const minutes = Math.floor((value % 3600) / 60)
95+
const sec = value % 60
96+
const wholeSeconds = Math.floor(sec)
97+
const tenths = Math.floor((sec - wholeSeconds) * 10)
98+
const formatUnit = (num: number) => String(num).padStart(2, '0')
99+
const c = colonVisible(value) ? ':' : ' '
100+
101+
if (hours > 0) {
102+
return `${formatUnit(hours)}${c}${formatUnit(minutes)}${c}${formatUnit(wholeSeconds)}.${tenths}`
103+
}
104+
105+
return `${formatUnit(minutes)}${c}${formatUnit(wholeSeconds)}.${tenths}`
106+
}
107+
108+
// 仅 ≤10 秒时显示 0.1 秒精度(无闪烁版本,给非激活方用)
109+
const formatTimeWithTenths = (value: number) => {
110+
if (value < 0) return '--:--'
111+
112+
const hours = Math.floor(value / 3600)
113+
const minutes = Math.floor((value % 3600) / 60)
114+
const sec = value % 60
115+
const wholeSeconds = Math.floor(sec)
116+
const tenths = Math.floor((sec - wholeSeconds) * 10)
117+
const formatUnit = (num: number) => String(num).padStart(2, '0')
118+
119+
if (hours > 0) {
120+
return `${formatUnit(hours)}:${formatUnit(minutes)}:${formatUnit(wholeSeconds)}.${tenths}`
121+
}
122+
123+
return `${formatUnit(minutes)}:${formatUnit(wholeSeconds)}.${tenths}`
124+
}
125+
126+
// 计算每个方向的时间显示
127+
const formatWhiteTime = computed(() => {
128+
const val = props.whiteTimeSeconds
129+
if (val === null || val === undefined || !props.hasGameStarted) {
130+
return formatTime(val)
131+
}
132+
const isActive = props.activeColor === 'white'
133+
if (!isActive) {
134+
if (val <= 10) return formatTimeWithTenths(val)
135+
return formatTime(val)
136+
}
137+
if (val <= 10) return formatTimeWithTenthsAndBlink(val)
138+
return formatTimeWithBlink(val)
139+
})
140+
141+
const formatBlackTime = computed(() => {
142+
const val = props.blackTimeSeconds
143+
if (val === null || val === undefined || !props.hasGameStarted) {
144+
return formatTime(val)
145+
}
146+
const isActive = props.activeColor === 'black'
147+
if (!isActive) {
148+
if (val <= 10) return formatTimeWithTenths(val)
149+
return formatTime(val)
150+
}
151+
if (val <= 10) return formatTimeWithTenthsAndBlink(val)
152+
return formatTimeWithBlink(val)
153+
})
50154
</script>
51155

52156
<style scoped>
@@ -85,8 +189,20 @@ const formattedBlackTime = computed(() => formatTime(props.blackTimeSeconds))
85189
border-color: var(--color-highlight);
86190
}
87191
192+
/* 低时间状态(< 10秒)的高亮边框 */
193+
.clock-side.is-low-time {
194+
border-color: var(--color-danger, #dc3545);
195+
}
196+
88197
.clock-time {
89198
font-size: 1rem;
90199
letter-spacing: 0.08em;
200+
font-variant-numeric: tabular-nums;
201+
}
202+
203+
/* 低于10秒时文本变红 */
204+
.clock-time.text-low-time {
205+
color: var(--color-danger, #dc3545);
206+
font-weight: bold;
91207
}
92208
</style>

src/components/Sidebar.vue

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
<template>
22
<aside class="sidebar">
33
<ChessClock :is-clock-enabled="isClockEnabled" :white-time-seconds="whiteTimeSeconds"
4-
:black-time-seconds="blackTimeSeconds" :active-color="activeColor" :test-id="clockTestId" />
4+
:black-time-seconds="blackTimeSeconds" :active-color="activeColor" :has-game-started="hasGameStarted" :test-id="clockTestId" />
55

66
<div class="material-row">
77
<div class=" material-diff">

src/composables/useGameState.ts

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import {
2323
soundVictory,
2424
soundDefeat,
2525
soundDraw,
26+
soundLowTime,
2627
} from '../assets/resourcePaths'
2728

2829
// ============================================================
@@ -35,6 +36,7 @@ const sounds = {
3536
victory: new Audio(soundVictory),
3637
defeat: new Audio(soundDefeat),
3738
draw: new Audio(soundDraw),
39+
lowTime: new Audio(soundLowTime),
3840
}
3941

4042
const INITIAL_CLOCK_SECONDS: number | null = null
@@ -87,6 +89,8 @@ export function useGameState(
8789
const blackTimeSeconds = ref<number | null>(INITIAL_CLOCK_SECONDS)
8890
const clockIncrementSeconds = ref(0)
8991
let clockTimer: number | null = null
92+
let lowTimePlayedWhite = false
93+
let lowTimePlayedBlack = false
9094

9195
// ---- 游戏终止标记 ----
9296
const isAgreedDraw = ref(false)
@@ -313,20 +317,28 @@ export function useGameState(
313317

314318
if (activeClockColor.value === 'white') {
315319
if (whiteTimeSeconds.value !== null) {
316-
whiteTimeSeconds.value = Math.max(0, whiteTimeSeconds.value - 1)
320+
whiteTimeSeconds.value = Math.max(0, +(whiteTimeSeconds.value - 0.1).toFixed(1))
321+
if (whiteTimeSeconds.value <= 10 && whiteTimeSeconds.value > 0 && !lowTimePlayedWhite) {
322+
lowTimePlayedWhite = true
323+
playSound('lowTime')
324+
}
317325
if (whiteTimeSeconds.value === 0) {
318326
handleClockTimeout('white')
319327
}
320328
}
321329
} else {
322330
if (blackTimeSeconds.value !== null) {
323-
blackTimeSeconds.value = Math.max(0, blackTimeSeconds.value - 1)
331+
blackTimeSeconds.value = Math.max(0, +(blackTimeSeconds.value - 0.1).toFixed(1))
332+
if (blackTimeSeconds.value <= 10 && blackTimeSeconds.value > 0 && !lowTimePlayedBlack) {
333+
lowTimePlayedBlack = true
334+
playSound('lowTime')
335+
}
324336
if (blackTimeSeconds.value === 0) {
325337
handleClockTimeout('black')
326338
}
327339
}
328340
}
329-
}, 1000)
341+
}, 100)
330342
}
331343

332344
const applyClockAfterMove = (moverColor: Color, nextTurn: Color, nextBoard: Board) => {
@@ -1237,6 +1249,8 @@ export function useGameState(
12371249
whiteTimeSeconds.value = config.timeMinutes * 60
12381250
blackTimeSeconds.value = config.timeMinutes * 60
12391251
clockIncrementSeconds.value = config.incrementSeconds
1252+
lowTimePlayedWhite = false
1253+
lowTimePlayedBlack = false
12401254

12411255
hasGameStarted.value = false
12421256
stopClock()

0 commit comments

Comments
 (0)