Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
- 音效与特效:出牌、不出、炸弹、王炸、同花顺等有局内反馈,可静音。
- 积分系统:可选 MySQL 持久化,斗地主和掼蛋积分榜独立统计。
- Discuz SSO:可选 JWT 单点登录,支持同步 Discuz 用户名和头像。
- 登录分支:默认首页保留 Discuz 登录/访客入口;普通玩家可访问 `/?auth=guest` 直接输入用户名进入。
- 移动端:大厅可响应式使用;牌桌在手机端要求横屏。

## 快速开始
Expand Down
3 changes: 2 additions & 1 deletion config.example.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,5 +10,6 @@
"DB_TABLE_PREFIX": "pre_",
"DB_DISABLE": 0,
"SCORE_BASE": 1,
"DISCUZ_AVATAR_BASE": "https://zwwx.club/uc_server/avatar.php?uid={uid}&size=middle"
"DISCUZ_AVATAR_BASE": "https://zwwx.club/uc_server/avatar.php?uid={uid}&size=middle",
"_LOGIN_MODES": "默认首页保留 Discuz 登录/游客。面向普通玩家可使用 /?auth=guest 直接显示游客用户名输入。"
}
41 changes: 37 additions & 4 deletions server.js
Original file line number Diff line number Diff line change
Expand Up @@ -276,6 +276,33 @@ function getGuandanCandidates(game, posId, leadOnly) {
});
}


function evaluateDoudizhuHand(cards) {
const groups = groupCardsByValue(cards || []);
const values = Object.keys(groups).map(Number);
let power = 0;
values.forEach(v => {
const n = groups[v].length;
if (v >= 16) power += 4;
else if (v === 15) power += 3 * n;
else if (v >= 13) power += 1.5 * n;
if (n >= 4) power += 7;
else if (n === 3) power += 2;
});
if (groups[16] && groups[17]) power += 8;
return power;
}

function shouldCallDoudizhuScore(cards, ctxScore) {
if (!ctxScore || !ctxScore.length) return 0;
const maxScore = Math.max.apply(null, ctxScore);
const power = evaluateDoudizhuHand(cards);
if (power >= 22 && ctxScore.indexOf(3) >= 0) return 3;
if (power >= 16 && ctxScore.indexOf(2) >= 0) return 2;
if (power >= 11 && ctxScore.indexOf(1) >= 0) return 1;
return (power >= 20 && maxScore) ? maxScore : 0;
}

