Commit 94cab0cd authored by 陈冲's avatar 陈冲

fix: 添加了提交分数5秒提示倒计时

parent 02b57a26
...@@ -36,7 +36,7 @@ const ROOM_ENTRY_LIMIT: usize = 20; ...@@ -36,7 +36,7 @@ const ROOM_ENTRY_LIMIT: usize = 20;
///游戏结束排序最大用户数 ///游戏结束排序最大用户数
const ROOM_SCORE_RANK_LIMIT: usize = 10; const ROOM_SCORE_RANK_LIMIT: usize = 10;
const GAME_START_COUNTDOWN_SECS: u64 = 3; const GAME_START_COUNTDOWN_SECS: u64 = 3;
const GAME_RESULT_WAIT_SECS: u64 = 2; const GAME_RESULT_WAIT_SECS: u64 = 5;
const GAME_RUNNING_SECS: u64 = 60; const GAME_RUNNING_SECS: u64 = 60;
//管理员退出5分钟后,关闭所有玩家的连接 //管理员退出5分钟后,关闭所有玩家的连接
const ADMIN_ABSENT_DEACTIVATE_PLAYER_SECS: u64 = 5 * 60; const ADMIN_ABSENT_DEACTIVATE_PLAYER_SECS: u64 = 5 * 60;
...@@ -1031,11 +1031,6 @@ async fn handle_room_command( ...@@ -1031,11 +1031,6 @@ async fn handle_room_command(
if game.insert().await { if game.insert().await {
if !already_top_ranked && (1..=ROOM_SCORE_RANK_LIMIT as i64).contains(&rank) { if !already_top_ranked && (1..=ROOM_SCORE_RANK_LIMIT as i64).contains(&rank) {
CLIENT_TOP_RANKED_MAP.insert(userid.to_string(), true); CLIENT_TOP_RANKED_MAP.insert(userid.to_string(), true);
if let Ok(mut state) = ROOM_STATE.lock() {
if let Some(player) = state.players.get_mut(userid) {
player.already_top_ranked = true;
}
}
} }
let mut payload = json!({ let mut payload = json!({
...@@ -1077,19 +1072,32 @@ async fn handle_room_command( ...@@ -1077,19 +1072,32 @@ async fn handle_room_command(
} }
} }
// 获取房间状态 // 获取房间状态
"room_state" => { "room_state" | "status" => {
if let Ok(state) = ROOM_STATE.lock() { if let Ok(state) = ROOM_STATE.lock() {
let gameId = state.current_game_id.clone().unwrap_or_default(); let gameId = state.current_game_id.clone().unwrap_or_default();
let count = state.players.len(); let count = state.players.len();
let ranking = state.current_ranking_payload(); let ranking = state.current_ranking_payload();
let status = state.status as i32; let status = state.status as i32;
let player_rank = if is_admin {
Value::Null
} else {
state.player_rank(userid).map_or(Value::Null, |rank| json!(rank))
};
let player_score = if is_admin {
Value::Null
} else {
state
.players
.get(userid)
.map_or(Value::Null, |player| json!(player.score))
};
// let snapshot = state.snapshot(); // let snapshot = state.snapshot();
drop(state); drop(state);
emit_msgpack( emit_msgpack(
&socket, &socket,
evt, evt,
&ok_resp(Some( &ok_resp(Some(
json!({"status":status,"list":ranking,"count":count,"gameId":gameId,}), json!({"status":status,"list":ranking,"count":count,"gameId":gameId,"rank":player_rank,"score":player_score,}),
)), )),
); );
} }
......
...@@ -66,6 +66,20 @@ export function $format_str(str: string, num: number) { ...@@ -66,6 +66,20 @@ export function $format_str(str: string, num: number) {
let toastElement: HTMLDivElement | undefined; let toastElement: HTMLDivElement | undefined;
let toastTimer: ReturnType<typeof window.setTimeout> | undefined; let toastTimer: ReturnType<typeof window.setTimeout> | undefined;
export function $hideToast() {
if (toastTimer) {
window.clearTimeout(toastTimer);
toastTimer = undefined;
}
if (!toastElement) {
return;
}
toastElement.style.opacity = '0';
toastElement.style.transform = 'translate(-50%, -40%)';
}
export function $toast(message?: string, duration = 2000) { export function $toast(message?: string, duration = 2000) {
if (!message) { if (!message) {
return; return;
......
...@@ -2,7 +2,7 @@ import { ref } from 'vue' ...@@ -2,7 +2,7 @@ import { ref } from 'vue'
import { useRouter } from 'vue-router' import { useRouter } from 'vue-router'
import type { Socket } from 'socket.io-client' import type { Socket } from 'socket.io-client'
import { initSocket, onSocketMessage, sendSocketMessage } from '@/commons/ws' import { initSocket, onSocketMessage, sendSocketMessage } from '@/commons/ws'
import { $getWechat, $read, $save, $toast, postScreenGameScore } from '@/commons/utils' import { $getWechat, $hideToast, $read, $save, $toast, postScreenGameScore } from '@/commons/utils'
type SocketPayload = { type SocketPayload = {
msg?: string msg?: string
...@@ -26,6 +26,7 @@ type GameSocketOptions = { ...@@ -26,6 +26,7 @@ type GameSocketOptions = {
onRescoreSubmitted?: (recount: number) => void onRescoreSubmitted?: (recount: number) => void
onRoomClosed?: (data: any) => void onRoomClosed?: (data: any) => void
onGameRecover?: (data: any) => void onGameRecover?: (data: any) => void
onStatus?: (data: any) => void
onEvent?: (evt: string, payload: SocketPayload) => void onEvent?: (evt: string, payload: SocketPayload) => void
onRoomBack?: (data: any) => void onRoomBack?: (data: any) => void
} }
...@@ -52,28 +53,59 @@ export function joinSharedRoom(nickname: string, token: string, avatar: string, ...@@ -52,28 +53,59 @@ export function joinSharedRoom(nickname: string, token: string, avatar: string,
return sendSocketMessage('room_join', { nickname: nickname, token, avatar, gameId: gameId }) return sendSocketMessage('room_join', { nickname: nickname, token, avatar, gameId: gameId })
} }
const networkDisconnectToastDuration = 30 * 60 * 1000;
export function sendGameMessage(cmd: string, data: any = {}) { export function sendGameMessage(cmd: string, data: any = {}) {
return sendSocketMessage(cmd, data) const sent = sendSocketMessage(cmd, data)
if (!sent) {
$toast('网络不好,分数无法提交', networkDisconnectToastDuration)
}
return sent
}
export function requestGameStatus() {
return sendSocketMessage('status')
} }
export const userJoinStatus = ref(false) export const userJoinStatus = ref(false)
export const userTop10RankStatus = ref(false) export const userTop10RankStatus = ref(false)
export const isGameSocketConnected = ref(false)
export function useGameSocket(options: GameSocketOptions) { export function useGameSocket(options: GameSocketOptions) {
const router = useRouter() const router = useRouter()
let socket: Socket | null = null let socket: Socket | null = null
let offSocketMessage: (() => void) | undefined let offSocketMessage: (() => void) | undefined
let statusTimer: ReturnType<typeof window.setInterval> | undefined
const domain = $read('domain', ''); const domain = $read('domain', '');
let resubmitcount = 0; let resubmitcount = 0;
const stopStatusPolling = () => {
if (statusTimer) {
window.clearInterval(statusTimer)
statusTimer = undefined
}
}
const startStatusPolling = () => {
stopStatusPolling()
requestGameStatus()
statusTimer = window.setInterval(requestGameStatus, 1000)
}
socket = initSocket({ socket = initSocket({
socket_path: '/socket.io', socket_path: '/socket.io',
client_ns: `${domain}/ws`, client_ns: `${domain}/ws`,
auth: options.auth, auth: options.auth,
onConnect: (socket) => { onConnect: (socket) => {
isGameSocketConnected.value = true;
$hideToast();
startStatusPolling();
options.onConnect?.(); options.onConnect?.();
}, },
onDisconnect: (_reason: string) => { onDisconnect: (_reason: string) => {
// $toast('网络中断,尝试重新连接...') // $toast('网络中断,尝试重新连接...')
isGameSocketConnected.value = false;
stopStatusPolling();
$toast('网络连接已断开,请检查网络后重新进入游戏', networkDisconnectToastDuration)
socket = null; socket = null;
}, },
onError: (error: any) => { onError: (error: any) => {
...@@ -136,6 +168,7 @@ export function useGameSocket(options: GameSocketOptions) { ...@@ -136,6 +168,7 @@ export function useGameSocket(options: GameSocketOptions) {
//是否上过前十榜 //是否上过前十榜
userTop10RankStatus.value = data.alreadyTopRanked userTop10RankStatus.value = data.alreadyTopRanked
$toast('您已进入游戏房间') $toast('您已进入游戏房间')
userTop10RankStatus.value = data.alreadyTopRanked
userJoinStatus.value = true userJoinStatus.value = true
break break
} }
...@@ -148,6 +181,7 @@ export function useGameSocket(options: GameSocketOptions) { ...@@ -148,6 +181,7 @@ export function useGameSocket(options: GameSocketOptions) {
let { rank, remainingSeconds, score } = data; let { rank, remainingSeconds, score } = data;
//返回的时间可能会慢一秒 //返回的时间可能会慢一秒
options.onGameRecover?.({ rank, remainingSeconds, score }); options.onGameRecover?.({ rank, remainingSeconds, score });
options.onGameRecover?.({ rank, remainingSeconds, score });
break break
} }
case 'game_start_result': { case 'game_start_result': {
...@@ -162,7 +196,6 @@ export function useGameSocket(options: GameSocketOptions) { ...@@ -162,7 +196,6 @@ export function useGameSocket(options: GameSocketOptions) {
break break
} }
case 'submit_score_save_result': { case 'submit_score_save_result': {
//if (!userTop10RankStatus) {//保证每一轮玩家数据完整性, 暂时不需要这个条件
const submittedScore = options.onScoreSubmitted?.(true, data) const submittedScore = options.onScoreSubmitted?.(true, data)
const token_origin = $getWechat()?.token_origin const token_origin = $getWechat()?.token_origin
if (token_origin && submittedScore && Number.isFinite(submittedScore.score)) { if (token_origin && submittedScore && Number.isFinite(submittedScore.score)) {
...@@ -174,7 +207,13 @@ export function useGameSocket(options: GameSocketOptions) { ...@@ -174,7 +207,13 @@ export function useGameSocket(options: GameSocketOptions) {
rank_no: submittedScore.rank_no, rank_no: submittedScore.rank_no,
}) })
} }
//} break
}
case 'status_result': {
if (data?.gameId && data.gameId !== options.gameId) {
return
}
options.onStatus?.(data)
break break
} }
case 'room_back_result': { case 'room_back_result': {
...@@ -189,6 +228,7 @@ export function useGameSocket(options: GameSocketOptions) { ...@@ -189,6 +228,7 @@ export function useGameSocket(options: GameSocketOptions) {
case 'room_rank_result': { case 'room_rank_result': {
//如果是游戏开始中再进入会收到这个排行榜消息 //如果是游戏开始中再进入会收到这个排行榜消息
options.onShowRank?.(data); options.onShowRank?.(data);
options.onShowRank?.(data);
return; return;
} }
default: { default: {
...@@ -207,6 +247,10 @@ export function useGameSocket(options: GameSocketOptions) { ...@@ -207,6 +247,10 @@ export function useGameSocket(options: GameSocketOptions) {
socket, socket,
userJoinStatus, userJoinStatus,
userTop10RankStatus, userTop10RankStatus,
offSocketMessage requestGameStatus,
offSocketMessage: () => {
stopStatusPolling()
offSocketMessage?.()
}
} }
} }
...@@ -34,6 +34,8 @@ const countdownInterval = ref(60) ...@@ -34,6 +34,8 @@ const countdownInterval = ref(60)
const isDivDescVisible = ref(false) const isDivDescVisible = ref(false)
const tick = ref(0) //点击次数 const tick = ref(0) //点击次数
const rank = ref(0) //排名 const rank = ref(0) //排名
const roomStatus = ref(0)
const hasFinalScoreResult = ref(false)
const currentGuFrame = ref(0) const currentGuFrame = ref(0)
const isGuPlaying = ref(false) const isGuPlaying = ref(false)
const token = ref(wechat?.token ?? '') const token = ref(wechat?.token ?? '')
...@@ -51,6 +53,47 @@ const confirmRefresh = (event: BeforeUnloadEvent) => { ...@@ -51,6 +53,47 @@ const confirmRefresh = (event: BeforeUnloadEvent) => {
event.returnValue = '' event.returnValue = ''
} }
function getServerRank(data: any, fallback?: unknown) {
const value = typeof data === 'object' && data
? data.rank ?? data.rank_no ?? data.rankNo ?? data.game_rank ?? data.gameRank ?? fallback
: data
const nextRank = Number(value)
return Number.isFinite(nextRank) && nextRank > 0 ? nextRank : 0
}
function showFinalScoreIfReady() {
if (roomStatus.value !== 4 || !hasFinalScoreResult.value) return
hasFinalScoreResult.value = false
showScoreView()
}
function markFinalScoreResult() {
hasFinalScoreResult.value = true
showFinalScoreIfReady()
}
function syncFinalStatus(data: any) {
const nextStatus = Number(data?.status)
if (!Number.isFinite(nextStatus)) return
roomStatus.value = nextStatus
if (nextStatus === 3) {
return
}
if (nextStatus !== 4) return
const nextRank = getServerRank(data)
const nextScore = Number(data?.score)
if (nextRank > 0) {
rank.value = nextRank
}
if (Number.isFinite(nextScore) && nextScore >= 0) {
tick.value = nextScore
}
showFinalScoreIfReady()
}
function setDivDescVisible(visible: boolean) { function setDivDescVisible(visible: boolean) {
isDivDescVisible.value = visible isDivDescVisible.value = visible
} }
...@@ -62,21 +105,27 @@ function stopGameCountdown() { ...@@ -62,21 +105,27 @@ function stopGameCountdown() {
} }
} }
function submitScore(save: boolean = false) { function submitScore(save: boolean = false, currentTick = tick.value) {
//最后提交保存在数据库中 //最后提交保存在数据库中
if (save) { if (save) {
//item_num 游戏项目(1到6) //item_num 游戏项目(1到6)
//wechat 原始token //wechat 原始token
sendGameMessage('submit_score_save', { const sent = sendGameMessage('submit_score_save', {
score: tick.value, score: currentTick,
wechat: wechat.token, wechat: wechat.token,
item_num: 1, item_num: 1,
nickname: wechat.nickname, nickname: wechat.nickname,
avatar: wechat.avatar, avatar: wechat.avatar,
rank: rank.value, rank: rank.value,
}) })
if (sent && !userTop10RankStatus.value) {
window.setTimeout(() => {
$toast('成绩提交中,请稍候...', 5000)
}, 0)
}
return sent
} else { } else {
sendGameMessage('submit_score', { score: tick.value }) return sendGameMessage('submit_score', { score: currentTick })
} }
} }
...@@ -92,9 +141,11 @@ function startGameCountdown(seconds = 60) { ...@@ -92,9 +141,11 @@ function startGameCountdown(seconds = 60) {
countdownInterval.value = 0 countdownInterval.value = 0
gameCountdownTimer = undefined gameCountdownTimer = undefined
setDivDescVisible(false) setDivDescVisible(false)
hasFinalScoreResult.value = false
submitScore(true) submitScore(true)
if (!userTop10RankStatus.value) { if (userTop10RankStatus.value) {
$toast('成绩提交中,请稍候...', 2000) userJoinStatus.value = false
$toast('一人只能上榜领奖一次,您的福利已到手,把机会留给其他玩家吧', 30000)
} }
return return
} }
...@@ -125,6 +176,8 @@ function resetToLoadingView() { ...@@ -125,6 +176,8 @@ function resetToLoadingView() {
} }
tick.value = 0 tick.value = 0
rank.value = 0 rank.value = 0
roomStatus.value = 0
hasFinalScoreResult.value = false
countdownInterval.value = 60 countdownInterval.value = 60
currentGuFrame.value = 0 currentGuFrame.value = 0
isGuPlaying.value = false isGuPlaying.value = false
...@@ -137,6 +190,8 @@ function startGameView() { ...@@ -137,6 +190,8 @@ function startGameView() {
stopGameCountdown() stopGameCountdown()
tick.value = 0 tick.value = 0
currentGuFrame.value = 0 currentGuFrame.value = 0
roomStatus.value = 2
hasFinalScoreResult.value = false
currentView.value = 'playing' currentView.value = 'playing'
startGameCountdown() startGameCountdown()
} }
...@@ -159,8 +214,9 @@ const touchHandler = () => { ...@@ -159,8 +214,9 @@ const touchHandler = () => {
return return
} }
tick.value += 5 const nextTick = tick.value + 5
submitScore() if (!submitScore(false, nextTick)) return
tick.value = nextTick
playGame1Music(); playGame1Music();
isGuPlaying.value = true isGuPlaying.value = true
...@@ -208,39 +264,42 @@ if (wechat) { ...@@ -208,39 +264,42 @@ if (wechat) {
onScoreSubmitted: (is_save, data) => { onScoreSubmitted: (is_save, data) => {
if (!is_save) {//非保存状态下 if (!is_save) {//非保存状态下
const nextScore = Number(typeof data === 'object' ? data?.score : NaN) const nextScore = Number(typeof data === 'object' ? data?.score : NaN)
const nextRank = Number(typeof data === 'object' ? data?.rank : data) const nextRank = getServerRank(data)
const remainingSeconds = Number(typeof data === 'object' ? data?.remainingSeconds : NaN) const remainingSeconds = Number(typeof data === 'object' ? data?.remainingSeconds : NaN)
if (Number.isFinite(nextScore) && nextScore >= 0) { if (Number.isFinite(nextScore) && nextScore >= 0) {
tick.value = nextScore tick.value = nextScore
} }
if (Number.isFinite(nextRank) && nextRank > 0) { if (nextRank > 0) {
rank.value = nextRank rank.value = nextRank
} }
if (Number.isFinite(remainingSeconds) && remainingSeconds >= 0) { if (Number.isFinite(remainingSeconds) && remainingSeconds >= 0) {
syncGameCountdown(remainingSeconds) syncGameCountdown(remainingSeconds)
} }
} else if (typeof data === 'object' && data) { } else if (typeof data === 'object' && data) {
const nextRank = Number(data.rank) const nextRank = getServerRank(data)
if (Number.isFinite(nextRank) && nextRank > 0) { if (nextRank > 0) {
rank.value = nextRank rank.value = nextRank
} }
showScoreView() markFinalScoreResult()
} }
return { code: 'JGQF', score: tick.value, rank_no: rank.value } return { code: 'JGQF', score: tick.value, rank_no: rank.value }
}, },
onStatus: syncFinalStatus,
onRescoreSubmitted: (_recount) => { onRescoreSubmitted: (_recount) => {
submitScore(true); submitScore(true)
}, },
onGameRecover: (data) => { onGameRecover: (data) => {
const recoveredRank = Number(data?.rank) const recoveredRank = getServerRank(data)
const recoveredScore = Number(data?.score) const recoveredScore = Number(data?.score)
const recoveredSeconds = Number(data?.remainingSeconds) const recoveredSeconds = Number(data?.remainingSeconds)
rank.value = Number.isFinite(recoveredRank) && recoveredRank > 0 ? recoveredRank : 0 rank.value = recoveredRank
tick.value = Number.isFinite(recoveredScore) && recoveredScore > 0 ? recoveredScore : 0 tick.value = Number.isFinite(recoveredScore) && recoveredScore > 0 ? recoveredScore : 0
showGameRule.value = false showGameRule.value = false
// showGameRank.value = false // showGameRank.value = false
currentView.value = 'playing' roomStatus.value = 2
hasFinalScoreResult.value = false
currentView.value = 'playing'
startGameCountdown( startGameCountdown(
Number.isFinite(recoveredSeconds) && recoveredSeconds > 0 ? recoveredSeconds : 0, Number.isFinite(recoveredSeconds) && recoveredSeconds > 0 ? recoveredSeconds : 0,
) )
......
...@@ -37,6 +37,8 @@ const countdownInterval = ref(60) ...@@ -37,6 +37,8 @@ const countdownInterval = ref(60)
const isDivDescVisible = ref(false) const isDivDescVisible = ref(false)
const score = ref(0) const score = ref(0)
const rank = ref(0) //排名 const rank = ref(0) //排名
const roomStatus = ref(0)
const hasFinalScoreResult = ref(false)
const rankList = ref<RankPlayer[]>([]) const rankList = ref<RankPlayer[]>([])
const displayRankList = computed<RankPlayer[]>(() => { const displayRankList = computed<RankPlayer[]>(() => {
const list = rankList.value.slice(0, 10) const list = rankList.value.slice(0, 10)
...@@ -76,6 +78,56 @@ const confirmRefresh = (event: BeforeUnloadEvent) => { ...@@ -76,6 +78,56 @@ const confirmRefresh = (event: BeforeUnloadEvent) => {
event.returnValue = '' event.returnValue = ''
} }
function getServerRank(data: any, fallback?: unknown) {
const value = typeof data === 'object' && data
? data.rank ?? data.rank_no ?? data.rankNo ?? data.game_rank ?? data.gameRank ?? fallback
: data
const nextRank = Number(value)
return Number.isFinite(nextRank) && nextRank > 0 ? nextRank : 0
}
function showFinalScoreIfReady() {
if (roomStatus.value !== 4 || !hasFinalScoreResult.value) return
hasFinalScoreResult.value = false
showGameOverRank()
}
function markFinalScoreResult() {
hasFinalScoreResult.value = true
showFinalScoreIfReady()
}
function syncFinalStatus(data: any) {
const nextStatus = Number(data?.status)
if (!Number.isFinite(nextStatus)) return
roomStatus.value = nextStatus
if (nextStatus === 3) {
return
}
if (nextStatus !== 4) return
if (Array.isArray(data?.list)) {
rankList.value = data.list.map((item: any, index: number) => ({
rank: getServerRank(item, index + 1),
nickname: String(item?.nickname ?? ''),
avatar: String(item?.avatar ?? ''),
score: Number(item?.score ?? 0),
}))
}
const nextRank = getServerRank(data)
const nextScore = Number(data?.score)
if (nextRank > 0) {
rank.value = nextRank
}
if (Number.isFinite(nextScore) && nextScore >= 0) {
score.value = nextScore
}
showFinalScoreIfReady()
}
function setDivDescVisible(visible: boolean) { function setDivDescVisible(visible: boolean) {
isDivDescVisible.value = visible isDivDescVisible.value = visible
} }
...@@ -92,7 +144,7 @@ function submitScore(save: boolean = false, currentTick = score.value) { ...@@ -92,7 +144,7 @@ function submitScore(save: boolean = false, currentTick = score.value) {
if (save) { if (save) {
//item_num 游戏项目(1到6) //item_num 游戏项目(1到6)
//wechat 原始token //wechat 原始token
sendGameMessage('submit_score_save', { const sent = sendGameMessage('submit_score_save', {
score: currentTick, score: currentTick,
wechat: wechat.token, wechat: wechat.token,
item_num: 2, item_num: 2,
...@@ -100,8 +152,14 @@ function submitScore(save: boolean = false, currentTick = score.value) { ...@@ -100,8 +152,14 @@ function submitScore(save: boolean = false, currentTick = score.value) {
avatar: wechat.avatar, avatar: wechat.avatar,
rank: rank.value, rank: rank.value,
}) })
if (sent && !userTop10RankStatus.value) {
window.setTimeout(() => {
$toast('成绩提交中,请稍候...', 5000)
}, 0)
}
return sent
} else { } else {
sendGameMessage('submit_score', { score: currentTick }) return sendGameMessage('submit_score', { score: currentTick })
} }
} }
...@@ -118,6 +176,7 @@ function startGameCountdown(seconds = 60) { ...@@ -118,6 +176,7 @@ function startGameCountdown(seconds = 60) {
gameCountdownTimer = undefined gameCountdownTimer = undefined
currentView.value = 'loading' currentView.value = 'loading'
setDivDescVisible(false) setDivDescVisible(false)
hasFinalScoreResult.value = false
submitScore(true) submitScore(true)
if (userTop10RankStatus.value) { if (userTop10RankStatus.value) {
userJoinStatus.value = false; userJoinStatus.value = false;
...@@ -125,7 +184,7 @@ function startGameCountdown(seconds = 60) { ...@@ -125,7 +184,7 @@ function startGameCountdown(seconds = 60) {
$toast('一人只能上榜领奖一次,您的福利已到手,把机会留给其他玩家吧', 30000) $toast('一人只能上榜领奖一次,您的福利已到手,把机会留给其他玩家吧', 30000)
//这里还要再发 //这里还要再发
} else { } else {
$toast('成绩提交中,请稍候...', 2000) $toast('成绩提交中,请稍候...', 5000)
} }
return return
} }
...@@ -156,7 +215,11 @@ function resetToLoadingView() { ...@@ -156,7 +215,11 @@ function resetToLoadingView() {
} }
score.value = 0 score.value = 0
rank.value = 0 rank.value = 0
roomStatus.value = 0
hasFinalScoreResult.value = false
rankList.value = [] rankList.value = []
roomStatus.value = 0
hasFinalScoreResult.value = false
countdownInterval.value = 60 countdownInterval.value = 60
// currentGu.value = guFrames[0] // currentGu.value = guFrames[0]
isGuPlaying.value = false isGuPlaying.value = false
...@@ -170,9 +233,15 @@ function startGameView() { ...@@ -170,9 +233,15 @@ function startGameView() {
stopGameCountdown() stopGameCountdown()
score.value = 0 score.value = 0
rank.value = 0 rank.value = 0
roomStatus.value = 0
hasFinalScoreResult.value = false
rankList.value = [] rankList.value = []
roomStatus.value = 0
hasFinalScoreResult.value = false
// currentGu.value = guFrames[0] // currentGu.value = guFrames[0]
showGameRank.value = false showGameRank.value = false
roomStatus.value = 2
hasFinalScoreResult.value = false
currentView.value = 'playing' currentView.value = 'playing'
startGameCountdown() startGameCountdown()
} }
...@@ -186,9 +255,9 @@ function showGameOverRank() { ...@@ -186,9 +255,9 @@ function showGameOverRank() {
} }
const touchHandler = (currentScore: number) => { const touchHandler = (currentScore: number) => {
if (!submitScore(false, currentScore)) return
score.value = currentScore score.value = currentScore
playGame2Music(); playGame2Music();
submitScore(false, currentScore)
} }
if (wechat) { if (wechat) {
...@@ -217,12 +286,12 @@ if (wechat) { ...@@ -217,12 +286,12 @@ if (wechat) {
onScoreSubmitted: (is_save, data) => { onScoreSubmitted: (is_save, data) => {
if (!is_save) {//非保存状态下 if (!is_save) {//非保存状态下
const nextScore = Number(typeof data === 'object' ? data?.score : NaN) const nextScore = Number(typeof data === 'object' ? data?.score : NaN)
const nextRank = Number(typeof data === 'object' ? data?.rank : data) const nextRank = getServerRank(data)
const remainingSeconds = Number(typeof data === 'object' ? data?.remainingSeconds : NaN) const remainingSeconds = Number(typeof data === 'object' ? data?.remainingSeconds : NaN)
if (Number.isFinite(nextScore) && nextScore >= 0) { if (Number.isFinite(nextScore) && nextScore >= 0) {
score.value = nextScore score.value = nextScore
} }
if (Number.isFinite(nextRank) && nextRank > 0) { if (nextRank > 0) {
rank.value = nextRank rank.value = nextRank
} }
if (Number.isFinite(remainingSeconds) && remainingSeconds >= 0) { if (Number.isFinite(remainingSeconds) && remainingSeconds >= 0) {
...@@ -231,36 +300,37 @@ if (wechat) { ...@@ -231,36 +300,37 @@ if (wechat) {
} else if (typeof data === 'object' && data) { } else if (typeof data === 'object' && data) {
if (Array.isArray(data.list)) { if (Array.isArray(data.list)) {
rankList.value = data.list.map((item: any, index: number) => ({ rankList.value = data.list.map((item: any, index: number) => ({
rank: Number(item?.rank ?? index + 1), rank: getServerRank(item, index + 1),
nickname: String(item?.nickname ?? ''), nickname: String(item?.nickname ?? ''),
avatar: String(item?.avatar ?? ''), avatar: String(item?.avatar ?? ''),
score: Number(item?.score ?? 0), score: Number(item?.score ?? 0),
})) }))
} }
const nextRank = Number(data.rank) const nextRank = getServerRank(data)
if (Number.isFinite(nextRank) && nextRank > 0) { if (nextRank > 0) {
rank.value = nextRank rank.value = nextRank
} }
if (!userTop10RankStatus.value) { markFinalScoreResult()
showGameOverRank()
}
} }
return { code: 'JBJF', score: score.value, rank_no: rank.value } return { code: 'JBJF', score: score.value, rank_no: rank.value }
}, },
onStatus: syncFinalStatus,
onRescoreSubmitted: (_recount) => { onRescoreSubmitted: (_recount) => {
submitScore(true); submitScore(true)
}, },
onGameRecover: (data) => { onGameRecover: (data) => {
const recoveredRank = Number(data?.rank) const recoveredRank = getServerRank(data)
const recoveredScore = Number(data?.score) const recoveredScore = Number(data?.score)
const recoveredSeconds = Number(data?.remainingSeconds) const recoveredSeconds = Number(data?.remainingSeconds)
rank.value = Number.isFinite(recoveredRank) && recoveredRank > 0 ? recoveredRank : 0 rank.value = recoveredRank
score.value = Number.isFinite(recoveredScore) && recoveredScore > 0 ? recoveredScore : 0 score.value = Number.isFinite(recoveredScore) && recoveredScore > 0 ? recoveredScore : 0
showGameRule.value = false showGameRule.value = false
// showGameRank.value = false // showGameRank.value = false
currentView.value = 'playing' roomStatus.value = 2
hasFinalScoreResult.value = false
currentView.value = 'playing'
startGameCountdown( startGameCountdown(
Number.isFinite(recoveredSeconds) && recoveredSeconds > 0 ? recoveredSeconds : 0, Number.isFinite(recoveredSeconds) && recoveredSeconds > 0 ? recoveredSeconds : 0,
) )
......
<script setup lang="ts"> <script setup lang="ts">
import { onBeforeUnmount, ref } from 'vue'; import { onBeforeUnmount, ref } from 'vue';
import { isGameSocketConnected } from '@/composables/useGameSocket';
defineProps<{ defineProps<{
imageUrls: Record<string, string> imageUrls: Record<string, string>
...@@ -74,6 +75,9 @@ function rotateCoin(event: PointerEvent) { ...@@ -74,6 +75,9 @@ function rotateCoin(event: PointerEvent) {
while (clockwiseAngle >= FULL_ROTATION) { while (clockwiseAngle >= FULL_ROTATION) {
clockwiseAngle -= FULL_ROTATION clockwiseAngle -= FULL_ROTATION
if (!isGameSocketConnected.value) {
return
}
score.value += 5 score.value += 5
emit('touch', score.value) emit('touch', score.value)
} }
......
...@@ -48,6 +48,8 @@ const countdownInterval = ref(60) ...@@ -48,6 +48,8 @@ const countdownInterval = ref(60)
const isDivDescVisible = ref(false) const isDivDescVisible = ref(false)
const score = ref(0) const score = ref(0)
const rank = ref(0) //排名 const rank = ref(0) //排名
const roomStatus = ref(0)
const hasFinalScoreResult = ref(false)
const rankList = ref<RankPlayer[]>([]) const rankList = ref<RankPlayer[]>([])
const displayRankList = computed<RankPlayer[]>(() => { const displayRankList = computed<RankPlayer[]>(() => {
const list = rankList.value.slice(0, 10) const list = rankList.value.slice(0, 10)
...@@ -88,6 +90,56 @@ const confirmRefresh = (event: BeforeUnloadEvent) => { ...@@ -88,6 +90,56 @@ const confirmRefresh = (event: BeforeUnloadEvent) => {
event.returnValue = '' event.returnValue = ''
} }
function getServerRank(data: any, fallback?: unknown) {
const value = typeof data === 'object' && data
? data.rank ?? data.rank_no ?? data.rankNo ?? data.game_rank ?? data.gameRank ?? fallback
: data
const nextRank = Number(value)
return Number.isFinite(nextRank) && nextRank > 0 ? nextRank : 0
}
function showFinalScoreIfReady() {
if (roomStatus.value !== 4 || !hasFinalScoreResult.value) return
hasFinalScoreResult.value = false
showGameOverRank()
}
function markFinalScoreResult() {
hasFinalScoreResult.value = true
showFinalScoreIfReady()
}
function syncFinalStatus(data: any) {
const nextStatus = Number(data?.status)
if (!Number.isFinite(nextStatus)) return
roomStatus.value = nextStatus
if (nextStatus === 3) {
return
}
if (nextStatus !== 4) return
if (Array.isArray(data?.list)) {
rankList.value = data.list.map((item: any, index: number) => ({
rank: getServerRank(item, index + 1),
nickname: String(item?.nickname ?? ''),
avatar: String(item?.avatar ?? ''),
score: Number(item?.score ?? 0),
}))
}
const nextRank = getServerRank(data)
const nextScore = Number(data?.score)
if (nextRank > 0) {
rank.value = nextRank
}
if (Number.isFinite(nextScore) && nextScore >= 0) {
score.value = nextScore
}
showFinalScoreIfReady()
}
function setDivDescVisible(visible: boolean) { function setDivDescVisible(visible: boolean) {
isDivDescVisible.value = visible isDivDescVisible.value = visible
} }
...@@ -100,13 +152,13 @@ function stopGameCountdown() { ...@@ -100,13 +152,13 @@ function stopGameCountdown() {
} }
function submitScore(save: boolean = false, currentTick = score.value) { function submitScore(save: boolean = false, currentTick = score.value) {
if (!wechat && !isLocalMode) return if (!wechat && !isLocalMode) return false
if (save) { if (save) {
if (isLocalMode && !wechat) { if (isLocalMode && !wechat) {
console.log('[game3 本地模式] 提交最终分数:', currentTick) console.log('[game3 本地模式] 提交最终分数:', currentTick)
return return true
} }
sendGameMessage('submit_score_save', { const sent = sendGameMessage('submit_score_save', {
score: currentTick, score: currentTick,
wechat: wechat.token, wechat: wechat.token,
item_num: 3, item_num: 3,
...@@ -114,12 +166,18 @@ function submitScore(save: boolean = false, currentTick = score.value) { ...@@ -114,12 +166,18 @@ function submitScore(save: boolean = false, currentTick = score.value) {
avatar: wechat.avatar, avatar: wechat.avatar,
rank: rank.value, rank: rank.value,
}) })
if (sent && !userTop10RankStatus.value) {
window.setTimeout(() => {
$toast('成绩提交中,请稍候...', 5000)
}, 0)
}
return sent
} else { } else {
if (isLocalMode && !wechat) { if (isLocalMode && !wechat) {
console.log('[game3 本地模式] 实时分数:', currentTick) console.log('[game3 本地模式] 实时分数:', currentTick)
return return true
} }
sendGameMessage('submit_score', { score: currentTick }) return sendGameMessage('submit_score', { score: currentTick })
} }
} }
...@@ -136,13 +194,14 @@ function startGameCountdown(seconds = 60) { ...@@ -136,13 +194,14 @@ function startGameCountdown(seconds = 60) {
gameCountdownTimer = undefined gameCountdownTimer = undefined
currentView.value = 'loading' currentView.value = 'loading'
setDivDescVisible(false) setDivDescVisible(false)
hasFinalScoreResult.value = false
submitScore(true) submitScore(true)
if (userTop10RankStatus.value) { if (userTop10RankStatus.value) {
userJoinStatus.value = false; userJoinStatus.value = false;
showGameRank.value = false; showGameRank.value = false;
$toast('一人只能上榜领奖一次,您的福利已到手,把机会留给其他玩家吧', 30000) $toast('一人只能上榜领奖一次,您的福利已到手,把机会留给其他玩家吧', 30000)
} else { } else {
$toast('成绩提交中,请稍候...', 2000) $toast('成绩提交中,请稍候...', 5000)
} }
return return
} }
...@@ -173,7 +232,11 @@ function resetToLoadingView() { ...@@ -173,7 +232,11 @@ function resetToLoadingView() {
} }
score.value = 0 score.value = 0
rank.value = 0 rank.value = 0
roomStatus.value = 0
hasFinalScoreResult.value = false
rankList.value = [] rankList.value = []
roomStatus.value = 0
hasFinalScoreResult.value = false
countdownInterval.value = 60 countdownInterval.value = 60
// currentGu.value = guFrames[0] // currentGu.value = guFrames[0]
isGuPlaying.value = false isGuPlaying.value = false
...@@ -187,9 +250,15 @@ function startGameView() { ...@@ -187,9 +250,15 @@ function startGameView() {
stopGameCountdown() stopGameCountdown()
score.value = 0 score.value = 0
rank.value = 0 rank.value = 0
roomStatus.value = 0
hasFinalScoreResult.value = false
rankList.value = [] rankList.value = []
roomStatus.value = 0
hasFinalScoreResult.value = false
// currentGu.value = guFrames[0] // currentGu.value = guFrames[0]
showGameRank.value = false showGameRank.value = false
roomStatus.value = 2
hasFinalScoreResult.value = false
currentView.value = 'playing' currentView.value = 'playing'
startGameCountdown() startGameCountdown()
} }
...@@ -209,20 +278,20 @@ const touchHandler = (mole: boolean) => { ...@@ -209,20 +278,20 @@ const touchHandler = (mole: boolean) => {
let scoreDelta = mole ? 5 : -3; let scoreDelta = mole ? 5 : -3;
let nextTick = score.value + scoreDelta let nextTick = score.value + scoreDelta
if (nextTick < 0) nextTick = 0 if (nextTick < 0) nextTick = 0
if (!submitScore(false, nextTick)) return
score.value = nextTick score.value = nextTick
if (mole) { if (mole) {
playGame3Music(1); playGame3Music(1);
} else { } else {
playGame3Music(2); playGame3Music(2);
} }
submitScore(false, nextTick)
} }
const scoreChangeHandler = (delta: number) => { const scoreChangeHandler = (delta: number) => {
let nextTick = score.value + delta let nextTick = score.value + delta
if (nextTick < 0) nextTick = 0 if (nextTick < 0) nextTick = 0
if (!submitScore(false, nextTick)) return
score.value = nextTick score.value = nextTick
submitScore(false, nextTick)
} }
if (wechat) { if (wechat) {
...@@ -245,12 +314,12 @@ if (wechat) { ...@@ -245,12 +314,12 @@ if (wechat) {
onScoreSubmitted: (is_save, data) => { onScoreSubmitted: (is_save, data) => {
if (!is_save) {//非保存状态下 if (!is_save) {//非保存状态下
const nextScore = Number(typeof data === 'object' ? data?.score : NaN) const nextScore = Number(typeof data === 'object' ? data?.score : NaN)
const nextRank = Number(typeof data === 'object' ? data?.rank : data) const nextRank = getServerRank(data)
const remainingSeconds = Number(typeof data === 'object' ? data?.remainingSeconds : NaN) const remainingSeconds = Number(typeof data === 'object' ? data?.remainingSeconds : NaN)
if (Number.isFinite(nextScore) && nextScore >= 0) { if (Number.isFinite(nextScore) && nextScore >= 0) {
score.value = nextScore score.value = nextScore
} }
if (Number.isFinite(nextRank) && nextRank > 0) { if (nextRank > 0) {
rank.value = nextRank rank.value = nextRank
} }
if (Number.isFinite(remainingSeconds) && remainingSeconds >= 0) { if (Number.isFinite(remainingSeconds) && remainingSeconds >= 0) {
...@@ -259,36 +328,37 @@ if (wechat) { ...@@ -259,36 +328,37 @@ if (wechat) {
} else if (typeof data === 'object' && data) { } else if (typeof data === 'object' && data) {
if (Array.isArray(data.list)) { if (Array.isArray(data.list)) {
rankList.value = data.list.map((item: any, index: number) => ({ rankList.value = data.list.map((item: any, index: number) => ({
rank: Number(item?.rank ?? index + 1), rank: getServerRank(item, index + 1),
nickname: String(item?.nickname ?? ''), nickname: String(item?.nickname ?? ''),
avatar: String(item?.avatar ?? ''), avatar: String(item?.avatar ?? ''),
score: Number(item?.score ?? 0), score: Number(item?.score ?? 0),
})) }))
} }
const nextRank = Number(data.rank) const nextRank = getServerRank(data)
if (Number.isFinite(nextRank) && nextRank > 0) { if (nextRank > 0) {
rank.value = nextRank rank.value = nextRank
} }
if (!userTop10RankStatus.value) { markFinalScoreResult()
showGameOverRank()
}
} }
return { code: 'MSYF', score: score.value , rank_no: rank.value} return { code: 'MSYF', score: score.value , rank_no: rank.value}
}, },
onStatus: syncFinalStatus,
onRescoreSubmitted: (_recount) => { onRescoreSubmitted: (_recount) => {
submitScore(true); submitScore(true)
}, },
onGameRecover: (data) => { onGameRecover: (data) => {
const recoveredRank = Number(data?.rank) const recoveredRank = getServerRank(data)
const recoveredScore = Number(data?.score) const recoveredScore = Number(data?.score)
const recoveredSeconds = Number(data?.remainingSeconds) const recoveredSeconds = Number(data?.remainingSeconds)
rank.value = Number.isFinite(recoveredRank) && recoveredRank > 0 ? recoveredRank : 0 rank.value = recoveredRank
score.value = Number.isFinite(recoveredScore) && recoveredScore > 0 ? recoveredScore : 0 score.value = Number.isFinite(recoveredScore) && recoveredScore > 0 ? recoveredScore : 0
showGameRule.value = false showGameRule.value = false
showGameRank.value = false showGameRank.value = false
currentView.value = 'playing' roomStatus.value = 2
hasFinalScoreResult.value = false
currentView.value = 'playing'
startGameCountdown( startGameCountdown(
Number.isFinite(recoveredSeconds) && recoveredSeconds > 0 ? recoveredSeconds : 0, Number.isFinite(recoveredSeconds) && recoveredSeconds > 0 ? recoveredSeconds : 0,
) )
...@@ -318,7 +388,9 @@ onMounted(() => { ...@@ -318,7 +388,9 @@ onMounted(() => {
// TODO_LOCAL: 本地模拟游戏开始,稍后部署上线前删除此段 // TODO_LOCAL: 本地模拟游戏开始,稍后部署上线前删除此段
if ($is_run_local()) { if ($is_run_local()) {
showGameRule.value = false showGameRule.value = false
currentView.value = 'playing' roomStatus.value = 2
hasFinalScoreResult.value = false
currentView.value = 'playing'
startGameCountdown() startGameCountdown()
} }
}) })
......
...@@ -59,6 +59,8 @@ const countdownInterval = ref(60); ...@@ -59,6 +59,8 @@ const countdownInterval = ref(60);
const isDivDescVisible = ref(false); const isDivDescVisible = ref(false);
const tick = ref(0); // 点击次数/摇动次数 const tick = ref(0); // 点击次数/摇动次数
const rank = ref(0); // 排名 const rank = ref(0); // 排名
const roomStatus = ref(0)
const hasFinalScoreResult = ref(false)
const currentHorse = ref(imageUrls.horse); const currentHorse = ref(imageUrls.horse);
const currentYaoyiyao = ref(imageUrls.yaoyiyao); const currentYaoyiyao = ref(imageUrls.yaoyiyao);
const token = ref(wechat?.token ?? ""); const token = ref(wechat?.token ?? "");
...@@ -76,6 +78,48 @@ const confirmRefresh = (event: BeforeUnloadEvent) => { ...@@ -76,6 +78,48 @@ const confirmRefresh = (event: BeforeUnloadEvent) => {
event.returnValue = ""; event.returnValue = "";
}; };
function getServerRank(data: any, fallback?: unknown) {
const value =
typeof data === "object" && data
? data.rank ?? data.rank_no ?? data.rankNo ?? data.game_rank ?? data.gameRank ?? fallback
: data;
const nextRank = Number(value);
return Number.isFinite(nextRank) && nextRank > 0 ? nextRank : 0;
}
function showFinalScoreIfReady() {
if (roomStatus.value !== 4 || !hasFinalScoreResult.value) return
hasFinalScoreResult.value = false
showScoreView()
}
function markFinalScoreResult() {
hasFinalScoreResult.value = true
showFinalScoreIfReady()
}
function syncFinalStatus(data: any) {
const nextStatus = Number(data?.status)
if (!Number.isFinite(nextStatus)) return
roomStatus.value = nextStatus
if (nextStatus === 3) {
return
}
if (nextStatus !== 4) return
const nextRank = getServerRank(data)
const nextScore = Number(data?.score)
if (nextRank > 0) {
rank.value = nextRank
}
if (Number.isFinite(nextScore) && nextScore >= 0) {
tick.value = nextScore
}
showFinalScoreIfReady()
}
function setDivDescVisible(visible: boolean) { function setDivDescVisible(visible: boolean) {
isDivDescVisible.value = visible; isDivDescVisible.value = visible;
} }
...@@ -87,13 +131,13 @@ function stopGameCountdown() { ...@@ -87,13 +131,13 @@ function stopGameCountdown() {
} }
} }
function submitScore(save: boolean = false) { function submitScore(save: boolean = false, currentTick = tick.value) {
// 最后提交保存在数据库中 // 最后提交保存在数据库中
if (save) { if (save) {
// item_num 游戏项目(1到6) // item_num 游戏项目(1到6)
// wechat 原始token // wechat 原始token
sendGameMessage("submit_score_save", { return sendGameMessage("submit_score_save", {
score: tick.value, score: currentTick,
wechat: wechat.token, wechat: wechat.token,
item_num: 4, item_num: 4,
nickname: wechat.nickname, nickname: wechat.nickname,
...@@ -101,7 +145,7 @@ function submitScore(save: boolean = false) { ...@@ -101,7 +145,7 @@ function submitScore(save: boolean = false) {
rank: rank.value, rank: rank.value,
}); });
} else { } else {
sendGameMessage("submit_score", { score: tick.value }); return sendGameMessage("submit_score", { score: currentTick });
} }
} }
...@@ -123,7 +167,7 @@ function startGameCountdown(seconds = 60) { ...@@ -123,7 +167,7 @@ function startGameCountdown(seconds = 60) {
// showGameRank.value = false; // showGameRank.value = false;
$toast('一人只能上榜领奖一次,您的福利已到手,把机会留给其他玩家吧', 30000) $toast('一人只能上榜领奖一次,您的福利已到手,把机会留给其他玩家吧', 30000)
} else { } else {
$toast('成绩提交中,请稍候...', 2000) $toast('成绩提交中,请稍候...', 5000)
} }
return; return;
} }
...@@ -189,8 +233,9 @@ function backToWaiting() { } ...@@ -189,8 +233,9 @@ function backToWaiting() { }
function handleRoomBack() { } function handleRoomBack() { }
const touchHandler = () => { const touchHandler = () => {
tick.value += 5; const nextTick = tick.value + 5;
submitScore(); if (!submitScore(false, nextTick)) return;
tick.value = nextTick;
}; };
const showGameRuleHandler = () => { const showGameRuleHandler = () => {
...@@ -233,7 +278,7 @@ if (wechat || isLocalMode) { ...@@ -233,7 +278,7 @@ if (wechat || isLocalMode) {
}, },
onScoreSubmitted: (is_save, data) => { onScoreSubmitted: (is_save, data) => {
const nextScore = Number(typeof data === "object" ? data?.score : NaN); const nextScore = Number(typeof data === "object" ? data?.score : NaN);
const nextRank = Number(typeof data === "object" ? data?.rank : data); const nextRank = getServerRank(data);
const remainingSeconds = Number( const remainingSeconds = Number(
typeof data === "object" ? data?.remainingSeconds : NaN, typeof data === "object" ? data?.remainingSeconds : NaN,
); );
...@@ -247,17 +292,18 @@ if (wechat || isLocalMode) { ...@@ -247,17 +292,18 @@ if (wechat || isLocalMode) {
) { ) {
syncGameCountdown(remainingSeconds); syncGameCountdown(remainingSeconds);
} }
if (Number.isFinite(nextRank) && nextRank > 0) { if (nextRank > 0) {
rank.value = nextRank; rank.value = nextRank;
} }
if (is_save && !userTop10RankStatus.value) { if (is_save) {
showScoreView(); markFinalScoreResult();
} }
return { code: "CMYF", score: tick.value, rank_no: rank.value }; return { code: "CMYF", score: tick.value, rank_no: rank.value };
}, },
onRescoreSubmitted: (_recount) => { onStatus: syncFinalStatus,
submitScore(true); onRescoreSubmitted: (_recount) => {
}, submitScore(true)
},
onRoomClosed: showLoadingView, onRoomClosed: showLoadingView,
onRoomBack: handleRoomBack, onRoomBack: handleRoomBack,
onGameRecover: () => { onGameRecover: () => {
......
...@@ -86,6 +86,8 @@ const countdownInterval = ref(60) ...@@ -86,6 +86,8 @@ const countdownInterval = ref(60)
const isDivDescVisible = ref(false) const isDivDescVisible = ref(false)
const tick = ref(0) //点击次数 const tick = ref(0) //点击次数
const rank = ref(0) //排名 const rank = ref(0) //排名
const roomStatus = ref(0)
const hasFinalScoreResult = ref(false)
const guFrames: [string, string, string] = [imageUrls.gu01, imageUrls.gu02, imageUrls.gu03] const guFrames: [string, string, string] = [imageUrls.gu01, imageUrls.gu02, imageUrls.gu03]
const currentGu = ref(imageUrls.gu01) const currentGu = ref(imageUrls.gu01)
const isGuPlaying = ref(false) const isGuPlaying = ref(false)
...@@ -118,6 +120,56 @@ const confirmRefresh = (event: BeforeUnloadEvent) => { ...@@ -118,6 +120,56 @@ const confirmRefresh = (event: BeforeUnloadEvent) => {
event.returnValue = '' event.returnValue = ''
} }
function getServerRank(data: any, fallback?: unknown) {
const value = typeof data === 'object' && data
? data.rank ?? data.rank_no ?? data.rankNo ?? data.game_rank ?? data.gameRank ?? fallback
: data
const nextRank = Number(value)
return Number.isFinite(nextRank) && nextRank > 0 ? nextRank : 0
}
function showFinalScoreIfReady() {
if (roomStatus.value !== 4 || !hasFinalScoreResult.value) return
hasFinalScoreResult.value = false
showGameOverRank()
}
function markFinalScoreResult() {
hasFinalScoreResult.value = true
showFinalScoreIfReady()
}
function syncFinalStatus(data: any) {
const nextStatus = Number(data?.status)
if (!Number.isFinite(nextStatus)) return
roomStatus.value = nextStatus
if (nextStatus === 3) {
return
}
if (nextStatus !== 4) return
if (Array.isArray(data?.list)) {
rankList.value = data.list.map((item: any, index: number) => ({
rank: getServerRank(item, index + 1),
nickname: String(item?.nickname ?? ''),
avatar: String(item?.avatar ?? ''),
score: Number(item?.score ?? 0),
}))
}
const nextRank = getServerRank(data)
const nextScore = Number(data?.score)
if (nextRank > 0) {
rank.value = nextRank
}
if (Number.isFinite(nextScore) && nextScore >= 0) {
tick.value = nextScore
}
showFinalScoreIfReady()
}
function setDivDescVisible(visible: boolean) { function setDivDescVisible(visible: boolean) {
isDivDescVisible.value = visible isDivDescVisible.value = visible
} }
...@@ -129,13 +181,13 @@ function stopGameCountdown() { ...@@ -129,13 +181,13 @@ function stopGameCountdown() {
} }
} }
function submitScore(save: boolean = false) { function submitScore(save: boolean = false, currentTick = tick.value) {
//最后提交保存在数据库中 //最后提交保存在数据库中
if (save) { if (save) {
//item_num 游戏项目(1到6) //item_num 游戏项目(1到6)
//wechat 原始token //wechat 原始token
const data = { const data = {
score: tick.value, score: currentTick,
wechat: wechat.token, wechat: wechat.token,
item_num: 5, item_num: 5,
nickname: wechat.nickname, nickname: wechat.nickname,
...@@ -143,11 +195,11 @@ function submitScore(save: boolean = false) { ...@@ -143,11 +195,11 @@ function submitScore(save: boolean = false) {
rank: rank.value, rank: rank.value,
} }
console.log('[WebSocket] send submit_score_save:', JSON.stringify(data, null, 2)) console.log('[WebSocket] send submit_score_save:', JSON.stringify(data, null, 2))
sendGameMessage('submit_score_save', data) return sendGameMessage('submit_score_save', data)
} else { } else {
const data = { score: tick.value } const data = { score: currentTick }
console.log('[WebSocket] send submit_score:', JSON.stringify(data)) console.log('[WebSocket] send submit_score:', JSON.stringify(data))
sendGameMessage('submit_score', data) return sendGameMessage('submit_score', data)
} }
} }
...@@ -163,13 +215,14 @@ function startGameCountdown(seconds = 60) { ...@@ -163,13 +215,14 @@ function startGameCountdown(seconds = 60) {
countdownInterval.value = 0 countdownInterval.value = 0
gameCountdownTimer = undefined gameCountdownTimer = undefined
setDivDescVisible(false) setDivDescVisible(false)
hasFinalScoreResult.value = false
submitScore(true) submitScore(true)
if (userTop10RankStatus.value) { if (userTop10RankStatus.value) {
userJoinStatus.value = false; userJoinStatus.value = false;
// showGameRank.value = false; // showGameRank.value = false;
$toast('一人只能上榜领奖一次,您的福利已到手,把机会留给其他玩家吧', 30000) $toast('一人只能上榜领奖一次,您的福利已到手,把机会留给其他玩家吧', 30000)
} else { } else {
$toast('成绩提交中,请稍候...', 2000) $toast('成绩提交中,请稍候...', 5000)
} }
return return
} }
...@@ -210,7 +263,11 @@ function resetToLoadingView() { ...@@ -210,7 +263,11 @@ function resetToLoadingView() {
} }
tick.value = 0 tick.value = 0
rank.value = 0 rank.value = 0
roomStatus.value = 0
hasFinalScoreResult.value = false
rankList.value = [] rankList.value = []
roomStatus.value = 0
hasFinalScoreResult.value = false
countdownInterval.value = 60 countdownInterval.value = 60
currentGu.value = guFrames[0] currentGu.value = guFrames[0]
isGuPlaying.value = false isGuPlaying.value = false
...@@ -224,9 +281,15 @@ function startGameView() { ...@@ -224,9 +281,15 @@ function startGameView() {
stopGameCountdown() stopGameCountdown()
tick.value = 0 tick.value = 0
rank.value = 0 rank.value = 0
roomStatus.value = 0
hasFinalScoreResult.value = false
rankList.value = [] rankList.value = []
roomStatus.value = 0
hasFinalScoreResult.value = false
currentGu.value = guFrames[0] currentGu.value = guFrames[0]
showGameRank.value = false showGameRank.value = false
roomStatus.value = 2
hasFinalScoreResult.value = false
currentView.value = 'playing' currentView.value = 'playing'
startGameCountdown() startGameCountdown()
} }
...@@ -244,9 +307,10 @@ function handleRoomBack() { ...@@ -244,9 +307,10 @@ function handleRoomBack() {
} }
const hitHandler = (score: number) => { const hitHandler = (score: number) => {
tick.value += score const nextTick = tick.value + score
if (!submitScore(false, nextTick)) return
tick.value = nextTick
console.log('[WebSocket] 圈中马!得分:', score, '当前总分:', tick.value) console.log('[WebSocket] 圈中马!得分:', score, '当前总分:', tick.value)
submitScore()
// console.log('[Audio] playGame1Music() 被调用 - 播放 game1.mp3 背景音乐') // console.log('[Audio] playGame1Music() 被调用 - 播放 game1.mp3 背景音乐')
// playGame1Music() // playGame1Music()
} }
...@@ -275,12 +339,12 @@ if (wechat) { ...@@ -275,12 +339,12 @@ if (wechat) {
onScoreSubmitted: (is_save, data) => { onScoreSubmitted: (is_save, data) => {
if (!is_save) {//非保存状态下 if (!is_save) {//非保存状态下
const nextScore = Number(typeof data === 'object' ? data?.score : NaN) const nextScore = Number(typeof data === 'object' ? data?.score : NaN)
const nextRank = Number(typeof data === 'object' ? data?.rank : data) const nextRank = getServerRank(data)
const remainingSeconds = Number(typeof data === 'object' ? data?.remainingSeconds : NaN) const remainingSeconds = Number(typeof data === 'object' ? data?.remainingSeconds : NaN)
if (Number.isFinite(nextScore) && nextScore >= 0) { if (Number.isFinite(nextScore) && nextScore >= 0) {
tick.value = nextScore tick.value = nextScore
} }
if (Number.isFinite(nextRank) && nextRank > 0) { if (nextRank > 0) {
rank.value = nextRank rank.value = nextRank
} }
if (Number.isFinite(remainingSeconds) && remainingSeconds >= 0) { if (Number.isFinite(remainingSeconds) && remainingSeconds >= 0) {
...@@ -289,25 +353,24 @@ if (wechat) { ...@@ -289,25 +353,24 @@ if (wechat) {
} else if (typeof data === 'object' && data) { } else if (typeof data === 'object' && data) {
if (Array.isArray(data.list)) { if (Array.isArray(data.list)) {
rankList.value = data.list.map((item: any, index: number) => ({ rankList.value = data.list.map((item: any, index: number) => ({
rank: Number(item?.rank ?? index + 1), rank: getServerRank(item, index + 1),
nickname: String(item?.nickname ?? ''), nickname: String(item?.nickname ?? ''),
avatar: String(item?.avatar ?? ''), avatar: String(item?.avatar ?? ''),
score: Number(item?.score ?? 0), score: Number(item?.score ?? 0),
})) }))
} }
const nextRank = Number(data.rank) const nextRank = getServerRank(data)
if (Number.isFinite(nextRank) && nextRank > 0) { if (nextRank > 0) {
rank.value = nextRank rank.value = nextRank
} }
if (!userTop10RankStatus.value) { markFinalScoreResult()
showGameOverRank()
}
} }
return { code: 'QCNF', score: tick.value, rank_no: rank.value } return { code: 'QCNF', score: tick.value, rank_no: rank.value }
}, },
onStatus: syncFinalStatus,
onRescoreSubmitted: (_recount) => { onRescoreSubmitted: (_recount) => {
submitScore(true); submitScore(true)
}, },
onRoomClosed: showLoadingView, onRoomClosed: showLoadingView,
onRoomBack: handleRoomBack, onRoomBack: handleRoomBack,
......
...@@ -44,6 +44,8 @@ const countdownInterval = ref(60) ...@@ -44,6 +44,8 @@ const countdownInterval = ref(60)
const isDivDescVisible = ref(false) const isDivDescVisible = ref(false)
const score = ref(0) const score = ref(0)
const rank = ref(0) //排名 const rank = ref(0) //排名
const roomStatus = ref(0)
const hasFinalScoreResult = ref(false)
const rankList = ref<RankPlayer[]>([]) const rankList = ref<RankPlayer[]>([])
const displayRankList = computed<RankPlayer[]>(() => { const displayRankList = computed<RankPlayer[]>(() => {
const list = rankList.value.slice(0, 10) const list = rankList.value.slice(0, 10)
...@@ -83,6 +85,56 @@ const confirmRefresh = (event: BeforeUnloadEvent) => { ...@@ -83,6 +85,56 @@ const confirmRefresh = (event: BeforeUnloadEvent) => {
event.returnValue = '' event.returnValue = ''
} }
function getServerRank(data: any, fallback?: unknown) {
const value = typeof data === 'object' && data
? data.rank ?? data.rank_no ?? data.rankNo ?? data.game_rank ?? data.gameRank ?? fallback
: data
const nextRank = Number(value)
return Number.isFinite(nextRank) && nextRank > 0 ? nextRank : 0
}
function showFinalScoreIfReady() {
if (roomStatus.value !== 4 || !hasFinalScoreResult.value) return
hasFinalScoreResult.value = false
showGameOverRank()
}
function markFinalScoreResult() {
hasFinalScoreResult.value = true
showFinalScoreIfReady()
}
function syncFinalStatus(data: any) {
const nextStatus = Number(data?.status)
if (!Number.isFinite(nextStatus)) return
roomStatus.value = nextStatus
if (nextStatus === 3) {
return
}
if (nextStatus !== 4) return
if (Array.isArray(data?.list)) {
rankList.value = data.list.map((item: any, index: number) => ({
rank: getServerRank(item, index + 1),
nickname: String(item?.nickname ?? ''),
avatar: String(item?.avatar ?? ''),
score: Number(item?.score ?? 0),
}))
}
const nextRank = getServerRank(data)
const nextScore = Number(data?.score)
if (nextRank > 0) {
rank.value = nextRank
}
if (Number.isFinite(nextScore) && nextScore >= 0) {
score.value = nextScore
}
showFinalScoreIfReady()
}
function setDivDescVisible(visible: boolean) { function setDivDescVisible(visible: boolean) {
isDivDescVisible.value = visible isDivDescVisible.value = visible
} }
...@@ -99,7 +151,7 @@ function submitScore(save: boolean = false, currentTick = score.value) { ...@@ -99,7 +151,7 @@ function submitScore(save: boolean = false, currentTick = score.value) {
if (save) { if (save) {
//item_num 游戏项目(1到6) //item_num 游戏项目(1到6)
//wechat 原始token //wechat 原始token
sendGameMessage('submit_score_save', { const sent = sendGameMessage('submit_score_save', {
score: currentTick, score: currentTick,
wechat: wechat.token, wechat: wechat.token,
item_num: 6, item_num: 6,
...@@ -107,8 +159,14 @@ function submitScore(save: boolean = false, currentTick = score.value) { ...@@ -107,8 +159,14 @@ function submitScore(save: boolean = false, currentTick = score.value) {
avatar: wechat.avatar, avatar: wechat.avatar,
rank: rank.value, rank: rank.value,
}) })
if (sent && !userTop10RankStatus.value) {
window.setTimeout(() => {
$toast('成绩提交中,请稍候...', 5000)
}, 0)
}
return sent
} else { } else {
sendGameMessage('submit_score', { score: currentTick }) return sendGameMessage('submit_score', { score: currentTick })
} }
} }
...@@ -125,13 +183,14 @@ function startGameCountdown(seconds = 60) { ...@@ -125,13 +183,14 @@ function startGameCountdown(seconds = 60) {
gameCountdownTimer = undefined gameCountdownTimer = undefined
currentView.value = 'loading' currentView.value = 'loading'
setDivDescVisible(false) setDivDescVisible(false)
hasFinalScoreResult.value = false
submitScore(true) submitScore(true)
if (userTop10RankStatus.value) { if (userTop10RankStatus.value) {
userJoinStatus.value = false; userJoinStatus.value = false;
showGameRank.value = false; showGameRank.value = false;
$toast('一人只能上榜领奖一次,您的福利已到手,把机会留给其他玩家吧', 30000) $toast('一人只能上榜领奖一次,您的福利已到手,把机会留给其他玩家吧', 30000)
} else { } else {
$toast('成绩提交中,请稍候...', 2000) $toast('成绩提交中,请稍候...', 5000)
} }
return return
} }
...@@ -162,7 +221,11 @@ function resetToLoadingView() { ...@@ -162,7 +221,11 @@ function resetToLoadingView() {
} }
score.value = 0 score.value = 0
rank.value = 0 rank.value = 0
roomStatus.value = 0
hasFinalScoreResult.value = false
rankList.value = [] rankList.value = []
roomStatus.value = 0
hasFinalScoreResult.value = false
countdownInterval.value = 60 countdownInterval.value = 60
// currentGu.value = guFrames[0] // currentGu.value = guFrames[0]
isGuPlaying.value = false isGuPlaying.value = false
...@@ -176,9 +239,15 @@ function startGameView() { ...@@ -176,9 +239,15 @@ function startGameView() {
stopGameCountdown() stopGameCountdown()
score.value = 0 score.value = 0
rank.value = 0 rank.value = 0
roomStatus.value = 0
hasFinalScoreResult.value = false
rankList.value = [] rankList.value = []
roomStatus.value = 0
hasFinalScoreResult.value = false
// currentGu.value = guFrames[0] // currentGu.value = guFrames[0]
showGameRank.value = false showGameRank.value = false
roomStatus.value = 2
hasFinalScoreResult.value = false
currentView.value = 'playing' currentView.value = 'playing'
startGameCountdown() startGameCountdown()
} }
...@@ -194,10 +263,10 @@ function showGameOverRank() { ...@@ -194,10 +263,10 @@ function showGameOverRank() {
const touchHandler = (mole: boolean) => { const touchHandler = (mole: boolean) => {
let scoreDelta = mole ? 5 : 0; let scoreDelta = mole ? 5 : 0;
let nextTick = score.value + scoreDelta let nextTick = score.value + scoreDelta
score.value = nextTick
if (mole) { if (mole) {
if (!submitScore(false, nextTick)) return
score.value = nextTick
playGame6Music(1); playGame6Music(1);
submitScore(false, nextTick)
} else { } else {
playGame6Music(2); playGame6Music(2);
} }
...@@ -223,12 +292,12 @@ if (wechat) { ...@@ -223,12 +292,12 @@ if (wechat) {
onScoreSubmitted: (is_save, data) => { onScoreSubmitted: (is_save, data) => {
if (!is_save) {//非保存状态下 if (!is_save) {//非保存状态下
const nextScore = Number(typeof data === 'object' ? data?.score : NaN) const nextScore = Number(typeof data === 'object' ? data?.score : NaN)
const nextRank = Number(typeof data === 'object' ? data?.rank : data) const nextRank = getServerRank(data)
const remainingSeconds = Number(typeof data === 'object' ? data?.remainingSeconds : NaN) const remainingSeconds = Number(typeof data === 'object' ? data?.remainingSeconds : NaN)
if (Number.isFinite(nextScore) && nextScore >= 0) { if (Number.isFinite(nextScore) && nextScore >= 0) {
score.value = nextScore score.value = nextScore
} }
if (Number.isFinite(nextRank) && nextRank > 0) { if (nextRank > 0) {
rank.value = nextRank rank.value = nextRank
} }
if (Number.isFinite(remainingSeconds) && remainingSeconds >= 0) { if (Number.isFinite(remainingSeconds) && remainingSeconds >= 0) {
...@@ -237,36 +306,37 @@ if (wechat) { ...@@ -237,36 +306,37 @@ if (wechat) {
} else if (typeof data === 'object' && data) { } else if (typeof data === 'object' && data) {
if (Array.isArray(data.list)) { if (Array.isArray(data.list)) {
rankList.value = data.list.map((item: any, index: number) => ({ rankList.value = data.list.map((item: any, index: number) => ({
rank: Number(item?.rank ?? index + 1), rank: getServerRank(item, index + 1),
nickname: String(item?.nickname ?? ''), nickname: String(item?.nickname ?? ''),
avatar: String(item?.avatar ?? ''), avatar: String(item?.avatar ?? ''),
score: Number(item?.score ?? 0), score: Number(item?.score ?? 0),
})) }))
} }
const nextRank = Number(data.rank) const nextRank = getServerRank(data)
if (Number.isFinite(nextRank) && nextRank > 0) { if (nextRank > 0) {
rank.value = nextRank rank.value = nextRank
} }
if (!userTop10RankStatus.value) { markFinalScoreResult()
showGameOverRank()
}
} }
return { code: 'FYDT', score: score.value, rank_no: rank.value } return { code: 'FYDT', score: score.value, rank_no: rank.value }
}, },
onStatus: syncFinalStatus,
onRescoreSubmitted: (_recount) => { onRescoreSubmitted: (_recount) => {
submitScore(true); submitScore(true)
}, },
onGameRecover: (data) => { onGameRecover: (data) => {
const recoveredRank = Number(data?.rank) const recoveredRank = getServerRank(data)
const recoveredScore = Number(data?.score) const recoveredScore = Number(data?.score)
const recoveredSeconds = Number(data?.remainingSeconds) const recoveredSeconds = Number(data?.remainingSeconds)
rank.value = Number.isFinite(recoveredRank) && recoveredRank > 0 ? recoveredRank : 0 rank.value = recoveredRank
score.value = Number.isFinite(recoveredScore) && recoveredScore > 0 ? recoveredScore : 0 score.value = Number.isFinite(recoveredScore) && recoveredScore > 0 ? recoveredScore : 0
showGameRule.value = false showGameRule.value = false
showGameRank.value = false showGameRank.value = false
currentView.value = 'playing' roomStatus.value = 2
hasFinalScoreResult.value = false
currentView.value = 'playing'
startGameCountdown( startGameCountdown(
Number.isFinite(recoveredSeconds) && recoveredSeconds > 0 ? recoveredSeconds : 0, Number.isFinite(recoveredSeconds) && recoveredSeconds > 0 ? recoveredSeconds : 0,
) )
......
...@@ -24,11 +24,16 @@ type AdminGameSocketOptions = { ...@@ -24,11 +24,16 @@ type AdminGameSocketOptions = {
export function useAdminGameSocket(options: AdminGameSocketOptions) { export function useAdminGameSocket(options: AdminGameSocketOptions) {
let offSocketMessage: (() => void) | undefined let offSocketMessage: (() => void) | undefined
let statusTimer: ReturnType<typeof window.setInterval> | undefined
function requestRoomState() { function requestRoomState() {
return sendSocketMessage('room_state') return sendSocketMessage('room_state')
} }
function requestStatus() {
return sendSocketMessage('status')
}
function createRoom(index: number) { function createRoom(index: number) {
return sendSocketMessage('room_create', index) return sendSocketMessage('room_create', index)
} }
...@@ -65,9 +70,15 @@ export function useAdminGameSocket(options: AdminGameSocketOptions) { ...@@ -65,9 +70,15 @@ export function useAdminGameSocket(options: AdminGameSocketOptions) {
} }
} }
window.setTimeout(requestRoomState, 200) window.setTimeout(requestRoomState, 200)
window.setTimeout(requestStatus, 200)
statusTimer = window.setInterval(requestStatus, 1000)
}, },
onDisconnect: (_reason: string) => { onDisconnect: (_reason: string) => {
console.log('[useAdminGameSocket] WebSocket 断开连接,reason:', _reason, 'gameId:', options.gameId); console.log('[useAdminGameSocket] WebSocket disconnected:', _reason, 'gameId:', options.gameId);
if (statusTimer) {
window.clearInterval(statusTimer)
statusTimer = undefined
}
}, },
onError: (error: any) => { onError: (error: any) => {
console.error('[useAdminGameSocket] WebSocket 错误:', error, 'gameId:', options.gameId); console.error('[useAdminGameSocket] WebSocket 错误:', error, 'gameId:', options.gameId);
...@@ -97,7 +108,8 @@ export function useAdminGameSocket(options: AdminGameSocketOptions) { ...@@ -97,7 +108,8 @@ export function useAdminGameSocket(options: AdminGameSocketOptions) {
} }
switch (evt) { switch (evt) {
case 'room_state_result': { case 'room_state_result':
case 'status_result': {
if (data.gameId) { if (data.gameId) {
//如果这两个值不一致说明浏览器有直接更新游戏房间号 //如果这两个值不一致说明浏览器有直接更新游戏房间号
if (data.gameId != options.gameId) { if (data.gameId != options.gameId) {
...@@ -134,11 +146,16 @@ export function useAdminGameSocket(options: AdminGameSocketOptions) { ...@@ -134,11 +146,16 @@ export function useAdminGameSocket(options: AdminGameSocketOptions) {
}) })
onBeforeUnmount(() => { onBeforeUnmount(() => {
if (statusTimer) {
window.clearInterval(statusTimer)
statusTimer = undefined
}
offSocketMessage?.() offSocketMessage?.()
}) })
return { return {
requestRoomState, requestRoomState,
requestStatus,
createRoom, createRoom,
backRoom, backRoom,
startGame, startGame,
......
...@@ -38,6 +38,9 @@ const playerCount = ref(0) ...@@ -38,6 +38,9 @@ const playerCount = ref(0)
const rankPlayers = ref<Player[]>(Array.from({ length: RANK_LIMIT }, (_, index) => emptyRankPlayer(index))) const rankPlayers = ref<Player[]>(Array.from({ length: RANK_LIMIT }, (_, index) => emptyRankPlayer(index)))
const rank1Ref = ref<InstanceType<typeof Rank1> | null>(null) const rank1Ref = ref<InstanceType<typeof Rank1> | null>(null)
let isPlayingStartAnimation = false let isPlayingStartAnimation = false
const isSubmitWaiting = ref(false)
const submitWaitingSeconds = ref(5)
let submitWaitingTimer: ReturnType<typeof window.setInterval> | undefined
const confirmRefresh = (event: BeforeUnloadEvent) => { const confirmRefresh = (event: BeforeUnloadEvent) => {
// closeRoom(); // closeRoom();
...@@ -87,11 +90,37 @@ const seedRankPlayersFromLoading = () => { ...@@ -87,11 +90,37 @@ const seedRankPlayersFromLoading = () => {
) )
} }
function showSubmitWaiting() {
if (!isSubmitWaiting.value) { submitWaitingSeconds.value = 5
if (submitWaitingTimer) {
window.clearInterval(submitWaitingTimer)
}
submitWaitingTimer = window.setInterval(() => {
submitWaitingSeconds.value = Math.max(0, submitWaitingSeconds.value - 1)
if (submitWaitingSeconds.value <= 0 && submitWaitingTimer) {
window.clearInterval(submitWaitingTimer)
submitWaitingTimer = undefined
}
}, 1000)
}
isSubmitWaiting.value = true
}
function clearSubmitWaiting() {
isSubmitWaiting.value = false
submitWaitingSeconds.value = 5
if (submitWaitingTimer) {
window.clearInterval(submitWaitingTimer)
submitWaitingTimer = undefined
}
}
const resetRoundState = () => { const resetRoundState = () => {
playerCount.value = 0 playerCount.value = 0
loadingPlayers.value = Array.from({ length: PLAYER_LIMIT }, emptyLoadingPlayer) loadingPlayers.value = Array.from({ length: PLAYER_LIMIT }, emptyLoadingPlayer)
rankPlayers.value = Array.from({ length: RANK_LIMIT }, (_, index) => emptyRankPlayer(index)) rankPlayers.value = Array.from({ length: RANK_LIMIT }, (_, index) => emptyRankPlayer(index))
isPlayingStartAnimation = false isPlayingStartAnimation = false
clearSubmitWaiting()
} }
const gotoLoading = (reset = false) => { const gotoLoading = (reset = false) => {
...@@ -110,7 +139,7 @@ const gotoRank1WithIntro = async () => { ...@@ -110,7 +139,7 @@ const gotoRank1WithIntro = async () => {
screen.value = 'rank1' screen.value = 'rank1'
await nextTick() await nextTick()
await rank1Ref.value?.startIntro() await rank1Ref.value?.startIntro()
screen.value = 'rank2' showSubmitWaiting()
isPlayingStartAnimation = false isPlayingStartAnimation = false
} }
...@@ -118,7 +147,7 @@ const gameId = 'game1' ...@@ -118,7 +147,7 @@ const gameId = 'game1'
const { startGame, createRoom, backRoom, closeRoom } = useAdminGameSocket({ const { startGame, createRoom, backRoom, closeRoom } = useAdminGameSocket({
gameId, gameId,
onRoomState: (data: any) => { onRoomState: (data: any) => {
if (Array.isArray(data?.list)) { if ((data?.status === 0 || data?.status === 1) && Array.isArray(data?.list)) {
renderPlayers(data) renderPlayers(data)
} }
switch (data?.status) { switch (data?.status) {
...@@ -133,10 +162,17 @@ const { startGame, createRoom, backRoom, closeRoom } = useAdminGameSocket({ ...@@ -133,10 +162,17 @@ const { startGame, createRoom, backRoom, closeRoom } = useAdminGameSocket({
gotoLoading() gotoLoading()
break break
case 2: case 2:
seedRankPlayersFromLoading() screen.value = 'rank1'
screen.value = 'rank1' clearSubmitWaiting()
break break
case 3: case 3:
showSubmitWaiting()
break
case 4:
clearSubmitWaiting()
if (Array.isArray(data?.list)) {
updateRankPlayers(data.list)
}
screen.value = 'rank2' screen.value = 'rank2'
break break
} }
...@@ -211,4 +247,66 @@ watch(screen, (value) => { ...@@ -211,4 +247,66 @@ watch(screen, (value) => {
@start="startGameWithMusic" @back="backHandler" /> @start="startGameWithMusic" @back="backHandler" />
<Rank1 v-else-if="screen === 'rank1'" ref="rank1Ref" :rank-list="rankPlayers" @start="startGameWithMusic" /> <Rank1 v-else-if="screen === 'rank1'" ref="rank1Ref" :rank-list="rankPlayers" @start="startGameWithMusic" />
<Rank2 v-else :rank-list="rankPlayers" @next="nextRoundWithMusic" @back="backHandler" /> <Rank2 v-else :rank-list="rankPlayers" @next="nextRoundWithMusic" @back="backHandler" />
<div v-if="isSubmitWaiting" class="submit-waiting-overlay">
<div class="submit-waiting-title">正在等待玩家提交分数</div>
<div :key="submitWaitingSeconds" class="submit-waiting-count">{{ submitWaitingSeconds }}</div>
</div>
</template> </template>
<style scoped>
.submit-waiting-overlay {
position: fixed;
inset: 0;
z-index: 9999;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
background: radial-gradient(ellipse at center, rgba(0, 0, 0, 0.22) 0%, rgba(0, 0, 0, 0.08) 48%, transparent 72%);
color: #fff;
pointer-events: none;
}
.submit-waiting-title {
position: absolute;
bottom: 8vh;
left: 0;
width: 100%;
font-size: 56px;
font-weight: 700;
line-height: 1;
text-align: center;
text-shadow: 0 10px 24px rgba(0, 0, 0, 0.35);
}
.submit-waiting-count {
color: white;
font-size: 300pt;
font-weight: 800;
line-height: 1;
text-shadow: 0 18px 38px rgba(0, 0, 0, 0.25);
animation: submit-countdown-pop 1s ease both;
}
@keyframes submit-countdown-pop {
0% {
opacity: 0;
transform: scale(0.45);
}
18% {
opacity: 1;
transform: scale(1.14);
}
58% {
opacity: 1;
transform: scale(1);
}
100% {
opacity: 0;
transform: scale(0.84);
}
}
</style>
...@@ -52,6 +52,9 @@ const playerCount = ref(0) ...@@ -52,6 +52,9 @@ const playerCount = ref(0)
const rankPlayers = ref<Player[]>(Array.from({ length: RANK_LIMIT }, (_, index) => emptyRankPlayer(index))) const rankPlayers = ref<Player[]>(Array.from({ length: RANK_LIMIT }, (_, index) => emptyRankPlayer(index)))
const rank1Ref = ref<InstanceType<typeof Rank1> | null>(null) const rank1Ref = ref<InstanceType<typeof Rank1> | null>(null)
let isPlayingStartAnimation = false let isPlayingStartAnimation = false
const isSubmitWaiting = ref(false)
const submitWaitingSeconds = ref(5)
let submitWaitingTimer: ReturnType<typeof window.setInterval> | undefined
const confirmRefresh = (event: BeforeUnloadEvent) => { const confirmRefresh = (event: BeforeUnloadEvent) => {
// closeRoom(); // closeRoom();
...@@ -117,11 +120,37 @@ const seedRankPlayersFromLoading = () => { ...@@ -117,11 +120,37 @@ const seedRankPlayersFromLoading = () => {
) )
} }
function showSubmitWaiting() {
if (!isSubmitWaiting.value) { submitWaitingSeconds.value = 5
if (submitWaitingTimer) {
window.clearInterval(submitWaitingTimer)
}
submitWaitingTimer = window.setInterval(() => {
submitWaitingSeconds.value = Math.max(0, submitWaitingSeconds.value - 1)
if (submitWaitingSeconds.value <= 0 && submitWaitingTimer) {
window.clearInterval(submitWaitingTimer)
submitWaitingTimer = undefined
}
}, 1000)
}
isSubmitWaiting.value = true
}
function clearSubmitWaiting() {
isSubmitWaiting.value = false
submitWaitingSeconds.value = 5
if (submitWaitingTimer) {
window.clearInterval(submitWaitingTimer)
submitWaitingTimer = undefined
}
}
const resetRoundState = () => { const resetRoundState = () => {
playerCount.value = 0 playerCount.value = 0
loadingPlayers.value = Array.from({ length: PLAYER_LIMIT }, emptyLoadingPlayer) loadingPlayers.value = Array.from({ length: PLAYER_LIMIT }, emptyLoadingPlayer)
rankPlayers.value = Array.from({ length: RANK_LIMIT }, (_, index) => emptyRankPlayer(index)) rankPlayers.value = Array.from({ length: RANK_LIMIT }, (_, index) => emptyRankPlayer(index))
isPlayingStartAnimation = false isPlayingStartAnimation = false
clearSubmitWaiting()
} }
const gotoLoading = (reset = false) => { const gotoLoading = (reset = false) => {
...@@ -140,7 +169,7 @@ const gotoRank1WithIntro = async () => { ...@@ -140,7 +169,7 @@ const gotoRank1WithIntro = async () => {
screen.value = 'rank1' screen.value = 'rank1'
await nextTick() await nextTick()
await rank1Ref.value?.startIntro() await rank1Ref.value?.startIntro()
screen.value = 'rank2' showSubmitWaiting()
isPlayingStartAnimation = false isPlayingStartAnimation = false
} }
...@@ -152,7 +181,7 @@ const { startGame, createRoom, backRoom, closeRoom } = useAdminGameSocket({ ...@@ -152,7 +181,7 @@ const { startGame, createRoom, backRoom, closeRoom } = useAdminGameSocket({
}, },
onRoomState: (data: any) => { onRoomState: (data: any) => {
console.log("[Game2] onRoomState 触发,data:", JSON.stringify(data)); console.log("[Game2] onRoomState 触发,data:", JSON.stringify(data));
if (Array.isArray(data?.list)) { if ((data?.status === 0 || data?.status === 1) && Array.isArray(data?.list)) {
renderPlayers(data) renderPlayers(data)
} }
switch (data?.status) { switch (data?.status) {
...@@ -168,11 +197,18 @@ const { startGame, createRoom, backRoom, closeRoom } = useAdminGameSocket({ ...@@ -168,11 +197,18 @@ const { startGame, createRoom, backRoom, closeRoom } = useAdminGameSocket({
break break
case 2: case 2:
console.log("[Game2] status=2,准备进入 rank1,loadingPlayers:", loadingPlayers.value.length); console.log("[Game2] status=2,准备进入 rank1,loadingPlayers:", loadingPlayers.value.length);
seedRankPlayersFromLoading() console.log("[Game2] seedRankPlayersFromLoading 后,rankPlayers:", rankPlayers.value.length, JSON.stringify(rankPlayers.value.slice(0, 3)));
console.log("[Game2] seedRankPlayersFromLoading 后,rankPlayers:", rankPlayers.value.length, JSON.stringify(rankPlayers.value.slice(0, 3)));
screen.value = 'rank1' screen.value = 'rank1'
clearSubmitWaiting()
break break
case 3: case 3:
showSubmitWaiting()
break
case 4:
clearSubmitWaiting()
if (Array.isArray(data?.list)) {
updateRankPlayers(data.list)
}
screen.value = 'rank2' screen.value = 'rank2'
break break
} }
...@@ -255,4 +291,66 @@ watch(screen, (value) => { ...@@ -255,4 +291,66 @@ watch(screen, (value) => {
@start="startGameWithMusic" @back="backHandler" /> @start="startGameWithMusic" @back="backHandler" />
<Rank1 v-else-if="screen === 'rank1'" ref="rank1Ref" :rank-list="rankPlayers" @start="startGameWithMusic" /> <Rank1 v-else-if="screen === 'rank1'" ref="rank1Ref" :rank-list="rankPlayers" @start="startGameWithMusic" />
<Rank2 v-else :rank-list="rankPlayers" @next="nextRoundWithMusic" @back="backHandler" /> <Rank2 v-else :rank-list="rankPlayers" @next="nextRoundWithMusic" @back="backHandler" />
<div v-if="isSubmitWaiting" class="submit-waiting-overlay">
<div class="submit-waiting-title">正在等待玩家提交分数</div>
<div :key="submitWaitingSeconds" class="submit-waiting-count">{{ submitWaitingSeconds }}</div>
</div>
</template> </template>
<style scoped>
.submit-waiting-overlay {
position: fixed;
inset: 0;
z-index: 9999;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
background: radial-gradient(ellipse at center, rgba(0, 0, 0, 0.22) 0%, rgba(0, 0, 0, 0.08) 48%, transparent 72%);
color: #fff;
pointer-events: none;
}
.submit-waiting-title {
position: absolute;
bottom: 8vh;
left: 0;
width: 100%;
font-size: 56px;
font-weight: 700;
line-height: 1;
text-align: center;
text-shadow: 0 10px 24px rgba(0, 0, 0, 0.35);
}
.submit-waiting-count {
color: white;
font-size: 300pt;
font-weight: 800;
line-height: 1;
text-shadow: 0 18px 38px rgba(0, 0, 0, 0.25);
animation: submit-countdown-pop 1s ease both;
}
@keyframes submit-countdown-pop {
0% {
opacity: 0;
transform: scale(0.45);
}
18% {
opacity: 1;
transform: scale(1.14);
}
58% {
opacity: 1;
transform: scale(1);
}
100% {
opacity: 0;
transform: scale(0.84);
}
}
</style>
...@@ -39,6 +39,9 @@ const rankPlayers = ref<Player[]>(Array.from({ length: RANK_LIMIT }, (_, index) ...@@ -39,6 +39,9 @@ const rankPlayers = ref<Player[]>(Array.from({ length: RANK_LIMIT }, (_, index)
const horseBoosts = ref<Record<string, number>>({}); const horseBoosts = ref<Record<string, number>>({});
const rank1Ref = ref<InstanceType<typeof Rank1> | null>(null) const rank1Ref = ref<InstanceType<typeof Rank1> | null>(null)
let isPlayingStartAnimation = false let isPlayingStartAnimation = false
const isSubmitWaiting = ref(false)
const submitWaitingSeconds = ref(5)
let submitWaitingTimer: ReturnType<typeof window.setInterval> | undefined
const confirmRefresh = (event: BeforeUnloadEvent) => { const confirmRefresh = (event: BeforeUnloadEvent) => {
event.preventDefault() event.preventDefault()
...@@ -99,12 +102,38 @@ const updateRankPlayers = (data: any) => { ...@@ -99,12 +102,38 @@ const updateRankPlayers = (data: any) => {
); );
} }
function showSubmitWaiting() {
if (!isSubmitWaiting.value) { submitWaitingSeconds.value = 5
if (submitWaitingTimer) {
window.clearInterval(submitWaitingTimer)
}
submitWaitingTimer = window.setInterval(() => {
submitWaitingSeconds.value = Math.max(0, submitWaitingSeconds.value - 1)
if (submitWaitingSeconds.value <= 0 && submitWaitingTimer) {
window.clearInterval(submitWaitingTimer)
submitWaitingTimer = undefined
}
}, 1000)
}
isSubmitWaiting.value = true
}
function clearSubmitWaiting() {
isSubmitWaiting.value = false
submitWaitingSeconds.value = 5
if (submitWaitingTimer) {
window.clearInterval(submitWaitingTimer)
submitWaitingTimer = undefined
}
}
const resetRoundState = () => { const resetRoundState = () => {
playerCount.value = 0 playerCount.value = 0
loadingPlayers.value = Array.from({ length: PLAYER_LIMIT }, emptyLoadingPlayer) loadingPlayers.value = Array.from({ length: PLAYER_LIMIT }, emptyLoadingPlayer)
rankPlayers.value = Array.from({ length: RANK_LIMIT }, (_, index) => emptyRankPlayer(index)) rankPlayers.value = Array.from({ length: RANK_LIMIT }, (_, index) => emptyRankPlayer(index))
horseBoosts.value = {} horseBoosts.value = {}
isPlayingStartAnimation = false isPlayingStartAnimation = false
clearSubmitWaiting()
} }
const gotoLoading = (reset = false) => { const gotoLoading = (reset = false) => {
...@@ -125,13 +154,14 @@ const gotoRank1WithIntro = async () => { ...@@ -125,13 +154,14 @@ const gotoRank1WithIntro = async () => {
await rank1Ref.value?.startIntro() await rank1Ref.value?.startIntro()
isPlayingStartAnimation = false isPlayingStartAnimation = false
clearSubmitWaiting()
} }
function handleRoomState(data: any) { function handleRoomState(data: any) {
console.log("[Game3] onRoomState 触发,data:", JSON.stringify(data)); console.log("[Game3] onRoomState 触发,data:", JSON.stringify(data));
if (Array.isArray(data?.list)) { if ((data?.status === 0 || data?.status === 1) && Array.isArray(data?.list)) {
renderPlayers(data) renderPlayers(data)
} }
switch (data?.status) { switch (data?.status) {
case 0: case 0:
...@@ -144,7 +174,7 @@ function handleRoomState(data: any) { ...@@ -144,7 +174,7 @@ function handleRoomState(data: any) {
case 2: case 2:
console.log("[Game3] status=2,准备进入 rank1,loadingPlayers:", loadingPlayers.value.length); console.log("[Game3] status=2,准备进入 rank1,loadingPlayers:", loadingPlayers.value.length);
// 重连/中途进房时游戏已在运行,直接进入赛马画面 // 重连/中途进房时游戏已在运行,直接进入赛马画面
if (isPlayingStartAnimation) break; if (screen.value === 'rank1' || isPlayingStartAnimation) break;
isPlayingStartAnimation = true; isPlayingStartAnimation = true;
screen.value = 'rank1' screen.value = 'rank1'
console.log('[game3] handleRoomState: status=2, 直接启动赛马动画'); console.log('[game3] handleRoomState: status=2, 直接启动赛马动画');
...@@ -155,8 +185,15 @@ function handleRoomState(data: any) { ...@@ -155,8 +185,15 @@ function handleRoomState(data: any) {
}); });
break break
case 3: case 3:
screen.value = 'rank2' showSubmitWaiting()
break break
case 4:
clearSubmitWaiting()
if (Array.isArray(data?.list)) {
updateRankPlayers(data.list)
}
screen.value = 'rank2'
break
} }
} }
const gameId='game3' const gameId='game3'
...@@ -297,4 +334,66 @@ watch(screen, (value) => { ...@@ -297,4 +334,66 @@ watch(screen, (value) => {
<Rank1 v-if="screen === 'rank1'" ref="rank1Ref" :rank-list="rankPlayers" :horse-boosts="horseBoosts" <Rank1 v-if="screen === 'rank1'" ref="rank1Ref" :rank-list="rankPlayers" :horse-boosts="horseBoosts"
@start="startGameWithMusic" @next="nextRoundWithMusic" @back="backHandler" /> @start="startGameWithMusic" @next="nextRoundWithMusic" @back="backHandler" />
<Rank2 v-if="screen === 'rank2'" :rank-list="rankPlayers" @next="nextRoundWithMusic" @back="backHandler" /> <Rank2 v-if="screen === 'rank2'" :rank-list="rankPlayers" @next="nextRoundWithMusic" @back="backHandler" />
<div v-if="isSubmitWaiting" class="submit-waiting-overlay">
<div class="submit-waiting-title">正在等待玩家提交分数</div>
<div :key="submitWaitingSeconds" class="submit-waiting-count">{{ submitWaitingSeconds }}</div>
</div>
</template> </template>
<style scoped>
.submit-waiting-overlay {
position: fixed;
inset: 0;
z-index: 9999;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
background: radial-gradient(ellipse at center, rgba(0, 0, 0, 0.22) 0%, rgba(0, 0, 0, 0.08) 48%, transparent 72%);
color: #fff;
pointer-events: none;
}
.submit-waiting-title {
position: absolute;
bottom: 8vh;
left: 0;
width: 100%;
font-size: 56px;
font-weight: 700;
line-height: 1;
text-align: center;
text-shadow: 0 10px 24px rgba(0, 0, 0, 0.35);
}
.submit-waiting-count {
color: white;
font-size: 300pt;
font-weight: 800;
line-height: 1;
text-shadow: 0 18px 38px rgba(0, 0, 0, 0.25);
animation: submit-countdown-pop 1s ease both;
}
@keyframes submit-countdown-pop {
0% {
opacity: 0;
transform: scale(0.45);
}
18% {
opacity: 1;
transform: scale(1.14);
}
58% {
opacity: 1;
transform: scale(1);
}
100% {
opacity: 0;
transform: scale(0.84);
}
}
</style>
...@@ -39,6 +39,9 @@ const playerCount = ref(0) ...@@ -39,6 +39,9 @@ const playerCount = ref(0)
const rankPlayers = ref<Player[]>(Array.from({ length: RANK_LIMIT }, (_, index) => emptyRankPlayer(index))) const rankPlayers = ref<Player[]>(Array.from({ length: RANK_LIMIT }, (_, index) => emptyRankPlayer(index)))
const rank1Ref = ref<InstanceType<typeof Rank1> | null>(null) const rank1Ref = ref<InstanceType<typeof Rank1> | null>(null)
let isPlayingStartAnimation = false let isPlayingStartAnimation = false
const isSubmitWaiting = ref(false)
const submitWaitingSeconds = ref(5)
let submitWaitingTimer: ReturnType<typeof window.setInterval> | undefined
const confirmRefresh = (event: BeforeUnloadEvent) => { const confirmRefresh = (event: BeforeUnloadEvent) => {
// closeRoom(); // closeRoom();
...@@ -105,11 +108,37 @@ const seedRankPlayersFromLoading = () => { ...@@ -105,11 +108,37 @@ const seedRankPlayersFromLoading = () => {
console.log("[Game4] seedRankPlayersFromLoading 完成,rankPlayers:", rankPlayers.value.length, JSON.stringify(rankPlayers.value.slice(0, 3))); console.log("[Game4] seedRankPlayersFromLoading 完成,rankPlayers:", rankPlayers.value.length, JSON.stringify(rankPlayers.value.slice(0, 3)));
} }
function showSubmitWaiting() {
if (!isSubmitWaiting.value) { submitWaitingSeconds.value = 5
if (submitWaitingTimer) {
window.clearInterval(submitWaitingTimer)
}
submitWaitingTimer = window.setInterval(() => {
submitWaitingSeconds.value = Math.max(0, submitWaitingSeconds.value - 1)
if (submitWaitingSeconds.value <= 0 && submitWaitingTimer) {
window.clearInterval(submitWaitingTimer)
submitWaitingTimer = undefined
}
}, 1000)
}
isSubmitWaiting.value = true
}
function clearSubmitWaiting() {
isSubmitWaiting.value = false
submitWaitingSeconds.value = 5
if (submitWaitingTimer) {
window.clearInterval(submitWaitingTimer)
submitWaitingTimer = undefined
}
}
const resetRoundState = () => { const resetRoundState = () => {
playerCount.value = 0 playerCount.value = 0
loadingPlayers.value = Array.from({ length: PLAYER_LIMIT }, emptyLoadingPlayer) loadingPlayers.value = Array.from({ length: PLAYER_LIMIT }, emptyLoadingPlayer)
rankPlayers.value = Array.from({ length: RANK_LIMIT }, (_, index) => emptyRankPlayer(index)) rankPlayers.value = Array.from({ length: RANK_LIMIT }, (_, index) => emptyRankPlayer(index))
isPlayingStartAnimation = false isPlayingStartAnimation = false
clearSubmitWaiting()
} }
const gotoLoading = (reset = false) => { const gotoLoading = (reset = false) => {
...@@ -131,6 +160,7 @@ const gotoRank1WithIntro = async () => { ...@@ -131,6 +160,7 @@ const gotoRank1WithIntro = async () => {
await rank1Ref.value?.startIntro() await rank1Ref.value?.startIntro()
// 结果页已在 Rank1View 内部渲染(showResult + result-overlay),不需要切到 rank2 // 结果页已在 Rank1View 内部渲染(showResult + result-overlay),不需要切到 rank2
isPlayingStartAnimation = false isPlayingStartAnimation = false
clearSubmitWaiting()
} }
const horseBoosts = ref<Record<string, number>>({}); const horseBoosts = ref<Record<string, number>>({});
...@@ -181,7 +211,7 @@ const { startGame, createRoom, backRoom, closeRoom } = useAdminGameSocket({ ...@@ -181,7 +211,7 @@ const { startGame, createRoom, backRoom, closeRoom } = useAdminGameSocket({
}, },
onRoomState: (data: any) => { onRoomState: (data: any) => {
console.log("[Game4] onRoomState 触发,data:", JSON.stringify(data)); console.log("[Game4] onRoomState 触发,data:", JSON.stringify(data));
if (Array.isArray(data?.list)) { if ((data?.status === 0 || data?.status === 1) && Array.isArray(data?.list)) {
renderPlayers(data) renderPlayers(data)
} }
switch (data?.status) { switch (data?.status) {
...@@ -197,11 +227,18 @@ const { startGame, createRoom, backRoom, closeRoom } = useAdminGameSocket({ ...@@ -197,11 +227,18 @@ const { startGame, createRoom, backRoom, closeRoom } = useAdminGameSocket({
break break
case 2: case 2:
console.log("[Game4] status=2,准备进入 rank1,loadingPlayers:", loadingPlayers.value.length); console.log("[Game4] status=2,准备进入 rank1,loadingPlayers:", loadingPlayers.value.length);
seedRankPlayersFromLoading() console.log("[Game4] seedRankPlayersFromLoading 后,rankPlayers:", rankPlayers.value.length, JSON.stringify(rankPlayers.value.slice(0, 3)));
console.log("[Game4] seedRankPlayersFromLoading 后,rankPlayers:", rankPlayers.value.length, JSON.stringify(rankPlayers.value.slice(0, 3)));
screen.value = 'rank1' screen.value = 'rank1'
clearSubmitWaiting()
break break
case 3: case 3:
showSubmitWaiting()
break
case 4:
clearSubmitWaiting()
if (Array.isArray(data?.list)) {
updateRankPlayers(data.list)
}
screen.value = 'rank1' screen.value = 'rank1'
break break
} }
...@@ -492,4 +529,66 @@ watch(screen, (value) => { ...@@ -492,4 +529,66 @@ watch(screen, (value) => {
<Rank1 v-if="screen === 'rank1'" ref="rank1Ref" :rank-list="rankPlayers" :horse-boosts="horseBoosts" <Rank1 v-if="screen === 'rank1'" ref="rank1Ref" :rank-list="rankPlayers" :horse-boosts="horseBoosts"
@start="startGameWithMusic" @next="nextRound" @back="backHandler" /> @start="startGameWithMusic" @next="nextRound" @back="backHandler" />
<!-- <Rank2 v-else-if="screen === 'rank2'" :rank-list="rankPlayers" @next="nextRound" /> --> <!-- <Rank2 v-else-if="screen === 'rank2'" :rank-list="rankPlayers" @next="nextRound" /> -->
<div v-if="isSubmitWaiting" class="submit-waiting-overlay">
<div class="submit-waiting-title">正在等待玩家提交分数</div>
<div :key="submitWaitingSeconds" class="submit-waiting-count">{{ submitWaitingSeconds }}</div>
</div>
</template> </template>
<style scoped>
.submit-waiting-overlay {
position: fixed;
inset: 0;
z-index: 9999;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
background: radial-gradient(ellipse at center, rgba(0, 0, 0, 0.22) 0%, rgba(0, 0, 0, 0.08) 48%, transparent 72%);
color: #fff;
pointer-events: none;
}
.submit-waiting-title {
position: absolute;
bottom: 8vh;
left: 0;
width: 100%;
font-size: 56px;
font-weight: 700;
line-height: 1;
text-align: center;
text-shadow: 0 10px 24px rgba(0, 0, 0, 0.35);
}
.submit-waiting-count {
color: white;
font-size: 300pt;
font-weight: 800;
line-height: 1;
text-shadow: 0 18px 38px rgba(0, 0, 0, 0.25);
animation: submit-countdown-pop 1s ease both;
}
@keyframes submit-countdown-pop {
0% {
opacity: 0;
transform: scale(0.45);
}
18% {
opacity: 1;
transform: scale(1.14);
}
58% {
opacity: 1;
transform: scale(1);
}
100% {
opacity: 0;
transform: scale(0.84);
}
}
</style>
...@@ -53,6 +53,9 @@ const playerCount = ref(0) ...@@ -53,6 +53,9 @@ const playerCount = ref(0)
const rankPlayers = ref<Player[]>(Array.from({ length: RANK_LIMIT }, (_, index) => emptyRankPlayer(index))) const rankPlayers = ref<Player[]>(Array.from({ length: RANK_LIMIT }, (_, index) => emptyRankPlayer(index)))
const rank1Ref = ref<InstanceType<typeof Rank1> | null>(null) const rank1Ref = ref<InstanceType<typeof Rank1> | null>(null)
let isPlayingStartAnimation = false let isPlayingStartAnimation = false
const isSubmitWaiting = ref(false)
const submitWaitingSeconds = ref(5)
let submitWaitingTimer: ReturnType<typeof window.setInterval> | undefined
const confirmRefresh = (event: BeforeUnloadEvent) => { const confirmRefresh = (event: BeforeUnloadEvent) => {
event.preventDefault() event.preventDefault()
...@@ -112,11 +115,37 @@ const seedRankPlayersFromLoading = () => { ...@@ -112,11 +115,37 @@ const seedRankPlayersFromLoading = () => {
) )
} }
function showSubmitWaiting() {
if (!isSubmitWaiting.value) { submitWaitingSeconds.value = 5
if (submitWaitingTimer) {
window.clearInterval(submitWaitingTimer)
}
submitWaitingTimer = window.setInterval(() => {
submitWaitingSeconds.value = Math.max(0, submitWaitingSeconds.value - 1)
if (submitWaitingSeconds.value <= 0 && submitWaitingTimer) {
window.clearInterval(submitWaitingTimer)
submitWaitingTimer = undefined
}
}, 1000)
}
isSubmitWaiting.value = true
}
function clearSubmitWaiting() {
isSubmitWaiting.value = false
submitWaitingSeconds.value = 5
if (submitWaitingTimer) {
window.clearInterval(submitWaitingTimer)
submitWaitingTimer = undefined
}
}
const resetRoundState = () => { const resetRoundState = () => {
playerCount.value = 0 playerCount.value = 0
loadingPlayers.value = Array.from({ length: PLAYER_LIMIT }, emptyLoadingPlayer) loadingPlayers.value = Array.from({ length: PLAYER_LIMIT }, emptyLoadingPlayer)
rankPlayers.value = Array.from({ length: RANK_LIMIT }, (_, index) => emptyRankPlayer(index)) rankPlayers.value = Array.from({ length: RANK_LIMIT }, (_, index) => emptyRankPlayer(index))
isPlayingStartAnimation = false isPlayingStartAnimation = false
clearSubmitWaiting()
} }
const gotoLoading = (reset = false) => { const gotoLoading = (reset = false) => {
...@@ -137,15 +166,15 @@ const gotoRank1WithIntro = async () => { ...@@ -137,15 +166,15 @@ const gotoRank1WithIntro = async () => {
screen.value = 'rank1' screen.value = 'rank1'
await nextTick() await nextTick()
await rank1Ref.value?.startIntro() await rank1Ref.value?.startIntro()
screen.value = 'rank2' showSubmitWaiting()
isPlayingStartAnimation = false isPlayingStartAnimation = false
} }
function handleRoomState(data: any) { function handleRoomState(data: any) {
console.log('[WebSocket] 房间状态更新 - status:', data?.status, '数据:', JSON.stringify(data)) console.log('[WebSocket] 房间状态更新 - status:', data?.status, '数据:', JSON.stringify(data))
if (Array.isArray(data?.list)) { if ((data?.status === 0 || data?.status === 1) && Array.isArray(data?.list)) {
renderPlayers(data) renderPlayers(data)
} }
switch (data?.status) { switch (data?.status) {
case 0: case 0:
...@@ -156,12 +185,19 @@ function handleRoomState(data: any) { ...@@ -156,12 +185,19 @@ function handleRoomState(data: any) {
gotoLoading() gotoLoading()
break break
case 2: case 2:
seedRankPlayersFromLoading() screen.value = 'rank1'
screen.value = 'rank1' clearSubmitWaiting()
break break
case 3: case 3:
screen.value = 'rank2' showSubmitWaiting()
break break
case 4:
clearSubmitWaiting()
if (Array.isArray(data?.list)) {
updateRankPlayers(data.list)
}
screen.value = 'rank2'
break
} }
} }
...@@ -263,4 +299,66 @@ watch(screen, (value) => { ...@@ -263,4 +299,66 @@ watch(screen, (value) => {
@next="nextRoundWithMusic" /> @next="nextRoundWithMusic" />
<!-- <Rank1 v-else ref="rank1Ref" :rank-list="rankPlayers" @start="startGameWithMusic" --> <!-- <Rank1 v-else ref="rank1Ref" :rank-list="rankPlayers" @start="startGameWithMusic" -->
<Rank2 v-else :rank-list="rankPlayers" @next="nextRoundWithMusic" @back="backHandler" /> <Rank2 v-else :rank-list="rankPlayers" @next="nextRoundWithMusic" @back="backHandler" />
<div v-if="isSubmitWaiting" class="submit-waiting-overlay">
<div class="submit-waiting-title">正在等待玩家提交分数</div>
<div :key="submitWaitingSeconds" class="submit-waiting-count">{{ submitWaitingSeconds }}</div>
</div>
</template> </template>
<style scoped>
.submit-waiting-overlay {
position: fixed;
inset: 0;
z-index: 9999;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
background: radial-gradient(ellipse at center, rgba(0, 0, 0, 0.22) 0%, rgba(0, 0, 0, 0.08) 48%, transparent 72%);
color: #fff;
pointer-events: none;
}
.submit-waiting-title {
position: absolute;
bottom: 8vh;
left: 0;
width: 100%;
font-size: 56px;
font-weight: 700;
line-height: 1;
text-align: center;
text-shadow: 0 10px 24px rgba(0, 0, 0, 0.35);
}
.submit-waiting-count {
color: white;
font-size: 300pt;
font-weight: 800;
line-height: 1;
text-shadow: 0 18px 38px rgba(0, 0, 0, 0.25);
animation: submit-countdown-pop 1s ease both;
}
@keyframes submit-countdown-pop {
0% {
opacity: 0;
transform: scale(0.45);
}
18% {
opacity: 1;
transform: scale(1.14);
}
58% {
opacity: 1;
transform: scale(1);
}
100% {
opacity: 0;
transform: scale(0.84);
}
}
</style>
...@@ -38,6 +38,9 @@ const playerCount = ref(0) ...@@ -38,6 +38,9 @@ const playerCount = ref(0)
const rankPlayers = ref<Player[]>(Array.from({ length: RANK_LIMIT }, (_, index) => emptyRankPlayer(index))) const rankPlayers = ref<Player[]>(Array.from({ length: RANK_LIMIT }, (_, index) => emptyRankPlayer(index)))
const rank1Ref = ref<InstanceType<typeof Rank1> | null>(null) const rank1Ref = ref<InstanceType<typeof Rank1> | null>(null)
let isPlayingStartAnimation = false let isPlayingStartAnimation = false
const isSubmitWaiting = ref(false)
const submitWaitingSeconds = ref(5)
let submitWaitingTimer: ReturnType<typeof window.setInterval> | undefined
const confirmRefresh = (event: BeforeUnloadEvent) => { const confirmRefresh = (event: BeforeUnloadEvent) => {
event.preventDefault() event.preventDefault()
...@@ -86,11 +89,37 @@ const seedRankPlayersFromLoading = () => { ...@@ -86,11 +89,37 @@ const seedRankPlayersFromLoading = () => {
) )
} }
function showSubmitWaiting() {
if (!isSubmitWaiting.value) { submitWaitingSeconds.value = 5
if (submitWaitingTimer) {
window.clearInterval(submitWaitingTimer)
}
submitWaitingTimer = window.setInterval(() => {
submitWaitingSeconds.value = Math.max(0, submitWaitingSeconds.value - 1)
if (submitWaitingSeconds.value <= 0 && submitWaitingTimer) {
window.clearInterval(submitWaitingTimer)
submitWaitingTimer = undefined
}
}, 1000)
}
isSubmitWaiting.value = true
}
function clearSubmitWaiting() {
isSubmitWaiting.value = false
submitWaitingSeconds.value = 5
if (submitWaitingTimer) {
window.clearInterval(submitWaitingTimer)
submitWaitingTimer = undefined
}
}
const resetRoundState = () => { const resetRoundState = () => {
playerCount.value = 0 playerCount.value = 0
loadingPlayers.value = Array.from({ length: PLAYER_LIMIT }, emptyLoadingPlayer) loadingPlayers.value = Array.from({ length: PLAYER_LIMIT }, emptyLoadingPlayer)
rankPlayers.value = Array.from({ length: RANK_LIMIT }, (_, index) => emptyRankPlayer(index)) rankPlayers.value = Array.from({ length: RANK_LIMIT }, (_, index) => emptyRankPlayer(index))
isPlayingStartAnimation = false isPlayingStartAnimation = false
clearSubmitWaiting()
} }
const gotoLoading = (reset = false) => { const gotoLoading = (reset = false) => {
...@@ -109,14 +138,14 @@ const gotoRank1WithIntro = async () => { ...@@ -109,14 +138,14 @@ const gotoRank1WithIntro = async () => {
screen.value = 'rank1' screen.value = 'rank1'
await nextTick() await nextTick()
await rank1Ref.value?.startIntro() await rank1Ref.value?.startIntro()
screen.value = 'rank2' showSubmitWaiting()
isPlayingStartAnimation = false isPlayingStartAnimation = false
} }
function handleRoomState(data: any) { function handleRoomState(data: any) {
if (Array.isArray(data?.list)) { if ((data?.status === 0 || data?.status === 1) && Array.isArray(data?.list)) {
renderPlayers(data) renderPlayers(data)
} }
switch (data?.status) { switch (data?.status) {
case 0: case 0:
...@@ -127,12 +156,19 @@ function handleRoomState(data: any) { ...@@ -127,12 +156,19 @@ function handleRoomState(data: any) {
gotoLoading() gotoLoading()
break break
case 2: case 2:
seedRankPlayersFromLoading() screen.value = 'rank1'
screen.value = 'rank1' clearSubmitWaiting()
break break
case 3: case 3:
screen.value = 'rank2' showSubmitWaiting()
break break
case 4:
clearSubmitWaiting()
if (Array.isArray(data?.list)) {
updateRankPlayers(data.list)
}
screen.value = 'rank2'
break
} }
} }
const gameId='game6' const gameId='game6'
...@@ -209,4 +245,66 @@ watch(screen, (value) => { ...@@ -209,4 +245,66 @@ watch(screen, (value) => {
@start="startGameWithMusic" @back="backHandler" /> @start="startGameWithMusic" @back="backHandler" />
<Rank1 v-else-if="screen === 'rank1'" ref="rank1Ref" :rank-list="rankPlayers" @start="startGameWithMusic" /> <Rank1 v-else-if="screen === 'rank1'" ref="rank1Ref" :rank-list="rankPlayers" @start="startGameWithMusic" />
<Rank2 v-else :rank-list="rankPlayers" @next="nextRoundWithMusic" @back="backHandler" /> <Rank2 v-else :rank-list="rankPlayers" @next="nextRoundWithMusic" @back="backHandler" />
<div v-if="isSubmitWaiting" class="submit-waiting-overlay">
<div class="submit-waiting-title">正在等待玩家提交分数</div>
<div :key="submitWaitingSeconds" class="submit-waiting-count">{{ submitWaitingSeconds }}</div>
</div>
</template> </template>
<style scoped>
.submit-waiting-overlay {
position: fixed;
inset: 0;
z-index: 9999;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
background: radial-gradient(ellipse at center, rgba(0, 0, 0, 0.22) 0%, rgba(0, 0, 0, 0.08) 48%, transparent 72%);
color: #fff;
pointer-events: none;
}
.submit-waiting-title {
position: absolute;
bottom: 8vh;
left: 0;
width: 100%;
font-size: 56px;
font-weight: 700;
line-height: 1;
text-align: center;
text-shadow: 0 10px 24px rgba(0, 0, 0, 0.35);
}
.submit-waiting-count {
color: white;
font-size: 300pt;
font-weight: 800;
line-height: 1;
text-shadow: 0 18px 38px rgba(0, 0, 0, 0.25);
animation: submit-countdown-pop 1s ease both;
}
@keyframes submit-countdown-pop {
0% {
opacity: 0;
transform: scale(0.45);
}
18% {
opacity: 1;
transform: scale(1.14);
}
58% {
opacity: 1;
transform: scale(1);
}
100% {
opacity: 0;
transform: scale(0.84);
}
}
</style>
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment