Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
2434d0f
feat: 新增百余项DLC并全面升级对局体验
yafeng2003 Jul 23, 2026
d4d6323
chore: 修复Railway生产部署
yafeng2003 Jul 23, 2026
cb89ea9
fix: 提交Railway构建依赖锁文件
yafeng2003 Jul 23, 2026
8a31e1f
feat: 扩展增益牌面并限制无主回光返照
yafeng2003 Jul 23, 2026
bde28b0
完善牌桌交互并新增记录在案与上称千斤
yafeng2003 Jul 24, 2026
5bf0336
优化埋底与换牌动画
yafeng2003 Jul 24, 2026
cd9d1b1
支持断线重连并修正规则胜者标识
yafeng2003 Jul 24, 2026
935e190
允许准备阶段玩家重新补位
yafeng2003 Jul 24, 2026
6070eac
触发生产部署并注明补位阶段
yafeng2003 Jul 24, 2026
0280425
修复牌局重连与规则流程
yafeng2003 Jul 25, 2026
1b37a4c
完善牌局流程并新增规则
yafeng2003 Jul 27, 2026
25576e6
新增投降流程并精简规则说明
yafeng2003 Jul 27, 2026
adaf472
统一规则选择短文案
yafeng2003 Jul 27, 2026
78a5382
修复牌局交互与规则状态同步
yafeng2003 Jul 28, 2026
40aa21a
修复出牌后剩余手牌闪选
yafeng2003 Jul 28, 2026
ffed48a
强化当前出牌玩家提示
yafeng2003 Jul 29, 2026
2f6aa01
优化返回房间与退出流程
yafeng2003 Jul 29, 2026
f32a567
修正规则文案与转化牌状态
yafeng2003 Jul 30, 2026
87f9e3f
完善新规则、投降流程与牌桌布局
yafeng2003 Jul 31, 2026
2683cbc
修复重返牌局并优化牌面与房间样式
yafeng2003 Jul 31, 2026
8dc216c
适配移动端横屏牌桌
yafeng2003 Aug 1, 2026
16ebc9c
完善移动端横屏牌桌布局
yafeng2003 Aug 1, 2026
ea4ca53
优化移动端规则窗与操作栏
yafeng2003 Aug 1, 2026
1a05492
优化移动端计分详情
yafeng2003 Aug 1, 2026
c0f6215
优化九子夺嫡手牌选择
yafeng2003 Aug 2, 2026
08522df
重做第二战场牌型与累计牌布局
yafeng2003 Aug 2, 2026
74d0e5a
add table audio and recoverable settlement UI
yafeng2003 Aug 6, 2026
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
17 changes: 17 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
.git
.github
.agents
.codex
.openai
.railway
**/node_modules
**/dist
**/.env
**/.env.*
!**/.env.example
WhoDesigned
docs
test-results
playwright-report
*.mp4
*.log
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,5 @@ playing-cards-assets/
rotate_svg.py
rotate.py
.DS_Store
/WhoDesigned/
tractor-game-simulator/server/.env
40 changes: 40 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
FROM node:22-bookworm-slim AS client-build

WORKDIR /app/client

COPY tractor-game-simulator/client/package.json tractor-game-simulator/client/package-lock.json ./
RUN npm ci

COPY tractor-game-simulator/client/ ./
RUN npm run build


FROM node:22-bookworm-slim AS runtime

ARG WHODESIGNED_COMMIT=d02e0d6786fba365c73ac784b449fc028d1c1293

