調整發球鏡像規則並更新說明文件

This commit is contained in:
2026-04-28 08:52:47 +08:00
parent edab74f125
commit f3e51ea83d
5 changed files with 203 additions and 133 deletions
+64 -8
View File
@@ -14,6 +14,8 @@ import {
convertDateToKey,
convertDbRecordToGroups,
formatDateInputValue,
getMirroredCourt,
getServiceCourt,
getServingPlayer,
getTeamDisplayName,
getWinnerName,
@@ -56,6 +58,7 @@ const initialScoreState: ScoreState = {
gamesRight: 0,
currentGame: 1,
targetScore: 21,
initialServing: null,
serving: null,
leftRightCourtPlayer: 'playerA',
rightRightCourtPlayer: 'playerA',
@@ -81,6 +84,13 @@ type VictoryAnnouncement = {
title: string
}
type VoiceAnnouncement = {
key: number
scorerName: string
serverChanged: boolean
serverName: string
}
const STREAK_TITLES: Record<number, string> = {
3: '大殺特殺',
4: '暴走',
@@ -129,6 +139,7 @@ function App() {
})
const [streakAnnouncement, setStreakAnnouncement] = useState<StreakAnnouncement | null>(null)
const [victoryAnnouncement, setVictoryAnnouncement] = useState<VictoryAnnouncement | null>(null)
const [voiceAnnouncement, setVoiceAnnouncement] = useState<VoiceAnnouncement | null>(null)
const [pwaUpdateReady, setPwaUpdateReady] = useState(false)
const [liveRoomSession, setLiveRoomSession] = useState<LiveRoomSession | null>(null)
const [navigationLockMessage, setNavigationLockMessage] = useState('')
@@ -288,6 +299,7 @@ function App() {
setPointLog([])
setStreakAnnouncement(null)
setVictoryAnnouncement(null)
setVoiceAnnouncement(null)
setSettlement({
error: '',
open: false,
@@ -441,9 +453,9 @@ function App() {
}
const winnerTeamName =
scoreState.scoreLeft >= scoreState.targetScore
hasWonGame(scoreState) && scoreState.scoreLeft > scoreState.scoreRight
? getTeamDisplayName(leftTeam)
: scoreState.scoreRight >= scoreState.targetScore
: hasWonGame(scoreState) && scoreState.scoreRight > scoreState.scoreLeft
? getTeamDisplayName(rightTeam)
: null
const nextStatus = winnerTeamName ? 'finished' : 'live'
@@ -668,6 +680,12 @@ function App() {
: current.serving === 'right'
? 'left'
: null,
initialServing:
current.initialServing === 'left'
? 'right'
: current.initialServing === 'right'
? 'left'
: null,
leftRightCourtPlayer: current.rightRightCourtPlayer,
rightRightCourtPlayer: current.leftRightCourtPlayer,
}))
@@ -692,12 +710,13 @@ function App() {
}
const setServing = (side: ScoreSide) => {
if (scoreHistory.length > 0) {
if (scoreHistory.length > 0 || scoreState.initialServing !== null) {
return
}
setScoreState((current) => ({
...current,
initialServing: side,
serving: side,
}))
}
@@ -747,6 +766,12 @@ function App() {
setScoreHistory((current) => [...current, { pointLog, scoreState }])
setPointLog(nextPointLog)
setScoreState(nextScoreState)
setVoiceAnnouncement({
key: Date.now(),
scorerName: side === 'left' ? leftTeam.playerA : rightTeam.playerA,
serverChanged: side === scoreState.serving,
serverName: getNextServerName(nextScoreState, leftTeam, rightTeam, side),
})
if (streakTitle) {
setStreakAnnouncement({
@@ -757,9 +782,7 @@ function App() {
})
}
const reachedTarget =
nextScoreState.scoreLeft >= nextScoreState.targetScore ||
nextScoreState.scoreRight >= nextScoreState.targetScore
const reachedTarget = hasWonGame(nextScoreState)
if (reachedTarget) {
setVictoryAnnouncement({
@@ -988,6 +1011,7 @@ function App() {
selectedGroup={selectedGroup}
streakAnnouncement={streakAnnouncement}
victoryAnnouncement={victoryAnnouncement}
voiceAnnouncement={voiceAnnouncement}
targetDate={targetDate}
onApplyMatchup={applyMatchup}
onCloseFinishDialog={closeSettlementDialog}
@@ -1081,7 +1105,7 @@ function getServerHistoryIndex(
return null
}
return server.slot === 'playerA' ? 0 : 1
return getMirroredCourt(getServiceCourt(state.scoreLeft)) === 'left' ? 0 : 1
}
if (state.serving === 'right') {
@@ -1091,12 +1115,44 @@ function getServerHistoryIndex(
return null
}
return server.slot === 'playerB' ? 2 : 3
return getServiceCourt(state.scoreRight) === 'right' ? 2 : 3
}
return null
}
function getNextServerName(
state: ScoreState,
leftTeam: GroupTeam,
rightTeam: GroupTeam,
side: ScoreSide,
) {
if (side === 'left') {
return getServingPlayer(leftTeam, state.leftRightCourtPlayer, state.scoreLeft)?.name ?? ''
}
return getServingPlayer(rightTeam, state.rightRightCourtPlayer, state.scoreRight)?.name ?? ''
}
function hasWonGame(state: ScoreState) {
const leadingScore = Math.max(state.scoreLeft, state.scoreRight)
const trailingScore = Math.min(state.scoreLeft, state.scoreRight)
if (leadingScore < state.targetScore) {
return false
}
if (leadingScore >= 30) {
return true
}
if (trailingScore >= state.targetScore - 1) {
return leadingScore - trailingScore >= 2
}
return true
}
function formatPlayedAt(timestamp: number) {
return new Date(timestamp * 1000).toLocaleString('zh-TW', { hour12: false })
}