Plinko — tài liệu ghép client
Thả bóng một shot. Chọn row 8/12/16 và risk 1/2/3. Server trừ bet và cộng win trong cùng một response. Trang này chỉ được serve khi ENVIRONMENT != production.
1. Tổng quan
| Mục | Giá trị |
|---|---|
| Boards | config[{row, risk, multipliers[]}] từ /gameplay |
| Row | 8 · 12 · 16 — số hàng peg. Số bucket = multipliers.length (= row + 1) |
| Risk | 1 low · 2 medium · 3 high. Cùng row, risk khác → bảng hệ số khác |
| Position | Index bucket đích (0 = mép trái). Dùng để animate bóng rơi |
| Tiền | −bet (BET) rồi +win_amount (WIN) cùng response. user_money đã net |
| Win | win_amount = multiplier × bet (float × int64, server round) |
| Base path | /api/v1 |
GET /gameplay trước drop đầu để lấy multiplier table và set session (thiếu → code 69).2. Demo peg pyramid
Hàng peg lệch nhau. Bucket vàng = bóng vừa rơi vào (position = 4, risk low row 8).
Index bucket 0…8 trái → phải. Animate path tới position rồi hiện multiplier × bet = win_amount. Đừng tự random bucket.
3. Auth & envelope
Authorization: Bearer <access_token> Content-Type: application/json
Mọi API dưới /api/v1 cần JWT. HTTP 200 + code = 0 là thành công; payload nằm trong msg.
{
"code": 0,
"msg": { ... }
}
| code | Ý nghĩa |
|---|---|
0 | OK |
-1 | Unknown / thiếu JWT / token invalid (HTTP 401) |
-2 | Invalid input (body JSON sai) |
69 | Wrong game state — chưa /gameplay, bet không thuộc bets, cặp row/risk không có trong config |
1000 | Popup error (i18n) |
POST /bet bị middleware.Block: user đang room PvP khác → HTTP 400 body "blocked".4. GET /api/v1/gameplay
Gọi khi vào game. Trả bảng hệ số cho mọi (row, risk). Client render selector + paytable từ đây, không hardcode.
| Field | Type | Client dùng để |
|---|---|---|
user_money | int64 | Chip hiện tại |
bets | int64[] | Mức bet hợp lệ |
show_bets | int64[] | Giá trị hiển thị (cùng index) |
config | Board[] | 9 board: 3 row × 3 risk |
config[].row | int | 8 / 12 / 16 |
config[].risk | int | 1 / 2 / 3 |
config[].multipliers | float[] | Hệ số từng bucket trái → phải. Length = số bucket |
{
"code": 0,
"msg": {
"user_money": 500000,
"bets": [200, 1000, 2000, 10000],
"show_bets": [200, 1000, 2000, 10000],
"config": [
{"row": 8, "risk": 1, "multipliers": [29, 4, 1.5, 0.3, 0.2, 0.3, 1.5, 4, 29]},
{"row": 8, "risk": 2, "multipliers": [13, 3, 1.3, 0.7, 0.4, 0.7, 1.3, 3, 13]},
{"row": 8, "risk": 3, "multipliers": [5.6, 2.1, 1.1, 1, 0.5, 1, 1.1, 2.1, 5.6]},
{"row": 12, "risk": 1, "multipliers": [170, 24, 8.1, 2, 0.7, 0.2, 0.2, 0.2, 0.7, 2, 8.1, 24, 170]},
{"row": 16, "risk": 1, "multipliers": [1000, 130, 26, 9, 4, 2, 0.2, 0.2, 0.2, 0.2, 0.2, 2, 4, 9, 26, 130, 1000]}
]
}
}
Response thật đủ 9 board. Rút gọn ở đây. Risk 2/3 của row 12/16 cũng nằm trong config.
5. POST /api/v1/bet
Một drop. Debit bet rồi credit win trong cùng handler. user_money đã là số dư sau cả hai bước.
| Request | Type | Ý nghĩa |
|---|---|---|
bet | int64 | Phải thuộc bets |
row | int | 8 / 12 / 16 |
risk | int | 1 / 2 / 3 |
cheat_position | int? | Chỉ non-prod / cheater. Server chỉ nhận khi > 0 (bucket 0 không cheat được qua field này) |
| Response | Type | Ý nghĩa |
|---|---|---|
user_money | int64 | Chip sau −bet +win |
bet | int64 | Bet đã dùng |
row, risk | int | Echo board |
position | int | Bucket đích — animate bóng tới đây |
multiplier | float | config.multipliers[position] |
win_amount | int64 | Chip thắng (có thể < bet nếu multiplier < 1) |
POST /api/v1/bet
{ "bet": 1000, "row": 8, "risk": 1 }
{
"code": 0,
"msg": {
"user_money": 499200,
"bet": 1000,
"row": 8,
"risk": 1,
"position": 4,
"multiplier": 0.2,
"win_amount": 200
}
}
user_money phản ánh net. High multiplier (bucket 0 = 29×) thì win lớn hơn bet.{
"code": 0,
"msg": {
"user_money": 528000,
"bet": 1000,
"row": 8,
"risk": 1,
"position": 0,
"multiplier": 29,
"win_amount": 29000
}
}
Cheat (non-prod)
{ "bet": 1000, "row": 8, "risk": 1, "cheat_position": 8 }
Production bỏ qua field này, luôn random. Client không gửi trên build live. cheat_position phải > 0 và < số bucket.
6. GET /top-win · GET /history
| API | msg |
|---|---|
GET /api/v1/top-win | winners[{rank, user_id, username, total_win}] |
GET /api/v1/history | logs[{user_money, win_amount, bet, multiplier, row, created_at}] |
GET /api/v1/top-win
{
"code": 0,
"msg": { "winners": [{ "rank": 1, "user_id": 1001, "username": "alice", "total_win": 2400000 }] }
}
GET /api/v1/history
{
"code": 0,
"msg": {
"logs": [
{ "user_money": 499200, "win_amount": 200, "bet": 1000, "multiplier": 0.2, "row": 8, "created_at": 1720000000 }
]
}
}
History không có field risk / position. created_at là unix second.
7. Luồng ghép
- Mở game →
GET /gameplay. Render selector row/risk, vẽ bucket theomultipliers, hiện money + bet chips. - User chọn bet (trong
bets), row, risk. Disable Drop nếuuser_money < bet. POST /bet. Khóa nút tới khi xong animation.- Animate bóng rơi (peg pyramid) tới bucket
position. Highlight bucket đó. - Hiện
multipliervàwin_amount. Cập nhật chip =user_money(đã net). - Mở khóa Drop. Không cần gọi lại
/gameplaytrừ khi code 69 / reconnect.
8. Error & tiền
| Tình huống | Hành vi client |
|---|---|
| HTTP 401 | Token hết hạn — relogin |
| code 69 | GET /gameplay lại, đồng bộ bets/config |
| code -1 khi bet | Hết tiền hoặc lỗi nội bộ — refresh money |
HTTP 400 blocked | User đang PvP — không cho drop |
user_money trên response.9. Checklist client
- Vào game:
GET /gameplaytrước drop. - Vẽ đúng
multipliers.lengthbucket;positionlà index 0-based. - Row/risk chỉ gửi cặp có trong
config. - Bet chỉ gửi giá trị trong
bets. - Animate theo
positionserver — đừng tự random. - Money =
user_moneysau response (đã −bet +win). cheat_positionchỉ non-prod; production bỏ field.- Không spam 2 drop song song — đợi response + bóng rơi xong.