Skip to content
项目
群组
代码片段
帮助
当前项目
正在载入...
登录 / 注册
切换导航面板
P
paopao
项目
项目
详情
活动
周期分析
仓库
仓库
文件
提交
分支
标签
贡献者
图表
比较
统计图
议题
0
议题
0
列表
看板
标记
里程碑
合并请求
0
合并请求
0
CI / CD
CI / CD
流水线
作业
日程
统计图
Wiki
Wiki
代码片段
代码片段
成员
成员
折叠边栏
关闭边栏
活动
图像
聊天
创建新问题
作业
提交
问题看板
Open sidebar
cocktail-party
paopao
Commits
9a27f2a4
提交
9a27f2a4
authored
4月 01, 2026
作者:
lidongxu
浏览文件
操作
浏览文件
下载
电子邮件补丁
差异文件
大屏幕效果ok版本
上级
c8fd4e62
显示空白字符变更
内嵌
并排
正在显示
5 个修改的文件
包含
267 行增加
和
44 行删除
+267
-44
main.js
big-screen/src/main.js
+129
-36
gameinfo.js
big-screen/src/renderer/gameinfo.js
+75
-3
socket.js
big-screen/src/socket.js
+5
-1
stateManager.js
big-screen/src/stateManager.js
+45
-0
roomHandler.js
server/src/socket/roomHandler.js
+13
-4
没有找到文件。
big-screen/src/main.js
浏览文件 @
9a27f2a4
...
...
@@ -2,13 +2,13 @@
* 大屏展示页入口:支持多玩家并排渲染
*/
import
{
initScaler
}
from
'./scaler.js'
import
{
getAllPlayerStates
,
clearGameState
,
getCurrentRoom
,
getPlayerTeam
,
getCountdown
}
from
'./stateManager.js'
import
{
getAllPlayerStates
,
clearGameState
,
getCurrentRoom
,
getPlayerTeam
,
getCountdown
,
getRoomTimer
}
from
'./stateManager.js'
import
{
initSocket
,
getConnectionStatus
}
from
'./socket.js'
import
{
drawBackground
}
from
'./renderer/background.js'
import
{
drawBubbleGrid
}
from
'./renderer/bubbleGrid.js'
import
{
drawBubble3D
,
getBubbleRadius
,
configureBubbleLayout
}
from
'./renderer/bubble.js'
import
{
drawShooter
}
from
'./renderer/shooter.js'
import
{
drawGameInfo
,
drawTeamResultOverlay
}
from
'./renderer/gameinfo.js'
import
{
drawGameInfo
,
drawTeamResultOverlay
,
drawRoomTimerCard
}
from
'./renderer/gameinfo.js'
import
{
detectAndCreateBursts
,
updateAndDrawBursts
,
clearPrevGrid
}
from
'./renderer/explosion.js'
import
{
drawIdleScreen
}
from
'./renderer/idleScreen.js'
import
{
SCREEN_WIDTH
,
SCREEN_HEIGHT
,
configureScreenRatio
,
configureSafeArea
}
from
'./constants.js'
...
...
@@ -25,6 +25,10 @@ gameBgImg.src = GAME_BG_URL
/** 每个玩家独立的碎裂效果列表:Map<playerId, BubbleBurst[]> */
const
playerBursts
=
new
Map
()
const
PLAYER_FRAME_MARGIN_X
=
18
const
PLAYER_FRAME_MARGIN_Y
=
18
const
PLAYER_FRAME_RADIUS
=
18
let
frameCount
=
0
// ─── 缩放 ─────────────────────────────────────────────────────────────────────
...
...
@@ -50,10 +54,76 @@ function drawPlayerGameBackground() {
ctx
.
fillRect
(
0
,
0
,
SCREEN_WIDTH
,
SCREEN_HEIGHT
)
}
function
roundRectPath
(
x
,
y
,
w
,
h
,
r
)
{
ctx
.
beginPath
()
ctx
.
moveTo
(
x
+
r
,
y
)
ctx
.
lineTo
(
x
+
w
-
r
,
y
)
ctx
.
arcTo
(
x
+
w
,
y
,
x
+
w
,
y
+
r
,
r
)
ctx
.
lineTo
(
x
+
w
,
y
+
h
-
r
)
ctx
.
arcTo
(
x
+
w
,
y
+
h
,
x
+
w
-
r
,
y
+
h
,
r
)
ctx
.
lineTo
(
x
+
r
,
y
+
h
)
ctx
.
arcTo
(
x
,
y
+
h
,
x
,
y
+
h
-
r
,
r
)
ctx
.
lineTo
(
x
,
y
+
r
)
ctx
.
arcTo
(
x
,
y
,
x
+
r
,
y
,
r
)
ctx
.
closePath
()
}
function
getPlayerFrameRect
()
{
return
{
x
:
PLAYER_FRAME_MARGIN_X
,
y
:
PLAYER_FRAME_MARGIN_Y
,
w
:
SCREEN_WIDTH
-
PLAYER_FRAME_MARGIN_X
*
2
,
h
:
SCREEN_HEIGHT
-
PLAYER_FRAME_MARGIN_Y
*
2
,
r
:
PLAYER_FRAME_RADIUS
,
}
}
function
drawPlayerFrameShell
(
frame
)
{
ctx
.
save
()
ctx
.
shadowColor
=
'rgba(0,0,0,0.38)'
ctx
.
shadowBlur
=
24
ctx
.
shadowOffsetY
=
10
const
bg
=
ctx
.
createLinearGradient
(
frame
.
x
,
frame
.
y
,
frame
.
x
,
frame
.
y
+
frame
.
h
)
bg
.
addColorStop
(
0
,
'rgba(20,10,42,0.30)'
)
bg
.
addColorStop
(
1
,
'rgba(8,4,22,0.46)'
)
ctx
.
fillStyle
=
bg
roundRectPath
(
frame
.
x
,
frame
.
y
,
frame
.
w
,
frame
.
h
,
frame
.
r
)
ctx
.
fill
()
ctx
.
restore
()
}
function
drawPlayerFrameBorder
(
frame
)
{
ctx
.
save
()
ctx
.
lineWidth
=
3
ctx
.
strokeStyle
=
'rgba(251,191,36,0.82)'
ctx
.
shadowColor
=
'rgba(251,191,36,0.28)'
ctx
.
shadowBlur
=
18
roundRectPath
(
frame
.
x
,
frame
.
y
,
frame
.
w
,
frame
.
h
,
frame
.
r
)
ctx
.
stroke
()
ctx
.
shadowBlur
=
0
ctx
.
save
()
roundRectPath
(
frame
.
x
,
frame
.
y
,
frame
.
w
,
frame
.
h
,
frame
.
r
)
ctx
.
clip
()
const
hl
=
ctx
.
createLinearGradient
(
frame
.
x
,
frame
.
y
,
frame
.
x
,
frame
.
y
+
frame
.
h
*
0.22
)
hl
.
addColorStop
(
0
,
'rgba(255,255,255,0.22)'
)
hl
.
addColorStop
(
1
,
'rgba(255,255,255,0)'
)
ctx
.
fillStyle
=
hl
ctx
.
fillRect
(
frame
.
x
,
frame
.
y
,
frame
.
w
,
frame
.
h
*
0.22
)
ctx
.
restore
()
ctx
.
restore
()
}
// ─── 单个玩家画面渲染 ──────────────────────────────────────────────────────────
function
renderPlayer
(
state
,
offsetX
,
roomId
)
{
const
pid
=
state
.
playerId
??
1
const
frame
=
getPlayerFrameRect
()
const
contentScale
=
Math
.
min
(
frame
.
w
/
SCREEN_WIDTH
,
frame
.
h
/
SCREEN_HEIGHT
)
const
contentW
=
SCREEN_WIDTH
*
contentScale
const
contentH
=
SCREEN_HEIGHT
*
contentScale
const
contentX
=
frame
.
x
+
(
frame
.
w
-
contentW
)
/
2
const
contentY
=
frame
.
y
+
(
frame
.
h
-
contentH
)
/
2
// 从 grid 数据推算偶数行列数,动态调整泡泡大小
// 偶数行(row 0)的列数决定了整个网格的列配置
...
...
@@ -68,6 +138,14 @@ function renderPlayer(state, offsetX, roomId) {
ctx
.
save
()
ctx
.
translate
(
offsetX
,
0
)
drawPlayerFrameShell
(
frame
)
ctx
.
save
()
roundRectPath
(
frame
.
x
,
frame
.
y
,
frame
.
w
,
frame
.
h
,
frame
.
r
)
ctx
.
clip
()
ctx
.
translate
(
contentX
,
contentY
)
ctx
.
scale
(
contentScale
,
contentScale
)
drawPlayerGameBackground
()
// 泡泡网格
...
...
@@ -100,13 +178,17 @@ function renderPlayer(state, offsetX, roomId) {
drawGameInfo
(
ctx
,
state
.
score
??
0
,
state
.
isGameOver
??
false
,
roomId
??
state
.
roomId
??
''
,
state
.
nickname
??
''
)
ctx
.
restore
()
drawPlayerFrameBorder
(
frame
)
ctx
.
restore
()
}
// ─── 多玩家分隔线 ──────────────────────────────────────────────────────────────
function
drawDivider
(
x
)
{
ctx
.
save
()
const
grad
=
ctx
.
createLinearGradient
(
x
,
0
,
x
,
SCREEN_HEIGHT
)
const
top
=
PLAYER_FRAME_MARGIN_Y
+
26
const
bottom
=
SCREEN_HEIGHT
-
PLAYER_FRAME_MARGIN_Y
-
26
const
grad
=
ctx
.
createLinearGradient
(
x
,
top
,
x
,
bottom
)
grad
.
addColorStop
(
0
,
'rgba(139,92,246,0)'
)
grad
.
addColorStop
(
0.2
,
'rgba(139,92,246,0.5)'
)
grad
.
addColorStop
(
0.8
,
'rgba(139,92,246,0.5)'
)
...
...
@@ -115,8 +197,8 @@ function drawDivider(x) {
ctx
.
lineWidth
=
2
ctx
.
setLineDash
([
6
,
6
])
ctx
.
beginPath
()
ctx
.
moveTo
(
x
,
0
)
ctx
.
lineTo
(
x
,
SCREEN_HEIGHT
)
ctx
.
moveTo
(
x
,
top
)
ctx
.
lineTo
(
x
,
bottom
)
ctx
.
stroke
()
ctx
.
setLineDash
([])
ctx
.
restore
()
...
...
@@ -126,32 +208,36 @@ function drawDivider(x) {
function
drawVSDivider
(
x
)
{
ctx
.
save
()
// 发光效果
ctx
.
shadowColor
=
'rgba(251,191,36,0.5)'
ctx
.
shadowBlur
=
20
const
grad
=
ctx
.
createLinearGradient
(
x
,
0
,
x
,
SCREEN_HEIGHT
)
grad
.
addColorStop
(
0
,
'rgba(251,191,36,0)'
)
grad
.
addColorStop
(
0.15
,
'rgba(251,191,36,0.8)'
)
grad
.
addColorStop
(
0.5
,
'rgba(251,191,36,1)'
)
grad
.
addColorStop
(
0.85
,
'rgba(251,191,36,0.8)'
)
grad
.
addColorStop
(
1
,
'rgba(251,191,36,0)'
)
ctx
.
strokeStyle
=
grad
ctx
.
lineWidth
=
3
ctx
.
beginPath
()
ctx
.
moveTo
(
x
,
SCREEN_HEIGHT
*
0.15
)
ctx
.
lineTo
(
x
,
SCREEN_HEIGHT
*
0.85
)
const
centerY
=
SCREEN_HEIGHT
/
2
const
pillW
=
58
const
pillH
=
70
const
pillX
=
x
-
pillW
/
2
const
pillY
=
centerY
-
pillH
/
2
ctx
.
fillStyle
=
'rgba(12,18,36,0.82)'
ctx
.
strokeStyle
=
'rgba(103,232,249,0.9)'
ctx
.
lineWidth
=
2
ctx
.
shadowColor
=
'rgba(34,211,238,0.35)'
ctx
.
shadowBlur
=
18
roundRectPath
(
pillX
,
pillY
,
pillW
,
pillH
,
16
)
ctx
.
fill
()
ctx
.
stroke
()
ctx
.
shadowBlur
=
0
ctx
.
fillStyle
=
'rgba(125,211,252,0.95)'
ctx
.
fillRect
(
pillX
+
8
,
pillY
+
8
,
pillW
-
16
,
3
)
// VS 文字
// V
|
S 文字
ctx
.
textAlign
=
'center'
ctx
.
textBaseline
=
'middle'
ctx
.
font
=
'bold 48px Arial'
ctx
.
fillStyle
=
'rgba(251,191,36,0.9)'
ctx
.
shadowColor
=
'rgba(251,191,36,0.6)'
ctx
.
shadowBlur
=
15
ctx
.
fillText
(
'VS'
,
x
,
SCREEN_HEIGHT
/
2
)
ctx
.
font
=
'bold 44px Arial'
ctx
.
fillStyle
=
'#F8FAFC'
ctx
.
strokeStyle
=
'rgba(8,15,34,0.9)'
ctx
.
lineWidth
=
4
ctx
.
shadowColor
=
'rgba(103,232,249,0.65)'
ctx
.
shadowBlur
=
16
ctx
.
strokeText
(
'V|S'
,
x
,
centerY
+
1
)
ctx
.
fillText
(
'V|S'
,
x
,
centerY
)
ctx
.
shadowBlur
=
0
ctx
.
restore
()
}
...
...
@@ -212,6 +298,9 @@ function loop() {
const
roomId
=
getCurrentRoom
()
const
connStatus
=
getConnectionStatus
()
const
playerCount
=
states
.
length
||
1
const
totalWidth
=
SCREEN_WIDTH
*
Math
.
max
(
playerCount
,
1
)
const
roomTimer
=
getRoomTimer
()
let
allGameOver
=
false
// 从任意一个玩家的 state 中提取屏幕比例和安全区域,在渲染前统一配置
if
(
states
.
length
>
0
&&
states
[
0
].
screenRatio
)
{
...
...
@@ -245,7 +334,7 @@ function loop() {
ctx
.
restore
()
if
(
states
.
length
>
0
)
{
const
allGameOver
=
states
.
every
(
s
=>
s
.
isGameOver
)
allGameOver
=
states
.
every
(
s
=>
s
.
isGameOver
)
if
(
allGameOver
&&
states
.
length
>
1
)
{
// 最终结果页使用干净背景,不再保留各玩家游戏盘面
...
...
@@ -259,41 +348,45 @@ function loop() {
}
else
{
// ── 按队伍分组渲染:A队左,B队右 ────────────────────────────────────────
let
currentOffsetX
=
0
const
dividerXs
=
[]
const
hasBothTeams
=
teamAStates
.
length
>
0
&&
teamBStates
.
length
>
0
// 渲染A队玩家
teamAStates
.
forEach
((
state
,
idx
)
=>
{
const
offsetX
=
currentOffsetX
// 分隔线(A队内部)
if
(
idx
>
0
)
d
rawDivider
(
offsetX
)
//
记录
分隔线(A队内部)
if
(
idx
>
0
)
d
ividerXs
.
push
(
offsetX
)
renderPlayer
(
state
,
offsetX
,
roomId
)
currentOffsetX
+=
SCREEN_WIDTH
})
// 绘制 VS 分隔线(A队和B队之间)
if
(
teamAStates
.
length
>
0
&&
teamBStates
.
length
>
0
)
{
drawVSDivider
(
currentOffsetX
)
}
// 渲染B队玩家
teamBStates
.
forEach
((
state
,
idx
)
=>
{
const
offsetX
=
currentOffsetX
// 分隔线(B队内部)
if
(
idx
>
0
)
d
rawDivider
(
offsetX
)
//
记录
分隔线(B队内部)
if
(
idx
>
0
)
d
ividerXs
.
push
(
offsetX
)
renderPlayer
(
state
,
offsetX
,
roomId
)
currentOffsetX
+=
SCREEN_WIDTH
})
dividerXs
.
forEach
(
drawDivider
)
if
(
hasBothTeams
)
drawVSDivider
(
totalWidth
/
2
)
}
}
else
{
// ── 空闲等待画面 ──────────────────────────────────────────────────────
drawIdleScreen
(
ctx
,
SCREEN_WIDTH
,
SCREEN_HEIGHT
,
roomId
,
connStatus
,
SCREEN_NAME
,
frameCount
)
}
if
(
states
.
length
>
0
&&
!
allGameOver
&&
roomTimer
.
active
)
{
drawRoomTimerCard
(
ctx
,
roomTimer
.
remainingSec
,
totalWidth
)
}
// ── 倒计时覆盖层(服务端驱动,所有端同步)──────────────────────────────
drawCountdownOverlay
(
SCREEN_WIDTH
*
totalSlots
)
...
...
big-screen/src/renderer/gameinfo.js
浏览文件 @
9a27f2a4
...
...
@@ -6,6 +6,15 @@
*/
import
{
SCREEN_WIDTH
,
SCREEN_HEIGHT
,
SAFE_AREA_TOP
}
from
'../constants.js'
const
TEAM_NAME_MAP
=
{
A
:
'深耕队'
,
B
:
'致远队'
,
}
function
getTeamDisplayName
(
team
)
{
return
TEAM_NAME_MAP
[
team
]
||
team
}
// ─── 工具 ─────────────────────────────────────────────────────────────────────
function
roundRectPath
(
ctx
,
x
,
y
,
w
,
h
,
r
)
{
...
...
@@ -154,6 +163,69 @@ function drawRoomCard(ctx, roomId) {
ctx
.
fillText
(
label
,
cx
,
boxY
+
38
)
}
function
formatRemainTime
(
remainingSec
)
{
const
safeSec
=
Math
.
max
(
0
,
Math
.
floor
(
remainingSec
||
0
))
const
minutes
=
String
(
Math
.
floor
(
safeSec
/
60
)).
padStart
(
2
,
'0'
)
const
seconds
=
String
(
safeSec
%
60
).
padStart
(
2
,
'0'
)
return
`
${
minutes
}
:
${
seconds
}
`
}
export
function
drawRoomTimerCard
(
ctx
,
remainingSec
,
totalWidth
=
SCREEN_WIDTH
)
{
const
label
=
formatRemainTime
(
remainingSec
)
const
boxY
=
SAFE_AREA_TOP
+
8
const
boxH
=
44
const
r
=
10
ctx
.
save
()
ctx
.
font
=
'bold 22px Arial'
const
timeW
=
ctx
.
measureText
(
label
).
width
ctx
.
font
=
'bold 11px Arial'
const
titleW
=
ctx
.
measureText
(
'时间'
).
width
const
boxW
=
Math
.
max
(
timeW
,
titleW
)
+
34
const
boxX
=
totalWidth
/
2
-
boxW
/
2
ctx
.
shadowColor
=
'rgba(0,0,0,0.45)'
ctx
.
shadowBlur
=
10
ctx
.
shadowOffsetY
=
4
const
bg
=
ctx
.
createLinearGradient
(
boxX
,
boxY
,
boxX
,
boxY
+
boxH
)
bg
.
addColorStop
(
0
,
'rgba(139,92,246,0.92)'
)
bg
.
addColorStop
(
1
,
'rgba(124,58,237,0.97)'
)
ctx
.
fillStyle
=
bg
roundRectPath
(
ctx
,
boxX
,
boxY
,
boxW
,
boxH
,
r
)
ctx
.
fill
()
ctx
.
shadowBlur
=
0
const
hl
=
ctx
.
createLinearGradient
(
boxX
,
boxY
,
boxX
+
boxW
,
boxY
)
hl
.
addColorStop
(
0
,
'rgba(167,139,250,0.55)'
)
hl
.
addColorStop
(
0.5
,
'rgba(196,181,253,0.88)'
)
hl
.
addColorStop
(
1
,
'rgba(167,139,250,0.55)'
)
ctx
.
fillStyle
=
hl
ctx
.
fillRect
(
boxX
+
4
,
boxY
+
4
,
boxW
-
8
,
3
)
ctx
.
strokeStyle
=
'rgba(167,139,250,0.55)'
ctx
.
lineWidth
=
1.5
roundRectPath
(
ctx
,
boxX
,
boxY
,
boxW
,
boxH
,
r
)
ctx
.
stroke
()
const
cx
=
boxX
+
boxW
/
2
ctx
.
textAlign
=
'center'
ctx
.
textBaseline
=
'alphabetic'
ctx
.
font
=
'bold 11px Arial'
ctx
.
fillStyle
=
'rgba(221,214,254,0.95)'
ctx
.
fillText
(
'时间'
,
cx
,
boxY
+
18
)
ctx
.
font
=
'bold 22px Arial'
ctx
.
fillStyle
=
'rgba(0,0,0,0.32)'
ctx
.
fillText
(
label
,
cx
+
1
,
boxY
+
39
)
const
tg
=
ctx
.
createLinearGradient
(
cx
-
35
,
0
,
cx
+
35
,
0
)
tg
.
addColorStop
(
0
,
'#FDE68A'
)
tg
.
addColorStop
(
0.5
,
'#FCD34D'
)
tg
.
addColorStop
(
1
,
'#F59E0B'
)
ctx
.
fillStyle
=
tg
ctx
.
fillText
(
label
,
cx
,
boxY
+
38
)
ctx
.
restore
()
}
// ─── 游戏结束大卡片 ───────────────────────────────────────────────────────────
function
getStarCount
(
score
)
{
...
...
@@ -362,7 +434,7 @@ export function drawTeamResultOverlay(ctx, playerStates, getPlayerTeam, totalWid
ctx
.
textAlign
=
'center'
ctx
.
textBaseline
=
'middle'
ctx
.
font
=
'bold 42px Arial'
const
titleText
=
isDraw
?
'平局!'
:
`
${
winner
}
队
胜利!`
const
titleText
=
isDraw
?
'平局!'
:
`
${
getTeamDisplayName
(
winner
)}
胜利!`
const
titleColor
=
isDraw
?
'#FCD34D'
:
winner
===
'A'
?
'#8B5CF6'
:
'#EC4899'
ctx
.
shadowColor
=
titleColor
ctx
.
shadowBlur
=
25
...
...
@@ -485,7 +557,7 @@ function drawTeamScoreBig(ctx, x, y, team, score, isWinner) {
ctx
.
textBaseline
=
'middle'
ctx
.
font
=
'bold 18px Arial'
ctx
.
fillStyle
=
isWinner
?
color
:
'rgba(150,150,150,0.6)'
ctx
.
fillText
(
`
${
team
}
队`
,
x
,
y
-
28
)
ctx
.
fillText
(
getTeamDisplayName
(
team
)
,
x
,
y
-
28
)
// 分数(胜利方更大更亮,失败方灰色暗淡)
ctx
.
font
=
isWinner
?
'bold 56px Arial'
:
'bold 42px Arial'
...
...
@@ -520,7 +592,7 @@ function drawTeamPlayerList(ctx, x, y, w, h, team, players, isWinner) {
ctx
.
textBaseline
=
'middle'
ctx
.
font
=
'bold 16px Arial'
ctx
.
fillStyle
=
displayColor
ctx
.
fillText
(
`
${
team
}
队
成员`
,
x
+
w
/
2
,
y
+
12
)
ctx
.
fillText
(
`
${
getTeamDisplayName
(
team
)}
成员`
,
x
+
w
/
2
,
y
+
12
)
// 分割线
ctx
.
strokeStyle
=
isWinner
?
color
+
'40'
:
'rgba(150,150,150,0.2)'
...
...
big-screen/src/socket.js
浏览文件 @
9a27f2a4
...
...
@@ -5,7 +5,7 @@
* 发送 → JSON.stringify({ event: string, data: any })
* 接收 ← JSON.stringify({ event: string, data: any })
*/
import
{
setCurrentRoom
,
setGameState
,
getPlayerState
,
clearGameState
,
setPlayerGameOver
,
setAllGameOver
,
setPlayerTeam
,
setCountdown
,
clearCountdown
}
from
'./stateManager.js'
import
{
setCurrentRoom
,
setGameState
,
getPlayerState
,
clearGameState
,
setPlayerGameOver
,
setAllGameOver
,
setPlayerTeam
,
setCountdown
,
clearCountdown
,
setRoomTimer
,
clearRoomTimer
}
from
'./stateManager.js'
/**
* WebSocket 服务器地址配置
...
...
@@ -109,6 +109,8 @@ function _dispatch(event, data) {
case
'screen:roomChanged'
:
{
const
roomId
=
data
?.
roomId
??
null
console
.
log
(
'[Socket] screen:roomChanged, roomId:'
,
roomId
)
clearCountdown
()
clearRoomTimer
()
setCurrentRoom
(
roomId
)
if
(
!
roomId
)
clearGameState
()
break
...
...
@@ -186,6 +188,7 @@ function _dispatch(event, data) {
case
'room:gameStart'
:
{
console
.
log
(
'[Socket] room:gameStart'
)
clearCountdown
()
setRoomTimer
(
data
?.
gameDuration
??
0
)
break
}
...
...
@@ -195,6 +198,7 @@ function _dispatch(event, data) {
*/
case
'room:timeUp'
:
{
console
.
log
(
'[Socket] room:timeUp,所有玩家游戏结束'
)
clearRoomTimer
()
setAllGameOver
()
break
}
...
...
big-screen/src/stateManager.js
浏览文件 @
9a27f2a4
...
...
@@ -14,6 +14,9 @@ const playerTeams = new Map()
/** 倒计时状态 */
let
countdownState
=
{
active
:
false
,
value
:
0
}
/** 游戏进行中的房间计时状态 */
let
roomTimerState
=
{
active
:
false
,
durationSec
:
0
,
startAtMs
:
0
}
export
function
setCurrentRoom
(
roomId
)
{
currentRoomId
=
roomId
}
...
...
@@ -129,8 +132,50 @@ export function getCountdown() {
return
countdownState
}
/**
* 设置房间游戏计时
*/
export
function
setRoomTimer
(
durationSec
,
startAtMs
=
Date
.
now
())
{
const
safeDuration
=
Number
(
durationSec
)
||
0
if
(
safeDuration
<=
0
)
{
roomTimerState
=
{
active
:
false
,
durationSec
:
0
,
startAtMs
:
0
}
return
}
roomTimerState
=
{
active
:
true
,
durationSec
:
safeDuration
,
startAtMs
,
}
}
/**
* 清除房间游戏计时
*/
export
function
clearRoomTimer
()
{
roomTimerState
=
{
active
:
false
,
durationSec
:
0
,
startAtMs
:
0
}
}
/**
* 获取当前房间剩余时间
*/
export
function
getRoomTimer
(
now
=
Date
.
now
())
{
if
(
!
roomTimerState
.
active
||
roomTimerState
.
durationSec
<=
0
)
{
return
{
active
:
false
,
remainingSec
:
0
,
durationSec
:
0
}
}
const
elapsedSec
=
Math
.
max
(
0
,
Math
.
floor
((
now
-
roomTimerState
.
startAtMs
)
/
1000
))
const
remainingSec
=
Math
.
max
(
0
,
roomTimerState
.
durationSec
-
elapsedSec
)
return
{
active
:
true
,
durationSec
:
roomTimerState
.
durationSec
,
remainingSec
,
}
}
export
function
clearGameState
()
{
playerStates
.
clear
()
playerTeams
.
clear
()
countdownState
=
{
active
:
false
,
value
:
0
}
roomTimerState
=
{
active
:
false
,
durationSec
:
0
,
startAtMs
:
0
}
}
server/src/socket/roomHandler.js
浏览文件 @
9a27f2a4
const
prisma
=
require
(
'../prisma/client'
);
const
TEAM_NAME_MAP
=
{
A
:
'深耕队'
,
B
:
'致远队'
,
};
function
getTeamDisplayName
(
team
)
{
return
TEAM_NAME_MAP
[
team
]
||
team
;
}
/**
* 内存等待表:roomId → { totalSeats, joined: Set<ws>, players: Array }
* 房间满员或游戏开始后从表中移除
...
...
@@ -102,7 +111,7 @@ function registerRoomHandlers(ws, { broadcastToRoom, joinRoom, leaveAllRooms, ro
ws
.
ctx
.
team
=
playerInfo
.
team
;
roomPlayerCounter
.
set
(
rid
,
1
);
console
.
log
(
`[Room] 创建房间
${
rid
}
,总座位:
${
seats
}
,房主:
${
playerInfo
.
nickname
}
(
${
playerInfo
.
team
}
队
)`
);
console
.
log
(
`[Room] 创建房间
${
rid
}
,总座位:
${
seats
}
,房主:
${
playerInfo
.
nickname
}
(
${
getTeamDisplayName
(
playerInfo
.
team
)}
)`
);
ws
.
sendEvent
(
'room:created'
,
{
roomId
:
rid
,
totalSeats
:
seats
,
...
...
@@ -172,10 +181,10 @@ function registerRoomHandlers(ws, { broadcastToRoom, joinRoom, leaveAllRooms, ro
const
teamBCount
=
waiting
.
players
.
filter
(
p
=>
p
.
team
===
'B'
).
length
;
if
(
finalTeam
===
'A'
&&
teamACount
>=
perTeamSeats
)
{
ws
.
sendEvent
(
'error'
,
{
message
:
'A队已满,请选择其他队伍'
,
code
:
'TEAM_FULL'
});
ws
.
sendEvent
(
'error'
,
{
message
:
`
${
getTeamDisplayName
(
'A'
)}
已满,请选择其他队伍`
,
code
:
'TEAM_FULL'
});
return
;
}
else
if
(
finalTeam
===
'B'
&&
teamBCount
>=
perTeamSeats
)
{
ws
.
sendEvent
(
'error'
,
{
message
:
'B队已满,请选择其他队伍'
,
code
:
'TEAM_FULL'
});
ws
.
sendEvent
(
'error'
,
{
message
:
`
${
getTeamDisplayName
(
'B'
)}
已满,请选择其他队伍`
,
code
:
'TEAM_FULL'
});
return
;
}
...
...
@@ -203,7 +212,7 @@ function registerRoomHandlers(ws, { broadcastToRoom, joinRoom, leaveAllRooms, ro
const
joinedCount
=
waiting
.
joined
.
size
;
const
{
totalSeats
,
players
}
=
waiting
;
console
.
log
(
`[Room] 玩家加入房间
${
rid
}
,playerId=
${
nextId
}
,team=
${
playerInfo
.
team
}
,当前
${
joinedCount
}
/
${
totalSeats
}
`
);
console
.
log
(
`[Room] 玩家加入房间
${
rid
}
,playerId=
${
nextId
}
,team=
${
getTeamDisplayName
(
playerInfo
.
team
)
}
,当前
${
joinedCount
}
/
${
totalSeats
}
`
);
// 通知自己加入成功(含最终分配的队伍,可能因满员被调整)
ws
.
sendEvent
(
'room:joined'
,
{
roomId
:
rid
,
joinedCount
,
totalSeats
,
playerId
:
nextId
,
players
,
myPlayerId
:
nextId
,
team
:
finalTeam
});
...
...
编写
预览
Markdown
格式
0%
重试
或
添加新文件
添加附件
取消
您添加了
0
人
到此讨论。请谨慎行事。
请先完成此评论的编辑!
取消
请
注册
或者
登录
后发表评论