二七王:新增根目录 shared 同步脚本与前端只读副本

同步是跨端动作(源在 server、目标在 client),脚本放仓库根目录。
只提供服务端→前端一个方向;前端副本只读,手改会被覆盖。
CLAUDE.md 常用命令一节同步收录。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-08-26 19:44:00 +08:00
co-authored by Claude Opus 5
parent 9f32af7d72
commit d287ce5c8c
4 changed files with 279 additions and 1 deletions
+8 -1
View File
@@ -12,13 +12,20 @@
## 常用命令
没有配置任何构建/lint/测试工具。仓库里唯一的脚本:
没有配置任何构建/lint/测试工具。仓库里的脚本:
```bash
# 根据 client/assets/spine/*.json 重新生成 client/generated/spine_assets.js 与 spine_data.js
client/scripts/build_spine_data.cmd # 内部调用 build_spine_data.ps1
```
```bash
# 把服务端共享算法同步到前端只读副本(改动一律落在服务端 shared/,改完跑一次)
sync_shared.cmd
```
`server/games/erqiwang/shared/` 是前后端共享算法的**唯一可修改处**;`client/js/01_SubGame/codes/shared/` 是它的只读副本,手改会被下次同步覆盖,且 `client/tests/test_shared_sync.js` 会因字节不一致而失败。
在 `client/assets/spine/` 增删 Spine 导出文件后运行;不要手改这两个生成文件。
查看客户端效果需用 HTTP 方式(而非 `file://`)打开 `client/index.html`,例如 `npx http-server client -p 8080`。
+250
View File
@@ -0,0 +1,250 @@
///////////////////////////////////////////////////////////////
////// youle_erqiwang_shared_cards: 前后端共享牌值算法 //////////
///////////////////////////////////////////////////////////////
// 【权威源】本文件是前后端共享算法的唯一可修改处。
// 前端副本 client/js/01_SubGame/codes/shared/cards.js 由仓库根目录的
// sync_shared.cmd 单向同步生成,为只读;任何改动都必须落在本文件,
// 改完跑一次 sync_shared.cmd,否则 client/tests/test_shared_sync.js 会红。
//
// 内容:牌值编码与牌型分组的纯函数,不依赖对局状态、不依赖平台 API。
// 牌编码区间约定(id_to_code 的产物,各处判定据此):
// 9554 大王 / 9553 小王 / 8000–9000 正7 / 7000–8000 副7
// 3000–4000 正2 / 2000–3000 副2 / 1000–2000 主花色普通牌 / 100–500 副牌普通牌
// ⇒ 主牌 ⟺ code > 1000
var youle_erqiwang_shared_cards = youle_erqiwang_shared_cards || {
//牌id转花色
id_to_flower: function(cardid){
if (cardid == 52 || cardid == 106){
//小王
return 5;
}
if (cardid == 53 || cardid == 107){
//大王
return 5;
}
return parseInt((cardid % 54) / 13) + 1;
},
//牌id转牌面数值
id_to_number: function(cardid){
if (cardid == 52 || cardid == 106){
//小王
return 53;
}
if (cardid == 53 || cardid == 107){
//大王
return 54;
}
return (cardid % 54) % 13 + 1;
},
//牌id转牌编码
id_to_code: function(mainflower, cardid){
if (cardid == 52 || cardid == 106){
//小王
return 9553;
}
if (cardid == 53 || cardid == 107){
//大王
return 9554;
}
var flower = youle_erqiwang_shared_cards.id_to_flower(cardid);
var number = youle_erqiwang_shared_cards.id_to_number(cardid);
var code = flower * 100 + number;
//特殊处理
switch (number)
{
case 1:
code = code + 13;
break;
case 2:
code = code + 2000;
break;
case 7:
code = code + 7000;
break;
}
//主牌花色处理
if (flower == mainflower){
code = code + 1000;
}
return code;
},
//从大到小排序(冒泡排序)
order_cards: function(mainflower, aryCardIDs){
if (aryCardIDs){
for (var i = 0; i < aryCardIDs.length; i++) {
for (var j = i + 1; j < aryCardIDs.length; j++) {
var i_code = youle_erqiwang_shared_cards.id_to_code(mainflower, aryCardIDs[i]);
var j_code = youle_erqiwang_shared_cards.id_to_code(mainflower, aryCardIDs[j]);
if (i_code < j_code) {
var tmp = aryCardIDs[j];
aryCardIDs[j] = aryCardIDs[i];
aryCardIDs[i] = tmp;
}
}
}
}
return aryCardIDs;
},
//判断两个牌是否是连续的
is_continuous: function(bigcode, smallcode){
//是小王
var is_xiaowang = function(_code){
if (_code == 9553){
return true;
}
return false;
}
//是正7
var is_zheng7 = function(_code){
if (_code > 8000 && _code < 9000){
return true;
}
return false;
}
//是负7
var is_fu7 = function(_code){
if (_code > 7000 && _code < 8000){
return true;
}
return false;
}
//是正2
var is_zheng2 = function(_code){
if (_code > 3000 && _code < 4000){
return true;
}
return false;
}
//是负2
var is_fu2 = function(_code){
if (_code > 2000 && _code < 3000){
return true;
}
return false;
}
//是主A
var is_zhuA = function(_code){
if (_code > 1000 && _code < 2000 && _code % 100 == 14){
return true;
}
return false;
}
//是8
var is_8 = function(_code){
if (_code % 100 == 8){
return true;
}
return false;
}
//是6
var is_6 = function(_code){
if (_code % 100 == 6){
return true;
}
return false;
}
//普通的连牌
if (bigcode - smallcode == 1){
return true;
}
//小王、正7
if (is_xiaowang(bigcode) && is_zheng7(smallcode)){
return true;
}
//正7、负7
if (is_zheng7(bigcode) && is_fu7(smallcode)){
return true;
}
//负7、正2
if (is_fu7(bigcode) && is_zheng2(smallcode)){
return true;
}
//正2、负2
if (is_zheng2(bigcode) && is_fu2(smallcode)){
return true;
}
//负2、主A
if (is_fu2(bigcode) && is_zhuA(smallcode)){
return true;
}
//8、6(design §5.3:7 不与 6/8 相连,6 与 8 相连)
//必须是同一花色的 8 与 6:牌编码同花色相差 2(主花色 1408-1406=2、副牌 308-306=2),
//跨花色则相差 100 的倍数。只判 %100 会把「♥8 + ♣6」误判为连对,
//进而在 get_chongguan 扫描整手牌时让副牌的 6 对错误接续主 8、多算奖(design §8.1 连对链)
if (is_8(bigcode) && is_6(smallcode) && (bigcode - smallcode == 2)){
return true;
}
return false;
},
//获取对子列表,cards为同一花色的排过序的牌列表
get_pairlist: function(mainflower, flowercards){
var pairlist = [];
var i = 0;
while (i < flowercards.length - 1){
//牌编码,两张牌两张牌一取
var code_i = youle_erqiwang_shared_cards.id_to_code(mainflower, flowercards[i]);
var code_j = youle_erqiwang_shared_cards.id_to_code(mainflower, flowercards[i + 1]);
if (code_i == code_j){
pairlist.push([flowercards[i], flowercards[i + 1]]);
i = i + 2;
} else {
i++;
}
}
return pairlist;
},
//获取拖拉机列表,pairlist为同一花色的排过序的对子列表,cardtype为拖拉机牌型
get_tuolaji_list: function(mainflower, pairlist, cardtype){
var tuolaji = [];
var count = cardtype - 300; //拖拉机的对子数量
var i = 0;
for (var i = 0; i < pairlist.length - count + 1; i++){
//每次按拖拉机的对子数量取N对
var tmplist = pairlist.slice(i, i + count);
//判断是否是cardtype类型的拖拉机
var is_tlj = true;
for (var j = 0; j < tmplist.length - 1; j++) {
var code_j1 = youle_erqiwang_shared_cards.id_to_code(mainflower, tmplist[j][0]);
var code_j2 = youle_erqiwang_shared_cards.id_to_code(mainflower, tmplist[j+1][0]);
if (!youle_erqiwang_shared_cards.is_continuous(code_j1, code_j2)){
is_tlj = false;
break;
}
}
if (is_tlj){
var tlj = [];
for (var k = 0; k < tmplist.length; k++){
tlj = tlj.concat(tmplist[k]);
}
tuolaji.push(tlj);
}
}
return tuolaji;
},
//主牌归一化大小(副7 同级取 7000、副2 同级取 2000,其余用牌编码),仅用于甩牌分量比大小
trump_rank: function(mainflower, cardid){
var code = youle_erqiwang_shared_cards.id_to_code(mainflower, cardid);
if (code > 7000 && code < 8000){ //副7
return 7000;
}
if (code > 2000 && code < 3000){ //副2
return 2000;
}
return code;
},
}
//Node 导出;友乐/浏览器无 module 时跳过(dev-guide 01 §1)
if (typeof module !== "undefined"){
module.exports = youle_erqiwang_shared_cards;
}
+3
View File
@@ -0,0 +1,3 @@
@echo off
REM 把服务端 shared/ 单向同步到前端只读副本。改动一律落在服务端,改完跑本脚本。
powershell -NoProfile -ExecutionPolicy Bypass -File "%~dp0sync_shared.ps1"
+18
View File
@@ -0,0 +1,18 @@
# server/games/erqiwang/shared/ -> client/js/01_SubGame/codes/shared/ 单向同步
# 【只有这一个方向】权威源在服务端,前端副本只读;前端的本地改动会被本脚本覆盖。
$ErrorActionPreference = 'Stop'
$root = Split-Path -Parent $MyInvocation.MyCommand.Path
$src = Join-Path $root 'server\games\erqiwang\shared'
$dst = Join-Path $root 'client\js\01_SubGame\codes\shared'
if (-not (Test-Path $src)) { Write-Error "源目录不存在: $src"; exit 1 }
if (-not (Test-Path $dst)) { New-Item -ItemType Directory -Path $dst -Force | Out-Null }
$files = Get-ChildItem -Path $src -Filter *.js -File
if ($files.Count -eq 0) { Write-Error "源目录没有 .js 文件: $src"; exit 1 }
foreach ($f in $files) {
Copy-Item -Path $f.FullName -Destination (Join-Path $dst $f.Name) -Force
Write-Host ("同步: " + $f.Name)
}
Write-Host ("完成,共 " + $files.Count + " 个文件 -> " + $dst)