function time() {
return (new Date()).toLocaleTimeString();
}
Expand Down Expand Up @@ -718,9 +745,10 @@ const proto = {
const game = this.gameDatas[deskId];
if (!game) return;
const ctxScore = game.getContextScore() || [];
let score = 0;
if (Math.random() < 0.65 && ctxScore.length) {
score = ctxScore[Math.floor(Math.random() * ctxScore.length)];
const hand = game.getCardsByPosId(posId) || [];
let score = shouldCallDoudizhuScore(hand, ctxScore);
if (!score && Math.random() < 0.12 && ctxScore.length) {
score = ctxScore[0];
}
const status = game.next(posId, score).getStatus();
if (status == 1) {
Expand Down Expand Up @@ -757,7 +785,12 @@ const proto = {
len: last.len, key: last.key, type: last.type, ctxPos: 'other'
};
let picks = [];
try { picks = (AISuggest.suggest(hand, lastInfo)) || []; } catch (e) { picks = []; }
const allOut = game.validate(posId, handRaw);
if (allOut && allOut.status) {
picks = handRaw.map(c => ({ value: c.value, type: c.type }));
} else {
try { picks = (AISuggest.suggest(hand, lastInfo)) || []; } catch (e) { picks = []; }
}
// 解析为真实牌实例(按下标占用避免重复)
const used = new Set();
let data = [];
Expand Down
27 changes: 18 additions & 9 deletions static/css/style.css
Original file line number Diff line number Diff line change
Expand Up @@ -1781,6 +1781,10 @@ button.btn-ghost {
font-size: 13px;
color: var(--jade-100);
}
.guandan-teams {
display: flex;
flex-direction: column;
}
.guandan-team-row span {
display: block;
max-width: 150px;
Expand Down Expand Up @@ -1842,10 +1846,15 @@ button.btn-ghost {
}
.room-guandan .player-top {
position: fixed;
left: calc(50vw - 400px);
top: calc(var(--table-top-safe) + var(--card-h) + 38px);
transform: scale(0.76);
transform-origin: top left;
left: 50%;
top: calc(var(--table-top-safe) + var(--card-h) + 30px);
transform: translateX(-50%) scale(0.92);
transform-origin: top center;
z-index: 28;
}
.room-guandan .player-top .team-tag {
top: -8px;
right: -14px;
}
.room-guandan .player-right {
right: calc(var(--table-right-safe) - 124px);
Expand Down Expand Up @@ -1990,11 +1999,11 @@ button.btn-ghost {
.player-right { right: calc(var(--table-right-safe) - 118px); top: calc(50vh - 58px); }
.player-top { top: calc(var(--table-top-safe) + var(--card-h) + 22px); }
.room-guandan .player-top {
display: none;
left: max(190px, calc(50vw - 315px));
top: calc(var(--table-top-safe) + var(--card-h) + 26px);
transform: scale(0.68);
transform-origin: top left;
display: block;
left: 50%;
top: calc(var(--table-top-safe) + var(--card-h) + 22px);
transform: translateX(-50%) scale(0.78);
transform-origin: top center;
}
.icon-group-left {
top: 20px;
Expand Down
13 changes: 7 additions & 6 deletions static/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no">
<meta name="viewport" content="width=device-width, initial-scale=1.0, minimum-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<meta name="theme-color" content="#0e2422">
<title>雀阁 · 纸牌房</title>
Expand All @@ -22,13 +22,13 @@
<div class="row title">
雀阁
</div>
<template v-if="isAutoLogin">
<template v-if="authMode === 'discuz' && isAutoLogin">
<div class="auto-login">正在使用 ZWWX.CLUB 登录,请稍候…</div>
<div class="row" style="margin-top:14px;text-align:center;">
<a href="javascript:;" @click="cancelAutoLogin()" style="color:#888;font-size:12px;">迟迟无法进入?点此取消并改用其他方式</a>
</div>
</template>
<template v-else-if="!guestMode">
<template v-else-if="authMode === 'discuz' && !guestMode">
<div v-if="loginError" class="login-error" style="margin:8px 0 12px;padding:10px 12px;background:#3b1f1f;border:1px solid #8a3a3a;color:#ffb3b3;border-radius:6px;font-size:13px;line-height:1.5;text-align:left;">
<div style="font-weight:bold;margin-bottom:4px;">登录失败</div>
<div>{{loginError}}</div>
Expand All @@ -45,7 +45,7 @@
@click="login">入 座</button>
</div>
<div class="row" style="margin-top:8px;text-align:center;">
<a href="javascript:;" @click="guestMode = false" style="color:#888;font-size:12px;">返回登录方式选择</a>
<a v-if="authMode === 'discuz'" href="javascript:;" @click="guestMode = false" style="color:#888;font-size:12px;">返回登录方式选择</a>
</div>
</template>
</div>
Expand Down Expand Up @@ -492,7 +492,8 @@ <h1>{{gameTypeName(selectedGameType)}}大厅</h1>

where: 0,//0登录界面 1大厅 2房间,
isAutoLogin: false,
guestMode: false,// 选择以访客身份进入
authMode: getURLParam('auth') === 'guest' ? 'guest' : 'discuz',// 登录分支:discuz=论坛/访客,guest=普通游客
guestMode: getURLParam('auth') === 'guest',// 选择以访客身份进入
loginError: '',//登录错误持久横幅
debugMode: /[?&]debug=1\b/.test(location.search),//?debug=1 显示调试信息
tokenSnippet: '',//最近一次握手用的 token 前后段(仅在 debug 显示)
Expand Down Expand Up @@ -602,7 +603,7 @@ <h1>{{gameTypeName(selectedGameType)}}大厅</h1>
});

// if Discuz token exists, mark as auto-login and the server will validate it
var _token = _getDiscuzToken();
var _token = this.authMode === 'discuz' ? _getDiscuzToken() : '';
if (_token) {
this.isAutoLogin = true;
// debug 模式记录 token 片段
Expand Down