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 个修改的文件
包含
138 行增加
和
8 行删除
+138
-8
main.js
big-screen/src/main.js
+0
-0
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
差异被折叠。
点击展开。
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
人
到此讨论。请谨慎行事。
请先完成此评论的编辑!
取消
请
注册
或者
登录
后发表评论