Commit a8efe97a authored by 陈冲's avatar 陈冲

feat: 功能修复

parent c1dbe1ba
......@@ -14,8 +14,8 @@ use std::sync::atomic::{AtomicU64, Ordering};
use std::time::{Duration, Instant};
lazy_static::lazy_static! {
/// 可以有多个管理员,但不要用同一个账户登录,否则后面的没效果
static ref CLIENT_ADMIN_MAP: DashMap<String, SocketRef> = DashMap::new();
/// 当前唯一在线管理员:(token, socket)。
static ref CLIENT_ADMIN_SESSION: std::sync::Mutex<Option<(String, SocketRef)>> = std::sync::Mutex::new(None);
static ref CLIENT_PLAYER_MAP: DashMap<String, (i32,SocketRef)> = DashMap::new(); // 1: 正在游戏中
static ref ROOM_STATE: std::sync::Mutex<RoomState> = std::sync::Mutex::new(RoomState::default());
}
......@@ -234,7 +234,10 @@ pub fn get_player_count() -> usize {
}
// 获取管理员数量
pub fn get_admin_count() -> usize {
CLIENT_ADMIN_MAP.len()
CLIENT_ADMIN_SESSION
.lock()
.map(|session| usize::from(session.is_some()))
.unwrap_or(0)
}
pub fn emit_to_user(userid: &str, event: &str, payload: &Value) {
......@@ -295,11 +298,46 @@ fn broadcast_msgpack_all_player(event: &str, payload: &Value) {
}
/// 发送给所有管理员
fn broadcast_msgpack_all_admin(event: &str, payload: &Value) {
if let Some(payload) = encode_msgpack(payload) {
for socket in CLIENT_ADMIN_MAP.iter() {
_ = socket.value().emit(event, &payload);
let Some(payload) = encode_msgpack(payload) else {
return;
};
let socket = CLIENT_ADMIN_SESSION
.lock()
.ok()
.and_then(|session| session.as_ref().map(|(_, socket)| socket.clone()));
if let Some(socket) = socket {
_ = socket.emit(event, &payload);
}
}
fn replace_current_admin(userid: String, socket: SocketRef) -> Result<Option<SocketRef>, ()> {
CLIENT_ADMIN_SESSION
.lock()
.map(|mut session| {
let old_socket = session.take().map(|(_, socket)| socket);
*session = Some((userid, socket));
old_socket
})
.map_err(|_| ())
}
fn clear_current_admin_if_match(userid: &str, socket: &SocketRef) -> bool {
CLIENT_ADMIN_SESSION
.lock()
.map(|mut session| {
let should_clear = session
.as_ref()
.map(|(current_userid, current_socket)| {
current_userid == userid && current_socket.id == socket.id
})
.unwrap_or(false);
if should_clear {
*session = None;
}
should_clear
})
.unwrap_or(false)
}
/// 发送给所有管理员和玩家
fn broadcast_msgpack_all(event: &str, payload: &Value) {
......@@ -503,6 +541,8 @@ async fn handle_room_command(
emit_msgpack(&socket, evt, &ok_resp(Some(json!(index))));
if cmd == "room_back" {
broadcast_msgpack_all_player(evt, &ok_resp(Some(json!(index))));
} else if cmd == "room_create" {
broadcast_msgpack_all_player(evt, &ok_resp(Some(json!(index))));
}
}
}
......@@ -534,6 +574,10 @@ async fn handle_room_command(
.to_string();
if let Ok(mut state) = ROOM_STATE.lock() {
// dbg!(
// &state.current_game_id.clone(),
// &state.status.clone().as_str()
// );
let Some(current_game_id) = state.current_game_id.clone() else {
drop(state);
return;
......@@ -773,17 +817,30 @@ async fn handle_room_command(
if !is_admin {
return;
}
let mut status = "".to_string();
let mut title = "".to_string();
if let Ok(mut state) = ROOM_STATE.lock() {
status = state.current_game_id.clone().unwrap_or_default();
title = get_game_tilte(&status);
state.reset();
drop(state);
}
broadcast_msgpack_all_player(evt, &json!({}));
broadcast_msgpack_all_player(
evt,
&err_resp(
&format!("管理员关闭了 {} 该房间", title),
Some(json!(status)),
),
);
broadcast_room_rank(Vec::new());
emit_msgpack(&socket, evt, &ok_resp(None));
}
// 获取房间状态
"room_state" => {
if let Ok(state) = ROOM_STATE.lock() {
let gameId = state.current_game_id.clone().unwrap_or_default();
let count = state.players.len();
let ranking = state.current_ranking_payload();
let status = state.status as i32;
......@@ -792,7 +849,9 @@ async fn handle_room_command(
emit_msgpack(
&socket,
evt,
&ok_resp(Some(json!({"status":status,"list":ranking,"count":count}))),
&ok_resp(Some(
json!({"status":status,"list":ranking,"count":count,"gameId":gameId,}),
)),
);
}
}
......@@ -858,24 +917,25 @@ pub async fn on_connect(_io: SocketIo, socket: SocketRef, Data(data): Data<Value
return;
}
}
// 原子检查加写入,避免并发下同一个 userid 同时登录。
if is_admin {
match CLIENT_ADMIN_MAP.entry(userid.clone()) {
Entry::Occupied(_) => {
match replace_current_admin(userid.clone(), socket.clone()) {
Ok(Some(old_socket)) => {
emit_msgpack(
&socket,
"login_result",
&err_resp("当前管理员账号已登录,不能重复登录", Some(json!(1))),
&old_socket,
"new_admin_result",
&err_resp("有其他管理员登录,您已下线", None),
);
_ = old_socket.disconnect();
}
Ok(None) => {}
Err(_) => {
_ = socket.disconnect();
return;
}
Entry::Vacant(entry) => {
entry.insert(socket.clone());
}
}
} else {
if CLIENT_ADMIN_MAP.is_empty() {
if get_admin_count() == 0 {
emit_msgpack(
&socket,
"connect_result",
......@@ -896,7 +956,6 @@ pub async fn on_connect(_io: SocketIo, socket: SocketRef, Data(data): Data<Value
}
}
}
// dbg!(&is_admin,&userid);
// 鉴权通过后,在这里挂玩家消息事件处理器。
let message_userid = userid.clone();
socket.on("message", move |socket: SocketRef, data: Data<Bytes>| {
......@@ -921,9 +980,11 @@ pub async fn on_connect(_io: SocketIo, socket: SocketRef, Data(data): Data<Value
move |socket: SocketRef, _reason: DisconnectReason| async move {
if !is_create_user {
if is_admin {
CLIENT_ADMIN_MAP.remove(&userid);
if !clear_current_admin_if_match(&userid, &socket) {
return;
}
// 前端页面刷新时房间状态丢失体验会不好
if CLIENT_ADMIN_MAP.is_empty() {
if get_admin_count() == 0 {
if let Ok(mut state) = ROOM_STATE.lock() {
state.reset(); // 如果管理员都退出了,就把房间状态重置。
drop(state);
......@@ -1013,12 +1074,25 @@ pub async fn login(req: &mut Request, res: &mut Response) {
res.render(Json(err_resp("用户名或密码不能为空", None)));
return;
}
//当前管理员已登录,不能重复登录
if (CLIENT_ADMIN_MAP.contains_key(&manager.token)) {
res.render(Json(err_resp("当前管理员已登录,不能重复登录", None)));
if get_admin_count() > 0 {
if let Ok(state) = ROOM_STATE.lock() {
let game_id = state.current_game_id.clone().unwrap_or("".to_string());
if matches!(
state.status,
RoomStatus::Start | RoomStatus::Running | RoomStatus::Submit
) {
drop(state);
res.render(Json(err_resp(
&format!(
"当前管理员已开启游戏房间 - {},请稍后再登录",
get_game_tilte(&game_id)
),
None,
)));
return;
}
}
}
res.render(Json(ok_resp(Some(json!({
"passphrase": passphrase,
"token": manager.token,
......
......@@ -2,7 +2,7 @@
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, minimum-scale=1, user-scalable=no">
<title>小游戏-H5</title>
<style>body{background: #FA6047;}</style>
<script>localStorage.setItem('domain', '');</script>
......
......@@ -21,6 +21,7 @@ html,
body {
width: 100%;
height: 100%;
touch-action: manipulation;
}
body {
......
......@@ -67,8 +67,8 @@ export function initSocket(args: SocketInitArgs): Socket | null {
});
socket.onAny((evt, payload) => {
const data = decode(payload);
if($is_run_local()){
console.log(evt, data);
if ($is_run_local()) {
console.log("recv<============", evt, data);
}
messageHandlers.forEach((handler) => handler(evt, data));
});
......@@ -94,7 +94,9 @@ export function sendSocketMessage(cmd: string, data: any = {}): boolean {
}
return false;
}
if ($is_run_local()) {
console.log('================>send', cmd, data);
}
socket.send(encode({ cmd, data }));
return true;
}
......
......@@ -16,7 +16,7 @@ const props = withDefaults(defineProps<{
height: 1624,
mode: 'contain',
backgroundMode: 'cover',
showGameRule: false,
showGameRule: true, //玩家要有操作动作才能播放音乐,这里会默认显示游戏规则,让玩家有关闭操作
showGameRank: false
})
......
......@@ -91,6 +91,12 @@ export function useGameSocket(options: GameSocketOptions) {
resubmitcount++;
options.onRescoreSubmitted?.(resubmitcount);
}, 200);
} else if (evt == 'room_close_result') {
userJoinStatus.value = false
if (data == options.gameId) {
options.onRoomClosed?.(data)
}
return;
}
return;
}
......@@ -101,11 +107,15 @@ export function useGameSocket(options: GameSocketOptions) {
}
switch (evt) {
case 'connect_result':{
case 'connect_result': {
debugger
break;
}
case 'room_create_result': {
if (`game${data}` == options.gameId) {
location.reload();
return;
}
const nextPath = getGameHomePath(data)
if (nextPath && router.currentRoute.value.path !== nextPath) {
......@@ -153,15 +163,13 @@ export function useGameSocket(options: GameSocketOptions) {
options.onScoreSubmitted?.(true, data)
break
}
case 'room_close_result': {
userJoinStatus.value = false
options.onRoomClosed?.(data)
break
}
case 'room_back_result': {
options.onRoomBack?.(data);
break;
}
default: {
debugger
}
}
options.onEvent?.(evt, payload)
......
<script setup lang="ts">
import { onBeforeUnmount, onMounted, ref } from 'vue'
import { onBeforeUnmount, onMounted, ref, watch } from 'vue'
import { cssAssetUrl } from '@/commons/assets.ts'
import MobileStage from '@/components/MobileStage.vue'
import { useGameSocket, joinSharedRoom, sendGameMessage, userJoinStatus } from '@/composables/useGameSocket'
......@@ -42,7 +42,7 @@ const isGuPlaying = ref(false)
const token = ref(wechat?.token ?? '')
const nickname = ref(wechat?.nickname ?? '')
const avatar = ref(wechat?.avatar ?? '')
const showGameRule = ref(false)
const showGameRule = ref(true)
const mobileStageRef = ref<InstanceType<typeof MobileStage> | null>(null)
const _offSocketMessage = ref<(() => void) | undefined>()
......@@ -261,9 +261,9 @@ const showGameRuleHandler = () => {
4.禁止外挂,作弊,违规账号取消成绩与领奖资格;<br /><br />
祝您游戏愉快!
</div>
</div>
<div class="rule-close" @click="showGameRuleHandler"></div>
</div>
</div>
</template>
<LoadingView v-if="currentView === 'loading'" :image-urls="imageUrls" :user-join-status="userJoinStatus"
@touchGameRule="showGameRuleHandler" />
......@@ -279,12 +279,9 @@ const showGameRuleHandler = () => {
<style scoped>
.rule-container {
.rule-txt-container {
position: absolute;
background: v-bind('imageUrls.rule') center / cover no-repeat;
width: 692px;
height: 797px;
left: -157px;
top: -75px;
transform: scale(0.5);
.rule-title {
......@@ -307,11 +304,11 @@ const showGameRuleHandler = () => {
.rule-close {
width: 57px;
height: 57px;
margin: 0 auto;
margin-top: 450px;
background: v-bind('imageUrls.close') center/cover;
position: relative;
transform: scale(0.8);
background: v-bind('imageUrls.close') center / cover no-repeat;
transform: translateX(-50%) scale(1.4);
position: absolute;
bottom: -75px;
left: 50%;
}
}
</style>
......@@ -23,7 +23,7 @@ onMounted(() => {
<div class="img-avatar" :style="`background: url(${avatar});border-radius:66px;width:132px;height:132px;`">
</div>
<div class="txt-nickname">{{ nickname }}</div>
<div class="txt-loading">您已成功加入游戏等待主持人开始</div>
<div class="txt-loading" v-if="userJoinStatus">您已成功加入游戏等待主持人开始</div>
<div class="txt-loading2">游戏即将开始 敬请期待</div>
<div class="bg-bottom"></div>
<div class="loading-gu"></div>
......
......@@ -8,6 +8,7 @@ import PlayingView from './views/PlayingView.vue'
import ScoreView from './views/ScoreView.vue'
import { $getWechat } from '@/commons/utils.ts'
const gameId = 'game4';
type GameView = 'loading' | 'playing' | 'score'
const imageUrls = {
......@@ -30,7 +31,9 @@ const imageUrls = {
scoreBg: cssAssetUrl('game1/score-bg.png'),
back: cssAssetUrl('game4/back.svg'),
replay: cssAssetUrl('game4/replay.svg'),
close: cssAssetUrl('game4/h5-game4-close.svg'),
close: cssAssetUrl('game1/close.png'),
rule: cssAssetUrl('game1/rule.png')
}
const wechat = $getWechat()
......@@ -52,7 +55,7 @@ const currentYaoyiyao = ref(imageUrls.yaoyiyao)
const token = ref(wechat?.token ?? '')
const nickname = ref(wechat?.nickname ?? '')
const avatar = ref(wechat?.avatar ?? '')
const showGameRule = ref(false)
const showGameRule = ref(true)
const mobileStageRef = ref<InstanceType<typeof MobileStage> | null>(null)
const _offSocketMessage = ref<(() => void) | undefined>()
......@@ -120,6 +123,7 @@ function showLoadingView() {
stopGameCountdown()
currentView.value = 'loading'
setDivDescVisible(false)
alert('管理员关闭了该房间')
}
function resetToLoadingView() {
......@@ -155,13 +159,13 @@ function replayGame() {
function backToWaiting() {
resetToLoadingView()
joinSharedRoom(nickname.value, token.value, avatar.value)
joinSharedRoom(nickname.value, token.value, avatar.value, gameId)
}
function handleRoomBack() {
resetToLoadingView()
window.setTimeout(() => {
joinSharedRoom(nickname.value, token.value, avatar.value)
joinSharedRoom(nickname.value, token.value, avatar.value, gameId)
}, 200)
}
......@@ -175,14 +179,15 @@ const showGameRuleHandler = () => {
}
if (wechat) {
const { offSocketMessage } = useGameSocket({
gameId: 'game4',
gameId,
auth: {
userid: wechat.token,
},
onConnect: () => {
window.setTimeout(() => {
joinSharedRoom(nickname.value, token.value, avatar.value)
joinSharedRoom(nickname.value, token.value, avatar.value, gameId)
}, 200)
},
onGameStart: async () => {
......@@ -224,8 +229,7 @@ onBeforeUnmount(() => {
</script>
<template>
<MobileStage v-if="token" :showGameRule="showGameRule" ref="mobileStageRef"
:background="stageBackground">
<MobileStage v-if="token" :showGameRule="showGameRule" ref="mobileStageRef" :background="stageBackground">
<template #gameRule>
<div class="rule-container">
<div class="rule-txt-container">
......@@ -239,9 +243,7 @@ onBeforeUnmount(() => {
5. 禁止外挂作弊,违规账号取消成绩与领奖资格;<br /><br />
祝您游戏愉快!
</div>
</div>
<div class="rule-close">
<div @click="showGameRuleHandler"></div>
<div class="rule-close" @click="showGameRuleHandler"></div>
</div>
</div>
</template>
......@@ -261,42 +263,36 @@ onBeforeUnmount(() => {
<style scoped>
.rule-container {
.rule-txt-container {
width: 62vw;
margin: 0 40px;
border-radius: 5px;
background: #FFF9E4;
padding-top: 20px;
padding-bottom: 10px;
background: v-bind('imageUrls.rule') center / cover no-repeat;
width: 692px;
height: 797px;
transform: scale(0.5);
.rule-title {
text-align: center;
color: #850000;
color: white;
font-weight: bold;
font-size: 15pt;
font-size: 22pt;
margin-top: 10px;
letter-spacing: 4px;
}
.rule-txt {
font-size: 12pt;
padding: 10px;
font-size: 26pt;
padding: 40px;
margin-top: 10px;
color: #AA0000;
}
}
.rule-close {
width: 32px;
height: 130px;
margin: 0 auto;
background: v-bind('imageUrls.close') center/cover;
position: relative;
>div {
width: 57px;
height: 57px;
background: v-bind('imageUrls.close') center / cover no-repeat;
transform: translateX(-50%) scale(1.4);
position: absolute;
bottom: 0;
width: 32px;
height: 32px;
border-radius: 16px;
}
bottom: -75px;
left: 50%;
}
}
</style>
......@@ -9,6 +9,7 @@ import ScoreView from './views/ScoreView.vue'
import { $getWechat } from '@/commons/utils.ts'
import { playGame1Music } from '@/commons/music'
const gameId = 'game5';
type GameView = 'loading' | 'playing' | 'score'
type RankPlayer = {
rank: number
......@@ -63,7 +64,7 @@ const isGuPlaying = ref(false)
const token = ref(wechat?.token ?? '')
const nickname = ref(wechat?.nickname ?? '')
const avatar = ref(wechat?.avatar ?? '')
const showGameRule = ref(false)
const showGameRule = ref(true)
const showGameRank = ref(false)
const rankList = ref<RankPlayer[]>([])
const displayRankList = computed<RankPlayer[]>(() => {
......@@ -149,6 +150,7 @@ function showLoadingView() {
stopGameCountdown()
currentView.value = 'loading'
setDivDescVisible(false)
alert('管理员关闭了该房间')
}
function resetToLoadingView() {
......@@ -191,7 +193,7 @@ function showGameOverRank() {
function handleRoomBack() {
resetToLoadingView()
window.setTimeout(() => {
joinSharedRoom(nickname.value, token.value, avatar.value)
joinSharedRoom(nickname.value, token.value, avatar.value, gameId)
}, 200)
}
......@@ -210,7 +212,7 @@ if (wechat) {
},
onConnect: () => {
window.setTimeout(() => {
const joined = joinSharedRoom(nickname.value, token.value, avatar.value)
const joined = joinSharedRoom(nickname.value, token.value, avatar.value, gameId)
}, 200)
},
onGameStart: async () => {
......@@ -325,9 +327,9 @@ const rankCloseHandler = () => {
4.禁止外挂作弊,违规账号取消成绩与领奖资格;<br /><br />
祝您游戏愉快!
</div>
</div>
<div class="rule-close" @click="showGameRuleHandler"></div>
</div>
</div>
</template>
<LoadingView v-if="currentView === 'loading'" :image-urls="imageUrls" :user-join-status="userJoinStatus"
@touchGameRule="showGameRuleHandler" />
......@@ -344,12 +346,9 @@ const rankCloseHandler = () => {
<style scoped>
.rule-container {
.rule-txt-container {
position: absolute;
background: v-bind('imageUrls.rule') center / cover no-repeat;
width: 692px;
height: 797px;
left: -157px;
top: -75px;
transform: scale(0.5);
.rule-title {
......@@ -372,11 +371,11 @@ const rankCloseHandler = () => {
.rule-close {
width: 57px;
height: 57px;
margin: 0 auto;
margin-top: 450px;
background: v-bind('imageUrls.close') center/cover;
position: relative;
transform: scale(0.8);
background: v-bind('imageUrls.close') center / cover no-repeat;
transform: translateX(-50%) scale(1.4);
position: absolute;
bottom: -75px;
left: 50%;
}
}
......
......@@ -145,6 +145,7 @@ onMounted(() => {
height: 126px;
color: white;
position: absolute;
z-index: -1;
top:786px;
left: 50%;
transform: translate(-50%);
......
......@@ -57,13 +57,20 @@ const displayRankList = computed<RankPlayer[]>(() => {
}
})
})
const rankScore = computed<number | string>(() => {
const currentPlayer = rankList.value
.slice(0, 10)
.find(item => item.rank === rank.value)
return currentPlayer?.score ?? score.value
})
// const guFrames: [string, string, string] = [imageUrls.gu01, imageUrls.gu02, imageUrls.gu03]
// const currentGu = ref(imageUrls.gu01)
const isGuPlaying = ref(false)
const token = ref(wechat?.token ?? '')
const nickname = ref(wechat?.nickname ?? '')
const avatar = ref(wechat?.avatar ?? '')
const showGameRule = ref(false)
const showGameRule = ref(true)
const showGameRank = ref(false)
const mobileStageRef = ref<InstanceType<typeof MobileStage> | null>(null)
......@@ -297,9 +304,9 @@ const rankCloseHandler = () => {
4. 禁止外挂作弊,违规账号取消成绩与领奖资格;<br /><br />
祝您游戏愉快!
</div>
</div>
<div class="rule-close" @click="showGameRuleHandler"></div>
</div>
</div>
</template>
<template #gameRank>
<div class="ranklist-container">
......@@ -330,7 +337,7 @@ const rankCloseHandler = () => {
<div class="avatar" :style="{ backgroundImage: `url(${avatar})` }"></div>
<div>{{ nickname }}</div>
</div>
<div class="col-3">{{ score }}</div>
<div class="col-3">{{ rankScore }}</div>
</div>
</div>
</div>
......@@ -348,12 +355,9 @@ const rankCloseHandler = () => {
<style scoped>
.rule-container {
.rule-txt-container {
position: absolute;
background: v-bind('imageUrls.rule') center / cover no-repeat;
width: 692px;
height: 797px;
left: -157px;
top: -75px;
transform: scale(0.5);
.rule-title {
......@@ -376,11 +380,11 @@ const rankCloseHandler = () => {
.rule-close {
width: 57px;
height: 57px;
margin: 0 auto;
margin-top: 450px;
background: v-bind('imageUrls.close') center/cover;
position: relative;
transform: scale(0.8);
background: v-bind('imageUrls.close') center / cover no-repeat;
transform: translateX(-50%) scale(1.4);
position: absolute;
bottom: -75px;
left: 50%;
}
}
......
......@@ -77,7 +77,7 @@ onMounted(() => {
.game-desc {
position: absolute;
z-index: 2;
z-index: 9999999999;
top: 50%;
left: var(--stage-viewport-left, 0);
display: flex;
......
......@@ -26,7 +26,7 @@ function copyImageAssets() {
}
export default defineConfig(({ command }) => ({
base: command === 'build' ? '/cc/h5/' : '/',
base: command === 'build' ? '/h5/' : '/',
define: {
__APP_DOMAIN__: JSON.stringify(command === 'build' ? buildDomain : ''),
__DEBUG__: JSON.stringify(command === 'serve'),
......
import game1MusicUrl from '@/assets/images/game1.mp3'
import game4MusicUrl from '@/assets/images/game1.mp3' // 需要改成正式的路径
import game5MusicUrl from '@/assets/images/game1.mp3' // 需要改成正式的路径
import game6MusicUrl from '@/assets/images/game6.mp3'
import go321 from '@/assets/images/321go.mp3'
import applause from '@/assets/images/applause.mp3'
let backgroundMusic: HTMLAudioElement | null = null
let countdownTemplate: HTMLAudioElement | null = null
let pendingCountdownRetry: (() => void) | null = null
const countdownAudios = new Set<HTMLAudioElement>()
function getBackgroundMusic(music: string) {
export function stopAllMusic() {
stopApplauseMusic();
stopGame1Music();
// stopGame2Music();
// stopGame3Music();
stopGame4Music();
stopGame5Music();
stopGame6Music();
backgroundMusic = null;
}
function getBackgroundMusic(music: string, force: boolean = true) {
if (force) { //
if (backgroundMusic) {
stopApplauseMusic();
stopGame1Music();
// stopGame2Music();
// stopGame3Music();
stopGame4Music();
stopGame5Music();
stopGame6Music();
backgroundMusic = null;
}
}
if (!backgroundMusic) {
backgroundMusic = new Audio(music)
backgroundMusic.loop = true
......@@ -19,6 +47,26 @@ function getBackgroundMusic(music: string) {
return backgroundMusic
}
export async function playApplauseMusic() {
const audio = getBackgroundMusic(applause)
try {
await audio.play()
window.removeEventListener('pointerdown', playApplauseMusic)
window.removeEventListener('keydown', playApplauseMusic)
} catch {
window.addEventListener('pointerdown', playApplauseMusic, { once: false })
window.addEventListener('keydown', playApplauseMusic, { once: false })
}
}
export function stopApplauseMusic() {
window.removeEventListener('pointerdown', playApplauseMusic)
window.removeEventListener('keydown', playApplauseMusic)
backgroundMusic?.pause()
}
export async function playGame1Music() {
const audio = getBackgroundMusic(game1MusicUrl)
......@@ -38,6 +86,45 @@ export function stopGame1Music() {
backgroundMusic?.pause()
}
export async function playGame4Music() {
const audio = getBackgroundMusic(game4MusicUrl)
try {
await audio.play()
window.removeEventListener('pointerdown', playGame4Music)
window.removeEventListener('keydown', playGame4Music)
} catch {
window.addEventListener('pointerdown', playGame4Music, { once: true })
window.addEventListener('keydown', playGame4Music, { once: true })
}
}
export function stopGame4Music() {
window.removeEventListener('pointerdown', playGame4Music)
window.removeEventListener('keydown', playGame4Music)
backgroundMusic?.pause()
}
export async function playGame5Music() {
const audio = getBackgroundMusic(game4MusicUrl)
try {
await audio.play()
window.removeEventListener('pointerdown', playGame5Music)
window.removeEventListener('keydown', playGame5Music)
} catch {
window.addEventListener('pointerdown', playGame5Music, { once: true })
window.addEventListener('keydown', playGame5Music, { once: true })
}
}
export function stopGame5Music() {
window.removeEventListener('pointerdown', playGame5Music)
window.removeEventListener('keydown', playGame5Music)
backgroundMusic?.pause()
}
export async function playGame6Music() {
const audio = getBackgroundMusic(game6MusicUrl)
......
......@@ -57,7 +57,7 @@ export function initSocket(args: SocketInitArgs): Socket | null {
socket.onAny((evt, payload) => {
const data = decode(payload);
if ($is_run_local()) {
console.log(evt, data);
console.log("recv<============", evt, data);
}
messageHandlers.forEach((handler) => handler(evt, data));
});
......@@ -78,7 +78,7 @@ export function sendSocketMessage(cmd: string, data: any = {}): boolean {
return false;
}
if ($is_run_local()) {
console.log('===================>send', cmd, data);
console.log('================>send', cmd, data);
}
socket.send(encode({ cmd, data }));
return true;
......
import CryptoJS from 'crypto-js'
import { onBeforeUnmount, onMounted } from 'vue'
import { $read_socket_storge } from '@/commons/utils'
import { $read_socket_storge, $remove_socket_storage } from '@/commons/utils'
import { initSocket, onSocketMessage, sendSocketMessage } from '@/commons/ws'
import router from '@/router'
type SocketPayload = {
msg?: string
......@@ -10,13 +11,14 @@ type SocketPayload = {
}
type AdminGameSocketOptions = {
gameId: string
gameId?: string,
loginRedirectPath?: string
onRoomState?: (data: any) => void
onRoomJoin?: (data: any) => void
onGameStart?: (data: any) => void | Promise<void>
onRoomRank?: (data: any) => void
onRelogin?: () => void
onAny?: (evt: string, data: any) => void
}
export function useAdminGameSocket(options: AdminGameSocketOptions) {
......@@ -41,13 +43,13 @@ export function useAdminGameSocket(options: AdminGameSocketOptions) {
return sendSocketMessage(cmd, data)
}
function closeRoom(){
function closeRoom() {
return sendSocketMessage('room_close', {})
}
onMounted(() => {
const { passphrase, token, socket_path, client_ns, domain } = $read_socket_storge()
initSocket({
let socket = initSocket({
socket_path: `${domain}${socket_path}`,
client_ns,
auth: {
......@@ -67,33 +69,51 @@ export function useAdminGameSocket(options: AdminGameSocketOptions) {
// }
if (evt == 'login_result') {
alert(msg)
options.onRelogin?.()
// options.onRelogin?.()
socket?.close();
$remove_socket_storage();
await router.replace(`/login`);
location.href = location.href;
return;
}
else if (evt == 'new_admin_result') {
alert(msg);
socket?.close();
$remove_socket_storage();
await router.replace(`/login`);
location.href = location.href;
return;
}
alert(msg)
return
}
switch (evt) {
case 'room_state_result': {
if (data.gameId) {
//如果这两个值不一致说明浏览器有直接更新游戏房间号
if (data.gameId != options.gameId) {
location.reload();
return;
}
}
options.onRoomState?.(data)
break
return
}
case 'room_join_result': {
options.onRoomJoin?.(data)
break
return
}
case 'game_start_result': {
if (data === options.gameId) {
await options.onGameStart?.(data)
}
break
return
}
case 'room_rank_result': {
options.onRoomRank?.(data)
break
return
}
}
options?.onAny?.(evt, payload);
})
})
......
<script setup lang="ts">
import { cssAssetUrl } from '@/commons/assets';
import { $save } from '@/commons/utils.ts';
import { onMounted, ref } from 'vue'
import { useRouter } from 'vue-router'
const emit = defineEmits<{
loginSuccess: []
}>()
const router = useRouter()
const username = ref('')
const password = ref('')
const imageUrls = {
bg1: cssAssetUrl('main-bg1.png'),
logo: cssAssetUrl('game1/logo.png'),
title1: cssAssetUrl('main-title.png'),
}
async function login() {
let domain = localStorage.getItem('domain') || '';
const res = await fetch(`${domain}/manager/login`, {
......@@ -22,11 +25,10 @@ async function login() {
})
const data = await res.json()
if (!res.ok || data.state !== 1) {
alert(data.msg || 'Login failed')
throw new Error(data.msg || 'Login failed')
}
// if (!res.ok || data.state !== 1) {
// alert(data.msg || 'Login failed')
// throw new Error(data.msg || 'Login failed')
// }
return data
}
......@@ -40,7 +42,8 @@ const loginHandler = async () => {
for (let i in data) {
$save(i, data[i]);
}
emit('loginSuccess');
alert('登录成功')
await router.replace(`/main`);
}
onMounted(() => {
......@@ -48,6 +51,9 @@ onMounted(() => {
</script>
<template>
<div class="bg">
<div class="img-logo"></div>
<div class="img-title1"></div>
<div class="login-page">
<form class="login-panel" @submit.prevent="loginHandler">
<div class="panel-header">
......@@ -67,9 +73,40 @@ onMounted(() => {
<button class="login-button" type="submit">登录</button>
</form>
</div>
</div>
</template>
<style scoped>
.bg {
width: 1920px;
height: 1080px;
background: v-bind('imageUrls.bg1') center / cover no-repeat;
display: flex;
flex-direction: column;
overflow: hidden;
position: relative;
.img-logo {
background: v-bind('imageUrls.logo');
background-size: cover;
width: 428px;
height: 77px;
position: absolute;
left: 37px;
top: 82px;
}
.img-title1 {
background: v-bind('imageUrls.title1') center / cover no-repeat;
width: 1024px;
height: 143px;
margin: 0 auto;
position: relative;
top: 50px;
}
}
.login-page {
position: absolute;
z-index: 1;
......@@ -79,7 +116,6 @@ onMounted(() => {
align-items: center;
justify-content: center;
overflow: hidden;
background: rgba(0, 0, 0, 0.8);
}
.login-panel {
......
<script setup lang="ts">
import { cssAssetUrl } from '@/commons/assets.ts';
import { $read } from '@/commons/utils';
import { useAdminGameSocket } from '@/composables/useAdminGameSocket';
import router from '@/router';
import { ref } from 'vue';
import Login from '../components/Login.vue';
import { onMounted, onUnmounted, ref } from 'vue';
const imageUrls = {
bg1: cssAssetUrl('main-bg1.png'),
......@@ -45,10 +45,27 @@ const loginSuccessHandler = () => {
show_login.value = false;
gameHandler(item_index.value);
}
const confirmRefresh = (event: BeforeUnloadEvent) => {
event.preventDefault()
event.returnValue = ''
}
useAdminGameSocket({})
onMounted(() => {
window.addEventListener('beforeunload', confirmRefresh)
})
onUnmounted(() => {
window.removeEventListener('beforeunload', confirmRefresh)
})
</script>
<template>
<Login v-if="show_login" @loginSuccess="loginSuccessHandler" />
<!-- <Login v-if="show_login" @loginSuccess="loginSuccessHandler" /> -->
<div class="bg">
<div class="img-logo"></div>
<div class="img-title1"></div>
......
<script setup lang="ts">
import { nextTick, onMounted, onUnmounted, ref } from 'vue'
import { nextTick, onMounted, onUnmounted, ref, watch } from 'vue'
import { useRouter } from 'vue-router'
import Loading from './views/LoadingView.vue'
import Rank1 from './views/Rank1View.vue'
import Rank2 from './views/Rank2View.vue'
import { useAdminGameSocket } from '@/composables/useAdminGameSocket'
import type Player from '@/commons/player.ts'
import { playGame1Music, stopGame1Music } from '@/commons/music'
import { playApplauseMusic, playGame1Music, stopAllMusic, stopGame1Music } from '@/commons/music'
import { $remove_socket_storage } from '@/commons/utils.ts'
const router = useRouter()
......@@ -114,17 +114,22 @@ const gotoRank1WithIntro = async () => {
isPlayingStartAnimation = false
}
function handleRoomState(data: any) {
const gameId = 'game1'
const { startGame, createRoom, backRoom, closeRoom } = useAdminGameSocket({
gameId,
onRoomState: (data: any) => {
if (Array.isArray(data?.list)) {
renderPlayers(data)
}
switch (data?.status) {
case 0:
gotoLoading(true)
createRoom(1)
break
case 1:
// if(data.gameId && data.gameId!=gameId){
// return;
// }
gotoLoading()
break
case 2:
......@@ -135,11 +140,7 @@ function handleRoomState(data: any) {
screen.value = 'rank2'
break
}
}
const { startGame, createRoom, backRoom, closeRoom } = useAdminGameSocket({
gameId: 'game1',
onRoomState: handleRoomState,
},
onRoomJoin: (data) => {
renderPlayers(data)
gotoLoading()
......@@ -184,9 +185,18 @@ onMounted(() => {
})
onUnmounted(() => {
stopGame1Music()
stopAllMusic()
window.removeEventListener('beforeunload', confirmRefresh)
})
watch(screen, (value) => {
// console.log(value);
if(value == 'rank2'){
playApplauseMusic();
}else if(value == 'loading'){
playGame1Music();
}
})
</script>
<template>
......
<script setup lang="ts">
import { nextTick, onMounted, onUnmounted, ref } from "vue";
import { nextTick, onMounted, onUnmounted, ref, watch } from "vue";
import { playApplauseMusic, playGame4Music, stopAllMusic, stopGame4Music } from '@/commons/music'
import Loading from "./views/LoadingView.vue";
import Rank1 from "./views/Rank1View.vue";
import Rank2 from "./views/Rank2View.vue";
import { useAdminGameSocket } from "@/composables/useAdminGameSocket";
import { onSocketMessage } from "@/commons/ws";
import type Player from "@/commons/player.ts";
import { useRouter } from 'vue-router'
import { $remove_socket_storage } from "@/commons/utils.ts";
type GameScreen = "loading" | "rank1" | "rank2";
const router = useRouter()
const PLAYER_LIMIT = 20;
const RANK_LIMIT = 10;
type GameScreen = 'loading' | 'rank1' | 'rank2'
const PLAYER_LIMIT = 20
const RANK_LIMIT = 10
const emptyLoadingPlayer = () => ({
score: 0,
telephone: "",
userid: "",
wechat_id: "",
nickname: "虚位以待",
avatar: "",
});
score: '-',
telephone: '',
userid: '',
wechat_id: '',
nickname: '虚位以待',
avatar: '',
})
const emptyRankPlayer = (index: number): Player => ({
active: false,
wechat_id: `empty-${index}`,
nickname: "虚位以待",
avatar: "",
score: 0,
});
const screen = ref<GameScreen>("loading");
const loadingPlayers = ref<any[]>(
Array.from({ length: PLAYER_LIMIT }, emptyLoadingPlayer),
);
const playerCount = ref(0);
const rankPlayers = ref<Player[]>(
Array.from({ length: RANK_LIMIT }, (_, index) => emptyRankPlayer(index)),
);
const rank1Ref = ref<InstanceType<typeof Rank1> | null>(null);
let isPlayingStartAnimation = false;
// H5 操控传来的加速度: { [userid]: boost(叠加系数, 0~N) }
const horseBoosts = ref<Record<string, number>>({});
nickname: '虚位以待',
avatar: '',
score: '-',
})
const screen = ref<GameScreen>('loading')
const loadingPlayers = ref<any[]>(Array.from({ length: PLAYER_LIMIT }, emptyLoadingPlayer))
const playerCount = ref(0)
const rankPlayers = ref<Player[]>(Array.from({ length: RANK_LIMIT }, (_, index) => emptyRankPlayer(index)))
const rank1Ref = ref<InstanceType<typeof Rank1> | null>(null)
let isPlayingStartAnimation = false
const confirmRefresh = (event: BeforeUnloadEvent) => {
// closeRoom();
event.preventDefault()
event.returnValue = ''
}
const renderPlayers = (data: any) => {
const players = Array.isArray(data?.list) ? data.list : []
playerCount.value = Number(data?.count ?? players.length)
loadingPlayers.value = Array.from(
{ length: PLAYER_LIMIT },
(_, index) => players[index] ?? emptyLoadingPlayer(),
)
}
const updateRankPlayers = (data: any) => {
if (!Array.isArray(data)) return
rankPlayers.value = Array.from(
{ length: RANK_LIMIT },
(_, index) => data[index] ?? emptyRankPlayer(index),
)
}
const playerToRankPlayer = (player: any, index: number): Player => ({
telephone: player?.telephone ?? '',
userid: player?.userid ?? '',
wechat_id: player?.wechat_id || `empty-${index}`,
nickname: player?.nickname || '虚位以待',
avatar: player?.avatar || '',
score: player?.score ?? '-',
})
const seedRankPlayersFromLoading = () => {
rankPlayers.value = Array.from(
{ length: RANK_LIMIT },
(_, index) => {
const player = loadingPlayers.value[index]
if (!player?.userid && !player?.wechat_id) {
return emptyRankPlayer(index)
}
return playerToRankPlayer(player, index)
},
)
}
let offHorseBoost: (() => void) | undefined;
const resetRoundState = () => {
playerCount.value = 0
loadingPlayers.value = Array.from({ length: PLAYER_LIMIT }, emptyLoadingPlayer)
rankPlayers.value = Array.from({ length: RANK_LIMIT }, (_, index) => emptyRankPlayer(index))
isPlayingStartAnimation = false
}
const gotoLoading = (reset = false) => {
if (reset) {
resetRoundState()
}
screen.value = 'loading'
}
// 监听 WebSocket 获取 H5 玩家操控的马匹加速度
offHorseBoost = onSocketMessage((evt, payload) => {
const gotoRank1WithIntro = async () => {
if (isPlayingStartAnimation) return
isPlayingStartAnimation = true
seedRankPlayersFromLoading()
screen.value = 'rank1'
await nextTick()
await rank1Ref.value?.startIntro()
screen.value = 'rank2'
isPlayingStartAnimation = false
}
const horseBoosts = ref<Record<string, number>>({});
const gameId = 'game4'
const { startGame, createRoom, backRoom, closeRoom } = useAdminGameSocket({
gameId,
onAny: (evt, payload) => {
const data = payload?.data ?? payload;
// 调试日志:打印 WebSocket 原始数据
......@@ -81,125 +155,281 @@ offHorseBoost = onSocketMessage((evt, payload) => {
console.log("[Game4 WebSocket] 解析到加速数据:", boosts);
horseBoosts.value = { ...boosts };
}
});
const confirmRefresh = (event: BeforeUnloadEvent) => {
event.preventDefault();
event.returnValue = "";
};
const renderPlayers = (data: any) => {
const players = Array.isArray(data?.list) ? data.list : [];
playerCount.value = Number(data?.count ?? players.length);
loadingPlayers.value = Array.from(
{ length: PLAYER_LIMIT },
(_, index) => players[index] ?? emptyLoadingPlayer(),
);
};
const updateRankPlayers = (data: any) => {
if (!Array.isArray(data)) return;
rankPlayers.value = Array.from(
{ length: RANK_LIMIT },
(_, index) => data[index] ?? emptyRankPlayer(index),
);
};
const gotoLoading = (reset = false) => {
if (reset) {
playerCount.value = 0;
loadingPlayers.value = Array.from(
{ length: PLAYER_LIMIT },
emptyLoadingPlayer,
);
rankPlayers.value = Array.from(
{ length: RANK_LIMIT },
(_, index) => emptyRankPlayer(index),
);
isPlayingStartAnimation = false;
}
screen.value = "loading";
};
const gotoRank1WithIntro = async () => {
if (isPlayingStartAnimation) return;
isPlayingStartAnimation = true;
screen.value = "rank1";
await nextTick();
await rank1Ref.value?.startIntro();
// 结果页已在 Rank1View 内部渲染,不需要切到 rank2
isPlayingStartAnimation = false;
};
function handleRoomState(data: any) {
},
onRoomState: (data: any) => {
if (Array.isArray(data?.list)) {
renderPlayers(data);
renderPlayers(data)
}
switch (data?.status) {
case 0:
gotoLoading(true);
createRoom(4);
break;
gotoLoading(true)
createRoom(4)
break
case 1:
gotoLoading();
break;
// if(data.gameId && data.gameId!=gameId){
// return;
// }
gotoLoading()
break
case 2:
screen.value = "rank1";
break;
seedRankPlayersFromLoading()
screen.value = 'rank1'
break
case 3:
screen.value = "rank2";
break;
screen.value = 'rank2'
break
}
}
const { startGame, createRoom, backRoom, requestRoomState } = useAdminGameSocket({
gameId: "game4",
// loginRedirectPath: '/game4',
onRoomState: handleRoomState,
},
onRoomJoin: (data) => {
renderPlayers(data);
gotoLoading();
renderPlayers(data)
gotoLoading()
},
onGameStart: gotoRank1WithIntro,
onRoomRank: updateRankPlayers,
});
onRelogin: async () => {
$remove_socket_storage();
await router.replace('/')
}
})
const startGameWithMusic = () => {
if (playerCount.value == 0) {
alert('房间中没有玩家')
return;
}
startGame()
}
const nextRound = () => {
gotoLoading(true);
// 使用 backRoom 重置已有房间,而非 createRoom 重复创建
backRoom(1);
setTimeout(() => {
requestRoomState();
}, 200);
};
gotoLoading(true)
//重开房间
backRoom(4)
}
const nextRoundWithMusic = () => {
nextRound()
}
const backHandler = () => {
if (confirm('是否关闭当前房间回到首页')) {
closeRoom();
stopGame4Music()
router.replace('/main');
}
}
onMounted(() => {
window.addEventListener("beforeunload", confirmRefresh);
});
window.addEventListener('beforeunload', confirmRefresh)
playGame4Music()
})
onUnmounted(() => {
window.removeEventListener("beforeunload", confirmRefresh);
offHorseBoost?.();
});
stopAllMusic()
window.removeEventListener('beforeunload', confirmRefresh)
})
watch(screen, (value) => {
// console.log(value);
if (value == 'rank2') {
playApplauseMusic();
} else if (value == 'loading') {
playGame4Music();
}
})
// type GameScreen = "loading" | "rank1" | "rank2";
// const PLAYER_LIMIT = 20;
// const RANK_LIMIT = 10;
// const emptyLoadingPlayer = () => ({
// score: 0,
// telephone: "",
// userid: "",
// wechat_id: "",
// nickname: "虚位以待",
// avatar: "",
// });
// const emptyRankPlayer = (index: number): Player => ({
// active: false,
// wechat_id: `empty-${index}`,
// nickname: "虚位以待",
// avatar: "",
// score: 0,
// });
// const screen = ref<GameScreen>("loading");
// const loadingPlayers = ref<any[]>(
// Array.from({ length: PLAYER_LIMIT }, emptyLoadingPlayer),
// );
// const playerCount = ref(0);
// const rankPlayers = ref<Player[]>(
// Array.from({ length: RANK_LIMIT }, (_, index) => emptyRankPlayer(index)),
// );
// const rank1Ref = ref<InstanceType<typeof Rank1> | null>(null);
// let isPlayingStartAnimation = false;
// // H5 操控传来的加速度: { [userid]: boost(叠加系数, 0~N) }
// const horseBoosts = ref<Record<string, number>>({});
// let offHorseBoost: (() => void) | undefined;
// // 监听 WebSocket 获取 H5 玩家操控的马匹加速度
// offHorseBoost = onSocketMessage((evt, payload) => {
// const data = payload?.data ?? payload;
// // 调试日志:打印 WebSocket 原始数据
// console.log("[Game4 WebSocket] 收到消息:", {
// event: evt,
// payloadType: typeof payload,
// dataType: typeof data,
// isArray: Array.isArray(data),
// rawPayload: payload,
// });
// if (!Array.isArray(data)) return;
// const boosts: Record<string, number> = {};
// let hasBoost = false;
// data.forEach((item: any) => {
// // WebSocket 数据无 userid/wechat_id,用 avatar 作为稳定标识
// const id = item.userid || item.wechat_id || item.avatar || "";
// if (!id) return;
// // 尝试多种字段名: score / boost / acceleration / speed / progress
// const raw = item.score ?? item.boost ?? item.acceleration ?? item.speed ?? item.progress;
// if (raw === undefined || raw === null) return;
// const val = Number(raw);
// if (isNaN(val)) return;
// // score/boost 范围 0~N,0 表示无加速,越大越快
// boosts[id] = Math.max(0, val);
// hasBoost = true;
// });
// if (hasBoost) {
// console.log("[Game4 WebSocket] 解析到加速数据:", boosts);
// horseBoosts.value = { ...boosts };
// }
// });
// const confirmRefresh = (event: BeforeUnloadEvent) => {
// event.preventDefault();
// event.returnValue = "";
// };
// const renderPlayers = (data: any) => {
// const players = Array.isArray(data?.list) ? data.list : [];
// playerCount.value = Number(data?.count ?? players.length);
// loadingPlayers.value = Array.from(
// { length: PLAYER_LIMIT },
// (_, index) => players[index] ?? emptyLoadingPlayer(),
// );
// };
// const updateRankPlayers = (data: any) => {
// if (!Array.isArray(data)) return;
// rankPlayers.value = Array.from(
// { length: RANK_LIMIT },
// (_, index) => data[index] ?? emptyRankPlayer(index),
// );
// };
// const gotoLoading = (reset = false) => {
// if (reset) {
// playerCount.value = 0;
// loadingPlayers.value = Array.from(
// { length: PLAYER_LIMIT },
// emptyLoadingPlayer,
// );
// rankPlayers.value = Array.from(
// { length: RANK_LIMIT },
// (_, index) => emptyRankPlayer(index),
// );
// isPlayingStartAnimation = false;
// }
// screen.value = "loading";
// };
// const gotoRank1WithIntro = async () => {
// if (isPlayingStartAnimation) return;
// isPlayingStartAnimation = true;
// screen.value = "rank1";
// await nextTick();
// await rank1Ref.value?.startIntro();
// // 结果页已在 Rank1View 内部渲染,不需要切到 rank2
// isPlayingStartAnimation = false;
// };
// function handleRoomState(data: any) {
// if (Array.isArray(data?.list)) {
// renderPlayers(data);
// }
// switch (data?.status) {
// case 0:
// gotoLoading(true);
// createRoom(4);
// break;
// case 1:
// gotoLoading();
// break;
// case 2:
// screen.value = "rank1";
// break;
// case 3:
// screen.value = "rank2";
// break;
// }
// }
// const { startGame, createRoom, backRoom, requestRoomState, closeRoom } = useAdminGameSocket({
// gameId: "game4",
// // loginRedirectPath: '/game4',
// onRoomState: handleRoomState,
// onRoomJoin: (data) => {
// renderPlayers(data);
// gotoLoading();
// },
// onGameStart: gotoRank1WithIntro,
// onRoomRank: updateRankPlayers,
// });
// const nextRound = () => {
// gotoLoading(true);
// // 使用 backRoom 重置已有房间,而非 createRoom 重复创建
// backRoom(1);
// setTimeout(() => {
// requestRoomState();
// }, 200);
// };
// onMounted(() => {
// window.addEventListener("beforeunload", confirmRefresh);
// playGame4Music()
// });
// onUnmounted(() => {
// stopGame4Music()
// window.removeEventListener("beforeunload", confirmRefresh);
// offHorseBoost?.();
// });
// const backHandler = () => {
// if (confirm('是否关闭当前房间回到首页')) {
// closeRoom();
// stopGame4Music()
// router.replace('/main');
// }
// }
</script>
<template>
<Loading
v-if="screen === 'loading'"
:players="loadingPlayers"
:player-count="playerCount"
@start="startGame"
/>
<Rank1
v-if="screen === 'rank1'"
ref="rank1Ref"
:rank-list="rankPlayers"
:horse-boosts="horseBoosts"
@start="startGame"
@next="nextRound"
/>
<Loading v-if="screen === 'loading'" :players="loadingPlayers" :player-count="playerCount"
@start="startGameWithMusic" @back="backHandler" />
<Rank1 v-if="screen === 'rank1'" ref="rank1Ref" :rank-list="rankPlayers" :horse-boosts="horseBoosts"
@start="startGameWithMusic" @next="nextRound" />
<!-- <Rank2 v-else-if="screen === 'rank2'" :rank-list="rankPlayers" @next="nextRound" /> -->
</template>
......@@ -11,6 +11,7 @@ defineProps<{
const emit = defineEmits<{
start: [];
back:[];
}>();
const imageUrls = {
......@@ -26,8 +27,10 @@ const imageUrls = {
avatarAnimation: cssAssetUrl("avatar_animation.png"),
avatar: cssAssetUrl("avatar.png"),
line: cssAssetUrl("game1/line.png"),
start1: cssAssetUrl("game1/start-1.png"),
start2: cssAssetUrl("game1/start-2.png"),
// start1: cssAssetUrl("game1/start-1.png"),
// start2: cssAssetUrl("game1/start-2.png"),
start: cssAssetUrl('game1/start.png'),
back: cssAssetUrl('game1/back.png'),
};
const isStartPressed = ref(false);
......@@ -67,10 +70,7 @@ const releaseStartButton = () => {
当前在线人数 <span>{{ playerCount }}</span>
</div>
<div class="list-container-left">
<div
v-for="(item, index) in players"
:key="item.userid || item.wechat_id || `empty-${index}`"
>
<div v-for="(item, index) in players" :key="item.userid || item.wechat_id || `empty-${index}`">
<div>
<div class="img-avatar-container">
<div class="img-avatar" :style="`${renderAvatar(item)}`"></div>
......@@ -83,14 +83,11 @@ const releaseStartButton = () => {
</div>
<div class="list-container-line"></div>
<div class="list-container-right">
<div
class="btn-start"
:class="{ pressed: isStartPressed }"
@pointerdown="pressStartButton"
@pointerup="releaseStartButton"
@pointerleave="releaseStartButton"
@pointercancel="releaseStartButton"
></div>
<!-- <div class="btn-start" :class="{ pressed: isStartPressed }" @pointerdown="pressStartButton"
@pointerup="releaseStartButton" @pointerleave="releaseStartButton" @pointercancel="releaseStartButton">
</div> -->
<div class="btn-start" @click="pressStartButton"></div>
<div class="btn-back" @click="emit('back')"></div>
</div>
</div>
</div>
......@@ -148,6 +145,7 @@ const releaseStartButton = () => {
width: 100%;
height: 144px;
border-radius: 10px;
img {
width: 140px;
height: 140px;
......@@ -159,6 +157,7 @@ const releaseStartButton = () => {
color: white;
}
}
.img-gift {
background: v-bind("imageUrls.gift") no-repeat;
width: 315px;
......@@ -167,6 +166,7 @@ const releaseStartButton = () => {
top: 124px;
right: 320px;
}
.img-small-gu {
background: v-bind("imageUrls.smallGu") no-repeat;
width: 161px;
......@@ -175,6 +175,7 @@ const releaseStartButton = () => {
top: 500px;
right: 340px;
}
.img-gu {
background: v-bind("imageUrls.gu") no-repeat;
width: 320px;
......@@ -199,7 +200,7 @@ const releaseStartButton = () => {
color: #aa0000;
font-weight: bold;
> span {
>span {
margin-left: 20px;
font-size: 48px;
position: relative;
......@@ -266,13 +267,21 @@ const releaseStartButton = () => {
width: 316px;
height: 105px;
margin-left: 60px;
margin-top: 80px;
background: v-bind("imageUrls.start1");
margin-top: 10px;
background: v-bind("imageUrls.start");
cursor: pointer;
}
.btn-start.pressed {
/* .btn-start.pressed {
background: v-bind("imageUrls.start2");
} */
.btn-back {
width: 316px;
height: 105px;
margin-left: 60px;
margin-top: 30px;
background: v-bind('imageUrls.back');
cursor: pointer;
}
}
}
......
<script setup lang="ts">
import { nextTick, onMounted, onUnmounted, ref } from 'vue'
import { nextTick, onMounted, onUnmounted, ref, watch } from 'vue'
import { useRouter } from 'vue-router'
import Loading from './views/LoadingView.vue'
import Rank1 from './views/Rank1View.vue'
import Rank2 from './views/Rank2View.vue'
import { useAdminGameSocket } from '@/composables/useAdminGameSocket'
import type Player from '@/commons/player.ts'
import { playGame1Music } from '@/commons/music'
import { playApplauseMusic, playGame5Music, stopAllMusic, stopGame5Music } from '@/commons/music'
import { $remove_socket_storage } from '@/commons/utils.ts'
const router = useRouter()
......@@ -124,7 +124,7 @@ function handleRoomState(data: any) {
switch (data?.status) {
case 0:
gotoLoading(true)
createRoom(1)
createRoom(5)
break
case 1:
gotoLoading()
......@@ -139,7 +139,7 @@ function handleRoomState(data: any) {
}
}
const { startGame, createRoom, backRoom } = useAdminGameSocket({
const { startGame, createRoom, closeRoom, backRoom } = useAdminGameSocket({
gameId: 'game5',
onRoomState: handleRoomState,
onRoomJoin: (data) => {
......@@ -166,34 +166,52 @@ const startGameWithMusic = () => {
alert('房间中没有玩家')
return;
}
playGame1Music()
playGame5Music()
startGame()
}
const nextRound = () => {
gotoLoading(true)
//重开房间
backRoom(1)
backRoom(5)
}
const nextRoundWithMusic = () => {
playGame1Music()
playGame5Music()
nextRound()
}
onMounted(() => {
window.addEventListener('beforeunload', confirmRefresh)
playGame1Music()
playGame5Music()
})
onUnmounted(() => {
stopAllMusic()
window.removeEventListener('beforeunload', confirmRefresh)
})
const backHandler = () => {
if (confirm('是否关闭当前房间回到首页')) {
closeRoom();
stopGame5Music()
router.replace('/main');
}
}
watch(screen, (value) => {
// console.log(value);
if(value == 'rank2'){
playApplauseMusic();
}else if(value == 'loading'){
playGame5Music();
}
})
</script>
<template>
<Loading v-if="screen === 'loading'" :players="loadingPlayers" :player-count="playerCount"
@start="startGameWithMusic" />
@start="startGameWithMusic" @back="backHandler" />
<Rank1 v-else-if="screen === 'rank1'" ref="rank1Ref" :rank-list="rankPlayers" @start="startGameWithMusic"
@next="nextRoundWithMusic" />
<!-- <Rank1 v-else ref="rank1Ref" :rank-list="rankPlayers" @start="startGameWithMusic" -->
......
......@@ -11,6 +11,7 @@ defineProps<{
const emit = defineEmits<{
start: [];
back: [];
}>();
const imageUrls = {
......@@ -25,8 +26,10 @@ const imageUrls = {
avatarAnimation: cssAssetUrl('avatar_animation.png'),
avatar: cssAssetUrl('avatar.png'),
line: cssAssetUrl('game1/line.png'),
start1: cssAssetUrl('game1/start-1.png'),
start2: cssAssetUrl('game1/start-2.png'),
// start1: cssAssetUrl('game1/start-1.png'),
// start2: cssAssetUrl('game1/start-2.png'),
start: cssAssetUrl('game1/start.png'),
back: cssAssetUrl('game1/back.png'),
};
const isStartPressed = ref(false);
......@@ -74,14 +77,16 @@ const renderAvatar = (item: any | null) => {
</div>
<div class="list-container-line"></div>
<div class="list-container-right">
<div
<!-- <div
class="btn-start"
:class="{ pressed: isStartPressed }"
@pointerdown="pressStartButton"
@pointerup="releaseStartButton"
@pointerleave="releaseStartButton"
@pointercancel="releaseStartButton"
></div>
></div> -->
<div class="btn-start" @click="pressStartButton"></div>
<div class="btn-back" @click="emit('back')"></div>
</div>
</div>
</div>
......@@ -139,6 +144,7 @@ const renderAvatar = (item: any | null) => {
width: 100%;
height: 144px;
border-radius: 10px;
img {
width: 147px;
height: 147px;
......@@ -159,6 +165,7 @@ const renderAvatar = (item: any | null) => {
top: 193px;
left: 47px;
}
.img-right {
background: v-bind('imageUrls.right') no-repeat;
width: 423px;
......@@ -251,13 +258,21 @@ const renderAvatar = (item: any | null) => {
width: 316px;
height: 105px;
margin-left: 60px;
margin-top: 80px;
background: v-bind('imageUrls.start1');
margin-top: 10px;
background: v-bind('imageUrls.start');
cursor: pointer;
}
.btn-start.pressed {
/* .btn-start.pressed {
background: v-bind('imageUrls.start2');
} */
.btn-back {
width: 316px;
height: 105px;
margin-left: 60px;
margin-top: 30px;
background: v-bind('imageUrls.back');
cursor: pointer;
}
}
}
......
<script setup lang="ts">
import { nextTick, onMounted, onUnmounted, ref } from 'vue'
import { nextTick, onMounted, onUnmounted, ref, watch } from 'vue'
import { useRouter } from 'vue-router'
import Loading from './views/LoadingView.vue'
import Rank1 from './views/Rank1View.vue'
import Rank2 from './views/Rank2View.vue'
import { useAdminGameSocket } from '@/composables/useAdminGameSocket'
import type Player from '@/commons/player.ts'
import { playGame6Music, stopGame6Music } from '@/commons/music'
import { playApplauseMusic, playGame6Music, stopAllMusic, stopGame6Music } from '@/commons/music'
import { $remove_socket_storage } from '@/commons/utils.ts'
const router = useRouter()
......@@ -135,9 +135,9 @@ function handleRoomState(data: any) {
break
}
}
const gameId='game6'
const { startGame, createRoom, backRoom, closeRoom } = useAdminGameSocket({
gameId: 'game6',
gameId,
onRoomState: handleRoomState,
onRoomJoin: (data) => {
renderPlayers(data)
......@@ -183,9 +183,18 @@ onMounted(() => {
})
onUnmounted(() => {
stopGame6Music();
stopAllMusic();
window.removeEventListener('beforeunload', confirmRefresh)
})
watch(screen, (value) => {
// console.log(value);
if(value == 'rank2'){
playApplauseMusic();
}else if(value == 'loading'){
playGame6Music();
}
})
</script>
<template>
......
......@@ -89,7 +89,7 @@ const renderAvatar = (item: any | null) => {
<div class="no-icon">{{ index + 4 }}</div>
<div>
<div class="img-avatar-container">
<div class="img-avatar"></div>
<div class="img-avatar" :style="`${renderAvatar(list[index + 3])}`"></div>
</div>
<div class="txt-nickname">{{ item.nickname }}</div>
</div>
......
import { createRouter, createWebHashHistory } from 'vue-router'
import { $read } from '@/commons/utils'
import Login from '@/pages/Login.vue'
import Main from '@/pages/Main.vue'
import Game1 from '@/pages/game1/Game.vue'
import Game4 from '@/pages/game4/Game4.vue'
......@@ -11,12 +13,20 @@ const router = createRouter({
routes: [
{
path: '/',
redirect: () => ($read('token', '') ? '/game1' : '/main'),
redirect: () => ($read('token', '') ? '/main' : '/login'),
},
{
path: '/login',
name: 'Login',
component: Login,
},
{
path: '/main',
name: 'Main',
component: Main,
meta: {
requiresAuth: true,
},
},
{
path: '/game1',
......
......@@ -26,7 +26,7 @@ function copyImageAssets() {
}
export default defineConfig(({ command }) => ({
base: command === 'build' ? '/cc/pc/' : '/',
base: '/',//command === 'build' ? '/cc/pc/' : '/',
define: {
__APP_DOMAIN__: JSON.stringify(command === 'build' ? buildDomain : ''),
// __APP_DOMAIN__: JSON.stringify(buildDomain),
......
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