PvP · shedding
star × deal

Tiến Lên Star — tài liệu ghép client

Giống Tiến Lên, thêm hệ số sao (1–4). Server random sao lúc chia bài và gửi luôn trong gói deal. Không còn bước user chọn sao. DealTime dài hơn (~10s) để client diễn animation chia bài + random sao.

1. Tổng quan

MụcGiá trị
GAME_IDtienlenstar
Proto gamepackage tienlenstar_event · tải .proto
Proto lobbypackage game_event · tải .proto
TagTagPlay=1
StarServer random 1..4 lúc chia bài. Field deal.star / room_state.star. Nhân tiền chặt đồ và settle cuối ván.
DealTime~10s (time.deal_time) — dài hơn Tiến Lên (~5s) để diễn random sao. Hết DealTime mới có player_turn.
StatePending=0 · Countdown=1 · DealTime=2 · ShowdownTime=3 · PlayerTurn=4 · PrePlayerTurn=5. Không còn ChoseStartTurn.
Combination / InstantWin / PenaltyGiống tienlen (Card…Pass, FiveConsecutivePairs…, PenaltyCard2…Burn)
ConnectGET /ws?access_token= domain GAME

ID lá bài: rank × 4 + suit. Rank 0=2 … 12=A. Suit 0=♠ 1=♣ 2=♦ 3=♥. Ví dụ 3♠ = 4.

Breaking: client không gửi chose_star. Không có start_chose_star / chose_star S→C. Sao nằm trong deal.star. Đừng hiện UI chọn sao.

2. Proto cho client

Tải file .proto rồi generate code (protoc / buf). Frame WS là binary protobuf, không JSON.

FilePackageDùng cho
/tienlenstar/tienlenstar_event.prototienlenstar_eventPayload bàn: data_send.data / game_message.data
/tienlenstar/game_event.protogame_eventEnvelope lobby (mọi PvP)
GET /tienlenstar/tienlenstar_event.proto
GET /tienlenstar/game_event.proto
# Host = domain DOCUMENT (trang docs này), không phải domain GAME.

3. Demo bàn (CSS)

DealTime: chia bài + random sao. Client quay ★ rồi dừng đúng deal.star (ví dụ 3). User không bấm chọn.

Tây
13
Bắc
12
Đông
11
3♠
3♣
★ 1
★ 2
★ 3 deal.star
★ 4

Animation random trong DealTime (~10s) rồi khóa vào giá trị server gửi. Không gửi request nào lúc này.

4. Kết nối & envelope

Mọi frame WebSocket là binary protobuf — không JSON. Một user chỉ được một connection; login trùng → lobby error 102.

GET /ws?access_token=<JWT>
# Host = domain GAME (không phải docs). Frame = bytes proto, không text JSON.
Handshake closeÝ nghĩa — client phải làm
0Closed — đóng bình thường, có thể reconnect
1Other device — đang chơi máy khác, hiện toast, đừng spam reconnect
2Too many — quá nhiều connection, đợi rồi thử lại
4Expired — JWT hết hạn, lấy token mới rồi connect lại
5Banned — tài khoản bị cấm, dừng retry

Envelope lobby package game_event (internal/proto/game-event). Payload bàn nằm trong data_send.data (C→S) và game_message.data (S→C) — bytes của proto game.

C→S game_event.Request
data_send.data = bytes game Request
S→C game_event.Response
game_message.data = bytes game Response
Unmarshal 2 lớp: (1) lobby Request/Response. (2) Nếu oneof là data thì unmarshal tiếp proto game. room_id trên envelope phải khớp bàn đang ngồi. Cheat chỉ non-prod.

5. Lobby C→S (game_event.Request)

oneofFieldMô tả
room_config(empty)Lấy config sảnh ngay sau handshake
get_room_listbet int64List bàn theo đúng mức cược
create_roombet int64, password string, max_players int32, name stringTạo bàn. password rỗng = public
join_roomroom_id int32, password string, is_viewer boolVào bàn chỉ định. Viewer tạm để false
join_any_roombet int64, max_players uint32Matchmaking. max_players=0 = mọi size
leave_roomroom_id int32Rời bàn ngay
find_roomroom_id int32Tra cứu trước khi join (private?)
inviteroom_id, password, user_idMời user rảnh
free_users_listbet int64User rảnh cùng mức cược, không đang bàn khác
send_chat_messageroom_id, message, to[] int32, is_private_chatChat bàn; whisper khi is_private_chat + to
data_sendroom_id + oneof data bytes | auto_leaveMọi action gameplay. auto_leave = đăng ký out cuối ván
create_room { bet: 1000, password: "", max_players: 4, name: "ban-1" }
join_room   { room_id: 42, password: "", is_viewer: false }
data_send   { room_id: 42, data: <marshal(game Request)> }
data_send   { room_id: 42, auto_leave: {} }

6. Lobby S→C (game_event.Response)

oneofFieldMô tả
errorcode uint32, tag uint32Lỗi lobby. tag = request gây lỗi (bảng dưới)
game_messageroom_id, create_time, oneof msgTin từ bàn
chat_messageroom_id, user_id, message, name, vipChat broadcast
room_listbet, rooms[]Danh sách bàn
room_configxem bảng RoomType + flagUI sảnh
find_roomroom_id, is_privatetrue → hiện ô mật khẩu
free_users_listplayers[] {user_id, avatar, name, vip}Danh sách mời
inviteuser_idAck đã gửi lời mời
someone_invited_youuser_id, name, avatar, room_id, passwordPush khi bị mời — join_room với password này

RoomType (trong room_config)