RUN apt-get update \
&& apt-get install -y --no-install-recommends ca-certificates git python3 \
&& rm -rf /var/lib/apt/lists/*

WORKDIR /app

COPY tractor-game-simulator/server/package.json tractor-game-simulator/server/package-lock.json ./tractor-game-simulator/server/
RUN cd tractor-game-simulator/server && npm ci --omit=dev

COPY tractor-game-simulator/server/ ./tractor-game-simulator/server/
COPY simple-bot/ ./simple-bot/
COPY --from=client-build /app/client/dist ./tractor-game-simulator/client/dist/

RUN git clone https://github.com/Roushelfy/WhoDesigned.git ./WhoDesigned \
&& git -C ./WhoDesigned checkout --detach "${WHODESIGNED_COMMIT}" \
&& rm -rf ./WhoDesigned/.git

ENV NODE_ENV=production
ENV PYTHON_BIN=python3

WORKDIR /app/tractor-game-simulator/server

EXPOSE 5000

CMD ["node", "src/index.js"]
263 changes: 177 additions & 86 deletions docs/archive/DLC.json

Large diffs are not rendered by default.

16 changes: 0 additions & 16 deletions nixpacks.toml

This file was deleted.

9 changes: 5 additions & 4 deletions railway.json
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
{
"$schema": "https://railway.app/railway.schema.json",
"$schema": "https://railway.com/railway.schema.json",
"build": {
"builder": "NIXPACKS",
"buildCommand": "npm run postinstall"
"builder": "DOCKERFILE",
"dockerfilePath": "Dockerfile"
},
"deploy": {
"startCommand": "npm start",
"healthcheckPath": "/health",
"healthcheckTimeout": 300,
"restartPolicyType": "ON_FAILURE",
"restartPolicyMaxRetries": 10
}
Expand Down
182 changes: 182 additions & 0 deletions simple-bot/who_designed_adapter.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,182 @@
#!/usr/bin/env python3
"""Adapter between the Tractor server snapshot and the WhoDesigned strategy."""

import json
import os
import sys
import types


# mvGen imports numpy but never uses it. Supplying a tiny placeholder keeps the
# deployment dependency-free while leaving the original bot repository intact.
sys.modules.setdefault("numpy", types.ModuleType("numpy"))

WHO_DESIGNED_DIR = os.path.abspath(
os.path.join(os.path.dirname(__file__), "..", "WhoDesigned")
)
sys.path.insert(0, WHO_DESIGNED_DIR)

from mvGen import move_generator # noqa: E402
from myutils import get_action_options, setMajor # noqa: E402


CARD_SCALE = ["A", "2", "3", "4", "5", "6", "7", "8", "9", "0", "J", "Q", "K"]
SUITS = ["s", "h", "c", "d"]


def card_base_id(card_name):
if card_name == "jo":
return 52
if card_name == "Jo":
return 53
return CARD_SCALE.index(card_name[1]) * 4 + SUITS.index(card_name[0])


class CardAllocator:
def __init__(self):
self.available = set(range(108))

def allocate(self, card_name):
base = card_base_id(card_name)
for card_id in (base, base + 54):
if card_id in self.available:
self.available.remove(card_id)
return card_id
raise ValueError(f"Too many copies of card {card_name}")


def normalize_level(level):
return "0" if str(level) == "10" else str(level)


def protect_point_trick_with_trump(generator, hand_ids, current_trick, history_ids, action):
"""Keep WhoDesigned's heuristics, but close its unsafe single-card point gap.

When this player is void in the led side suit, protect table points if an
opponent is currently winning, or if a known-void opponent still acts after
a winning teammate. This uses the same empty-suit memory as the original
full-request constructor.
"""
if not current_trick or any(len(cards) != 1 for cards in history_ids):
return action

lead = generator.Num2Poker(history_ids[0][0])
if generator.isMajor(lead):
return action
lead_suit = generator.get_suit(lead)

hand_pokers = [generator.Num2Poker(card_id) for card_id in hand_ids]
if any(not generator.isMajor(card) and card[0] == lead_suit for card in hand_pokers):
return action

if not any(
generator.Num2Poker(card_id)[1] in ("5", "0", "K")
for cards in history_ids
for card_id in cards
):
return action

winner_position = 0
winner = generator.Num2Poker(history_ids[0][0])
for position in range(1, len(history_ids)):
challenger = generator.Num2Poker(history_ids[position][0])
if challenger != winner and generator.bigger_card(winner, challenger) == challenger:
winner = challenger
winner_position = position

winner_id = int(current_trick[winner_position]["playerIndex"])
should_trump = winner_id != generator.teammate_id
if not should_trump and winner_id == generator.teammate_id:
already_played = {int(play["playerIndex"]) for play in current_trick}
future_opponents = [
player_id
for player_id in range(4)
if player_id not in already_played
and player_id != generator.selfid
and player_id != generator.teammate_id
]
should_trump = any(
lead_suit in generator.empty_suits[player_id]
and generator.major not in generator.empty_suits[player_id]
for player_id in future_opponents
)

if not should_trump:
return action

trumps = [
card_id
for card_id in hand_ids
if generator.isMajor(generator.Num2Poker(card_id))
]
trumps.sort(key=lambda card_id: generator.card_level(generator.Num2Poker(card_id)))
for card_id in trumps:
poker = generator.Num2Poker(card_id)
if not generator.isMajor(winner) or generator.card_level(poker) > generator.card_level(winner):
return [card_id]
return action


def main():
data = json.loads(sys.stdin.read())
player_id = int(data.get("id", 0))
hand_names = data.get("deck", [])
played_names = data.get("played", [[], [], [], []])
current_trick = data.get("history", [])
empty_suits = data.get("emptySuits", [[], [], [], []])
level = normalize_level(data.get("level", "2"))
major = data.get("trumpSuit") or "n"

allocator = CardAllocator()
played_ids = [[], [], [], []]
for index in range(4):
for card_name in played_names[index]:
played_ids[index].append(allocator.allocate(card_name))

hand_ids = [allocator.allocate(card_name) for card_name in hand_names]

# Current-trick cards are the tail of each player's cumulative played list.
history_ids = []
for play in current_trick:
index = int(play["playerIndex"])
count = len(play["cards"])
history_ids.append(played_ids[index][-count:] if count else [])

setMajor(major, level)
generator = move_generator(level, major)
generator.selfid = player_id
generator.teammate_id = (player_id + 2) % 4
generator.next_id = (player_id + 1) % 4
generator.prev_id = (player_id + 3) % 4
generator.hold = hand_ids[:]
generator.played = played_ids
generator.empty_suits = [
list(dict.fromkeys(empty_suits[index])) if index < len(empty_suits) else []
for index in range(4)
]
generator.cards_left = sorted(allocator.available)
generator.organized_hold_cards = generator.organize_cards(generator.hold)
generator.organized_left_cards = generator.organize_cards(generator.cards_left)

options = get_action_options(hand_ids, history_ids, level, generator)
action = options[0] if options and options[0] else []
if not isinstance(action, list):
action = [action]
action = protect_point_trick_with_trump(
generator,
hand_ids,
current_trick,
history_ids,
action,
)
action_names = [generator.Num2Poker(card_id) for card_id in action]

print(json.dumps({"player": player_id, "action": action_names}))


if __name__ == "__main__":
try:
main()
except Exception as exc:
print(f"WhoDesigned adapter failed: {exc}", file=sys.stderr)
raise
3 changes: 2 additions & 1 deletion tractor-game-simulator/.gitignore
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
# Dependencies
node_modules/
package-lock.json

# Build output
dist/
Expand Down Expand Up @@ -33,3 +32,5 @@ Thumbs.db
# Temporary files
*.tmp
.cache/
test-results/
playwright-report/
7 changes: 6 additions & 1 deletion tractor-game-simulator/client/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,12 @@
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/png" href="/favicon.png" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover" />
<meta name="theme-color" content="#073b35" />
<meta name="mobile-web-app-capable" content="yes" />
<meta name="apple-mobile-web-app-capable" content="yes" />
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" />
<link rel="manifest" href="/manifest.webmanifest" />
<title>拖拉机纸牌游戏模拟器</title>
</head>
<body>
Expand Down
Loading