Commit 4b927c24 authored by 董政锦's avatar 董政锦

Merge branch 'main' into 'game4-0604'

# Conflicts: # frontend-h5/src/components/MobileStage.vue
parents 62122a8e 4a6831ac
......@@ -278,6 +278,45 @@ fn broadcast_room_rank(ranking: Vec<Value>) {
broadcast_msgpack_all_admin("room_rank_result", &ok_resp(Some(json!(ranking))));
}
fn emit_msgpack_to_user(userid: &str, event: &str, payload: &Value) {
if userid.is_empty() {
return;
}
if let Some(socket) = CLIENT_PLAYER_MAP.get(userid) {
emit_msgpack(&socket.1, event, payload);
}
}
fn push_room_score_ranking() -> bool {
let (ranking, player_ranks) = if let Ok(state) = ROOM_STATE.lock() {
let full_ranking = state.full_score_ranking();
let ranking = RoomState::ranking_payload(
&full_ranking
.iter()
.take(ROOM_SCORE_RANK_LIMIT)
.cloned()
.collect::<Vec<_>>(),
);
let player_ranks = full_ranking
.into_iter()
.enumerate()
.map(|(index, player)| (player.wechat, json!(index + 1)))
.collect::<Vec<_>>();
(ranking, player_ranks)
} else {
return false;
};
broadcast_room_rank(ranking);
for (userid, rank) in player_ranks {
emit_msgpack_to_user(&userid, "submit_score_result", &ok_resp(Some(rank)));
}
true
}
async fn sleep_room_status(round_id: u64, secs: u64, expected_status: RoomStatus) -> bool {
for _ in 0..secs {
tokio::time::sleep(Duration::from_secs(1)).await;
......@@ -306,8 +345,23 @@ fn spawn_game_timeout(round_id: u64, secs: u64) {
return;
}
if !sleep_room_status(round_id, running_secs, RoomStatus::Running).await {
return;
for _ in 0..running_secs {
tokio::time::sleep(Duration::from_secs(1)).await;
if GAME_ROUND_ID.load(Ordering::SeqCst) != round_id {
return;
}
if let Ok(state) = ROOM_STATE.lock() {
if state.status != RoomStatus::Running {
return;
}
}
// 游戏正式计时期间,每秒统一给大屏和玩家推一次实时排名。
if !push_room_score_ranking() {
return;
}
}
if let Ok(mut state) = ROOM_STATE.lock() {
......@@ -525,14 +579,16 @@ async fn handle_room_command(
player.score = score;
// player.wechat = wechat.to_string();
player.online = true;
drop(state);
// 方案1:每有一名玩家提交时发送给所有玩家跟管理员,玩家过多时发送太频繁,
// 这里会选第2种方案, 游戏中时每秒发给所有玩家跟管理员 (push_room_score_ranking)
// 玩家分数排序
let ranking = RoomState::ranking_payload(&state.score_ranking());
let player_rank = state.score_result_payload(userid);
drop(state);
emit_msgpack(&socket, evt, &ok_resp(player_rank));
//通知管理员
broadcast_room_rank(ranking);
// let ranking = RoomState::ranking_payload(&state.score_ranking());
// let player_rank = state.score_result_payload(userid);
// drop(state);
// emit_msgpack(&socket, evt, &ok_resp(player_rank));
// broadcast_room_rank(ranking);
} else {
drop(state);
emit_msgpack(&socket, evt, &err_resp("提交异常", None));
......
......@@ -151,9 +151,11 @@ defineExpose({
<div v-if="countdownVisible" class="modal-container">
<div :key="countdownKey" class="countdown-number">{{ countdown }}</div>
</div>
<div v-else-if="showGameRule" class="modal-container">
<slot name="gameRule" ></slot>
</div>
<Transition name="game-rule-fade">
<div v-if="!countdownVisible && showGameRule" class="modal-container">
<slot name="gameRule"></slot>
</div>
</Transition>
</main>
</template>
......@@ -190,6 +192,16 @@ defineExpose({
/* pointer-events: none; */
}
.game-rule-fade-enter-active,
.game-rule-fade-leave-active {
transition: opacity 0.25s ease;
}
.game-rule-fade-enter-from,
.game-rule-fade-leave-to {
opacity: 0;
}
.countdown-number {
color: white;
font-size: 200pt;
......
......@@ -16,7 +16,7 @@ const imageUrls = {
bg2: cssAssetUrl('game1/bg2.png'),
logo: cssAssetUrl('logo.png'),
title: cssAssetUrl('game1/title.png'),
avatar: cssAssetUrl('avatar.png'),
avatar: cssAssetUrl('avatar-yellow.png'),
gu: cssAssetUrl('game1/gu.png'),
clock: cssAssetUrl('game1/clock.png'),
gu01: cssAssetUrl('game1/gu01.png'),
......@@ -26,6 +26,8 @@ const imageUrls = {
back: cssAssetUrl('game1/back.png'),
replay: cssAssetUrl('game1/replay.png'),
close: cssAssetUrl('game1/close.png'),
rule: cssAssetUrl('game1/rule.png')
}
const wechat = $getWechat()
......@@ -140,14 +142,6 @@ function showScoreView() {
currentView.value = 'score'
}
function replayGame() {
startGameView()
}
function backToWaiting() {
resetToLoadingView()
joinSharedRoom(nickname.value, token.value, avatar.value)
}
function handleRoomBack() {
resetToLoadingView()
......@@ -161,7 +155,7 @@ const touchHandler = () => {
return
}
tick.value += 1
tick.value += 5
submitScore()
playGame1Music();
isGuPlaying.value = true
......@@ -250,12 +244,14 @@ const showGameRuleHandler = () => {
<div class="rule-txt-container">
<div class="rule-title">游戏规则</div>
<div class="rule-txt">
微信用户通过扫描大屏幕二维码参与,可容纳多人同时进行且比拼敲鼓,谁敲的最多即为获胜,游戏结束按分值高低先后排名,有机会获得新疆福彩定制周边礼品一份。
1.主持人开赛即可点击鼓面,单次点击得 5 分;<br />
2.限时比拼手速,结束后按积分排名;<br />
3.高分用户有机会领取新疆福彩定制周边;<br />
4.禁止外挂,作弊,违规账号取消成绩与领奖资格;<br /><br />
祝您游戏愉快!
</div>
</div>
<div class="rule-close">
<div @click="showGameRuleHandler"></div>
</div>
<div class="rule-close" @click="showGameRuleHandler"></div>
</div>
</template>
<LoadingView v-if="currentView === 'loading'" :image-urls="imageUrls" :user-join-status="userJoinStatus"
......@@ -263,8 +259,7 @@ const showGameRuleHandler = () => {
<PlayingView v-else-if="currentView === 'playing'" :image-urls="imageUrls" :tick="tick" :rank="rank"
:current-gu="currentGu" :countdown-interval="countdownInterval" :is-div-desc-visible="isDivDescVisible"
@touch="touchHandler" />
<ScoreView v-else :image-urls="imageUrls" :rank="rank" :tick="tick" @replay="replayGame"
@back="backToWaiting" />
<ScoreView v-else :image-urls="imageUrls" :rank="rank" :tick="tick" />
</MobileStage>
<div v-else style="text-align: center; width: 100vw; height: 100vh; line-height: 30; font-size: 20px;">
请使用微信扫码进入游戏
......@@ -273,40 +268,39 @@ const showGameRuleHandler = () => {
<style scoped>
.rule-container {
.rule-txt-container {
background: #FFF9E4;
margin:0 40px;
border-radius: 5px;
padding-top: 20px;
padding-bottom: 10px;
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 {
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;
width: 57px;
height: 57px;
margin: 0 auto;
margin-top: 450px;
background: v-bind('imageUrls.close') center/cover;
position: relative;
>div {
position: absolute;
bottom: 0;
width: 32px;
height: 32px;
border-radius: 16px;
}
transform: scale(0.8);
}
}
</style>
\ No newline at end of file
......@@ -20,12 +20,11 @@ onMounted(() => {
<div class="game-desc" @click="$emit('touchGameRule')">游戏规则</div>
<div class="img-logo"></div>
<div class="img-title"></div>
<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">
<label v-if="userJoinStatus">您已成功加入游戏等待主持人开始</label>
<label v-else>正在加入游戏...</label>
<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-loading2">游戏即将开始 敬请期待</div>
<div class="bg-bottom"></div>
<div class="loading-gu"></div>
</div>
......@@ -105,7 +104,7 @@ onMounted(() => {
top: 640px;
left: 50%;
color: #E00000;
font-size: 25pt;
font-size: 30pt;
font-weight: bold;
transform: translateX(-50%);
}
......@@ -115,29 +114,28 @@ onMounted(() => {
top: 710px;
left: 50%;
width: 288px;
color: #E00000;
color: #6F0000;
font-size: 25pt;
font-weight: bold;
text-align: center;
transform: translateX(-50%);
}
.txt-loading label {
display: inline-block;
transform-origin: center;
animation: loading-text-scale 1.2s ease-in-out infinite;
}
@keyframes loading-text-scale {
0%,
100% {
transform: scale(1);
}
50% {
transform: scale(1.08);
}
.txt-loading2 {
width: 500px;
height: 90px;
background: #E61903;
color: white;
position: absolute;
top:850px;
left: 50%;
transform: translate(-50%);
line-height: 90px;
text-align: center;
font-size: 36px;
font-weight: bold;
letter-spacing:5px;
border-radius: 10px;
}
.loading-gu {
......
......@@ -37,10 +37,6 @@ onMounted(() => {
<div><label>{{ rank }}</label></div>
</div>
</div>
<div class="btn-group">
<div class="btn-replay" @click="$emit('replay')"></div>
<div class="btn-back" @click="$emit('back')"></div>
</div>
</div>
</template>
......@@ -142,26 +138,4 @@ onMounted(() => {
margin: 0 10px;
}
.btn-group {
position: absolute;
top: 1320px;
left: 50%;
display: flex;
gap: 40px;
transform: translateX(-50%);
}
.btn-replay,
.btn-back {
width: 315px;
height: 100px;
}
.btn-replay {
background: v-bind('imageUrls.replay') center/cover;
}
.btn-back {
background: v-bind('imageUrls.back') center/cover;
}
</style>
......@@ -120,7 +120,7 @@ function handleTargetTouch(index: number) {
return
}
const scoreDelta = item.kind === 'mole' ? 1 : -1
const scoreDelta = item.kind === 'mole' ? 5 : -3
emit('touch', scoreDelta)
clearTargetTimer(item)
item.state = 'hit'
......
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
<script setup lang="ts">
import { cssAssetUrl } from '@/commons/assets.ts';
const imageUrls = {
bg1: cssAssetUrl('main-bg1.png'),
logo: cssAssetUrl('game1/logo.png'),
title1: cssAssetUrl('main-title.png'),
}
</script>
<template>
<div class="bg">
<div class="img-logo"></div>
<div class="img-title1"></div>
<div class="rule-container">
<div class="rule-title">游戏规则</div>
<div class="rule-text">
扫码即玩,大屏实时同步。每局结束大屏公布前10名。主持人邀请获奖用户,凭获奖界面作为凭证,上台领奖。,核对后发放实物奖品。奖品不折现、不替换,逾期作废。
</div>
</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;
}
.rule-container {
box-sizing: border-box;
width: 638px;
min-height: 162px;
border-radius: 12px;
background:
linear-gradient(90deg, rgba(124, 77, 28, 0.74), rgba(136, 122, 76, 0.72)),
rgba(124, 91, 48, 0.52);
backdrop-filter: blur(18px) saturate(0.9);
-webkit-backdrop-filter: blur(18px) saturate(0.9);
box-shadow:
inset 0 1px 0 rgba(255, 255, 255, 0.18),
inset 0 -1px 0 rgba(0, 0, 0, 0.08);
color: white;
padding: 27px 34px 24px;
position: absolute;
left: 0;
top: 0;
font-weight: 700;
text-shadow:
0 2px 0 rgba(0, 0, 0, 0.65),
1px 0 0 rgba(0, 0, 0, 0.45),
-1px 0 0 rgba(0, 0, 0, 0.45),
0 -1px 0 rgba(0, 0, 0, 0.45);
}
.rule-title {
margin-bottom: 12px;
font-size: 22px;
line-height: 1.2;
text-align: center;
}
.rule-text {
font-size: 16px;
line-height: 1.58;
}
</style>
......@@ -64,7 +64,7 @@ const renderAvatar = (item: any | null) => {
<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.avatar)}`"></div>
<div class="img-avatar" :style="`${renderAvatar(item)}`"></div>
</div>
<div class="txt-nickname">{{ $format_str(item.nickname, 12) }}</div>
</div>
......
......@@ -182,9 +182,12 @@ onUnmounted(() => {
</script>
<template>
<Rank1 ref="rank1Ref" :rank-list="rankPlayers" @start="startGameWithMusic" />
<!-- <Loading v-if="screen === 'loading'" :players="loadingPlayers" :player-count="playerCount"
<!-- <Loading :players="loadingPlayers" :player-count="playerCount"
@start="startGameWithMusic" /> -->
<!-- <Rank1 ref="rank1Ref" :rank-list="rankPlayers" @start="startGameWithMusic" /> -->
<!-- <Rank2 :rank-list="rankPlayers" @next="nextRoundWithMusic" /> -->
<Loading v-if="screen === 'loading'" :players="loadingPlayers" :player-count="playerCount"
@start="startGameWithMusic" />
<Rank1 v-else-if="screen === 'rank1'" ref="rank1Ref" :rank-list="rankPlayers" @start="startGameWithMusic" />
<Rank2 v-else :rank-list="rankPlayers" @next="nextRoundWithMusic" /> -->
<Rank2 v-else :rank-list="rankPlayers" @next="nextRoundWithMusic" />
</template>
......@@ -19,7 +19,7 @@ const imageUrls = {
title1: cssAssetUrl('game6/title.png'),
title2: cssAssetUrl('game1/title2.png'),
qrcode: assetUrl('game6/qrcode.png'),
gu: cssAssetUrl('game1/gu.png'),
// gu: cssAssetUrl('game1/gu.png'),
// bg: cssAssetUrl('game1/bg.png'),
// logo: cssAssetUrl('game1/logo.png'),
......@@ -27,12 +27,12 @@ const imageUrls = {
// title2: cssAssetUrl('game1/title2.png'),
// qrcode: assetUrl('game1/qrcode.png'),
// gu: cssAssetUrl('game1/gu.png'),
imgMole:cssAssetUrl('game6/mole.png'),
imgRabbit:cssAssetUrl('game6/rabbit.png'),
imgMole: cssAssetUrl('game6/mole.png'),
imgRabbit: cssAssetUrl('game6/rabbit.png'),
bottomLayers: cssAssetUrl('game1/bottom-layers.png'),
avatarAnimation: cssAssetUrl('avatar_animation.png'),
avatar: cssAssetUrl('avatar.png'),
avatarAnimation: cssAssetUrl('avatar-red_animation.png'),
avatar: cssAssetUrl('avatar-red.png'),
line: cssAssetUrl('game1/line.png'),
start1: cssAssetUrl('game1/start-1.png'),
start2: cssAssetUrl('game1/start-2.png'),
......@@ -52,7 +52,7 @@ const releaseStartButton = () => {
const renderAvatar = (item: any | null) => {
if (item && item.avatar) {
return `background: url("${String(item.avatar).replace(/"/g, '\\"')}") center / cover no-repeat;`
}
}
return `background: ${imageUrls.avatar} center / cover no-repeat;`
}
</script>
......@@ -75,7 +75,7 @@ const renderAvatar = (item: any | null) => {
<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>
<div class="img-avatar" :style="`${renderAvatar(item)}`"></div>
</div>
<div class="txt-nickname">{{ $format_str(item.nickname, 12) }}</div>
</div>
......@@ -83,14 +83,9 @@ const renderAvatar = (item: any | null) => {
</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>
</div>
</div>
......@@ -164,6 +159,7 @@ const renderAvatar = (item: any | null) => {
top: 315px;
left: 150px;
}
.img-rabbit {
background: v-bind('imageUrls.imgRabbit') center / cover no-repeat;
width: 475px;
......@@ -180,7 +176,7 @@ const renderAvatar = (item: any | null) => {
position: absolute;
left: 60px;
bottom: 60px;
border:3px solid #AA0000;
border: 3px solid #AA0000;
border-radius: 10px;
.txt-count {
......@@ -221,7 +217,7 @@ const renderAvatar = (item: any | null) => {
position: absolute;
inset: 0;
border-radius: inherit;
background: v-bind('imageUrls.avatarAnimation');
background: v-bind('imageUrls.avatarAnimation') center / cover no-repeat;
animation: avatar-rotate 10s linear infinite;
}
......
import { createRouter, createWebHashHistory } from 'vue-router'
import { $read } from '@/commons/utils'
import Main from '@/pages/Main.vue'
import Login from '@/pages/Login.vue'
import Game1 from '@/pages/game1/Game.vue'
import Game4 from '@/pages/game4/Game4.vue'
......@@ -10,7 +11,12 @@ const router = createRouter({
routes: [
{
path: '/',
redirect: () => ($read('token', '') ? '/game1' : '/login'),
redirect: () => ($read('token', '') ? '/game1' : '/main'),
},
{
path: '/main',
name: 'Main',
component: Main,
},
{
path: '/login',
......
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