FieldÝ nghĩa
bet, ccu, joinableMức cược, số người online, có vào được không
vip_min/max, level_min/maxĐiều kiện VIP / level
chip_amount_min/maxSàn/trần chip để ngồi bàn
number_of_played_games_min/maxSố ván đã chơi

Flag room_config: disable_create · disable_find · disable_get_list · disable_select (chỉ Play now) · play_now · best_choice (bet gợi ý). Room: room_id, is_private, bet, create_time, metadata map, name. metadata thường có max_players, number_of_players, label.

game_message.msg

oneofFieldClient
databytesUnmarshal proto game Response
user_joineduser_id, name, chip, avatar, seat, room_id, state, vip, levelstate 0=view · 1=play. Seat 0..max
user_leftuser_id, disconnectdisconnect=true nếu mất WS giữa ván
start_countdownseconds, data bytesĐếm ngược; data có thể chứa payload game
start_gameplayers[] (cùng field user_joined trừ room_id), dataKhóa ghế, bắt đầu ván
reset_gameplayers[]Ván xong, bàn về pending
auto_leaveuser_id, auto_exitAck đăng ký thoát cuối ván

error.tag — gói nào gây lỗi

tagRequesttagRequest
100room_config106data_send
101create_room107send_chat_message
102join_room108find_room
103join_any_room109free_users_list
104get_room_list110invite
105auto_leave

Luồng sảnh (mọi game WS này)

  1. Mở WS GET /ws?access_token= trên domain GAME. Đọc close code nếu fail.
  2. Gửi room_config. Render mức cược; ẩn nút theo disable_* ; Play now nếu play_now.
  3. Tuỳ UI: get_room_list / find_room / create_room / join_any_room / join_room.
  4. Ngồi bàn: nhận user_joined (kể cả mình), có thể room_state trong game_message.data.
  5. Đủ người: start_countdown rồi start_game. Từ đây chỉ gửi action qua data_send.data.
  6. Chat: send_chat_message → chat_message. Mời: free_users_list → invite → đối phương nhận someone_invited_you.
  7. Hết ván: reset_game. Rời: leave_room hoặc data_send.auto_leave (out sau ván).
Reconnect giữa ván: giữ cùng JWT, join_room lại (cùng room_id). Server trả game_message.data = room_state proto game. Đừng tạo phòng mới. Một user một socket — socket cũ bị đóng với handshake 1 hoặc lobby 102.
CấmLý do
JSON trên WSServer chỉ unmarshal protobuf binary
Hai tab cùng account102 duplicated login / handshake 1
data_send khi chưa join118 not in room / 103 not found
create_room bet lạ115 bet not found
Lỗi lobby: 100 missing payload · 102 duplicated login · 103 room not found · 108 bad password · 113 room full · 114 in other room · 115 bet not found · 120 not enough money. Bảng đầy đủ mục Errors.

7. Gameplay C→S (proto tienlenstar_event)

Marshal message dưới thành bytes, gửi trong data_send.data. Chỉ play trong ván thường — không có chose_star.

oneofFieldTag / ghi chú
playcards[], combinationTagPlay=1 — đánh bài, giống tienlen. Pass: combination=8, cards=[]
play { cards: [4], combination: Card }          // TagPlay=1
play { cards: [], combination: Pass }
Gửi play trong DealTime → error 1 WrongState. Đợi hết animation deal/sao, nhận player_turn rồi mới đánh.

8. Gameplay S→C

Nằm trong game_message.data. Reconnect: unmarshal room_state trước, rồi sync UI.

oneofFieldKhi nào
dealcards[], combination (InstantWin), show_card_count, star (1–4)Chia 13 lá + sao. Diễn animation chia bài và random sao trong DealTime (~10s). star là kết quả cuối, không phải gợi ý.
player_turnseconds, end_at, user_id, is_new_round, must_play_3spade, win_penaltyHết DealTime mới gửi. Tới lượt.
player_playuser_id, is_auto, cards, combinationĐánh / pass / auto
finishwinner_id, player_result[{winAmount,user_money,cards,penalty,point,user_id}], win_penalty, instant_winHết ván. Tiền đã × star
room_statenhư tienlen + starReconnect: restore sao hiện tại. star=0 nếu chưa deal

WinPenalty: winner_id, loser_id, win_amount, lose_amount, winner_money, loser_money — đã nhân star. Animate chặt heo rồi mới cập nhật chip.

9. Luồng ghép

  1. Connect WS, join bàn, đợi start_countdown / start_game như mọi PvP.
  2. Nhận deal: render 13 lá + đọc deal.star (1–4). Diễn animation chia bài và random sao trong DealTime (~10s). Không gửi gì.
  3. Hết DealTime → player_turn. Nếu instant win → chờ finish (star vẫn áp dụng).
  4. Khi player_turn.user_id = mình: chọn bộ, gửi play. Hết giờ server is_auto.
  5. Mọi client play animation player_play, cập nhật table_cards.
  6. Round mới (is_new_round): xóa bài bàn. win_penalty nếu có chặt heo (đã × star).
  7. finish: lật bài còn lại, cộng winAmount (đã × star), hiện penalty.

10. Error codes

codeNguồnÝ nghĩa
0 / 1 / 2 / 4 / 5Handshakeclosed · other device · too many · expired · banned
100 · 102 · 103 · 108 · 113 · 114 · 115 · 120Lobbymissing payload · duplicated login · room not found · bad password · room full · in other room · bet not found · not enough money
1 WrongStateTagPlayKhông trong PlayerTurn (kể cả gửi play lúc DealTime)
2 NotTurnTagPlayKhông phải lượt
3 InvalidCardTagPlayLá không trên tay
4 InvalidCombinationTagPlayBộ không hợp lệ
5 CombinationCanNotBeatTagPlayKhông chặt được

11. Checklist client