NON-PRODUCTION

Zeus — tài liệu ghép client

Slot 6×5 pay-anywhere / tumble (Gates of Olympus style). Client nói HTTP JSON với server game. Trang này chỉ được serve khi ENVIRONMENT != production.

1. Tổng quan

MụcGiá trị
Grid6 cột × 5 hàng · reels[col][row], row 0 = trên cùng
PayPay-anywhere: ≥ 8 symbol giống nhau ở bất kỳ vị trí
TumbleÔ thắng biến mất, symbol còn lại rơi xuống, ô trống fill từ trên
OrbsSymbol multiplier (id 10), value 2x–500x. Cộng dồn rồi nhân tổng win của spin
Free spinsBase: ≥ 4 scatter → 15 FS. Trong FS: ≥ 3 scatter → +5. Orbs được bank suốt feature
Buy FSGiá = buy_free_spins_price × bet (mặc định 100× bet), guaranteed ≥ 4 scatter
Max win5000× bet. Chạm cap thì kết thúc round (kể cả FS còn lại)
Base path/api/v1
Session gameplay nằm in-memory trên server. Client bắt buộc gọi GET /api/v1/gameplay trước khi spin. Không persist FS qua restart process — reconnect thì đọc lại state từ /gameplay.

2. Luồng ghép

  1. Mở game → gửi GET /api/v1/gameplay với JWT. Render board preview, bet, paytable, money, FS state nếu đang giữa feature.
  2. User chọn bet (chỉ khi is_free_spin = false).
  3. Spin thường → POST /api/v1/spins body {"bet": ...}.
  4. Play tuần tự từng phần tử win_result[] (tumble). Xem mục 8.
  5. Sau tumble, apply multiplier nếu applied_multiplier > 1.
  6. Nếu is_bonus_free_spin → popup +N FS, chuyển UI sang free-spin mode.
  7. Khi is_free_spin trên response spin: không trừ bet, tiếp tục POST /spins cho tới khi is_finish_free_spin.
  8. Buy bonus → POST /api/v1/buy-freespins (không được gọi khi đang FS).
  9. Luôn lấy số dư mới từ user_money của response vừa nhận.

3. Auth & envelope

Header

Authorization: Bearer <access_token>
Content-Type: application/json

Mọi API dưới /api/v1 đều cần JWT. Trang docs không cần token.

Envelope

HTTP 200 + code = 0 là thành công. Payload nằm trong msg.

{
  "code": 0,
  "msg": { ... }
}

Lỗi nghiệp vụ thường trả HTTP 500, JWT sai/thiếu trả HTTP 401. Client luôn đọc field code.

codeÝ nghĩa
0OK
-1Unknown / thiếu JWT / token invalid (tùy HTTP status)
-2Invalid input (body JSON sai)
69Wrong game state — chưa /gameplay, đang FS mà đổi bet, bet không hợp lệ
1000Popup error (message i18n, dùng khi buy FS không hợp lệ)

4. Grid & symbol

Mọi board (init_reels, win_result[].reels, cheat reels) đều là mảng cột:

reels[col][row]     // col 0..5 trái → phải; row 0..4 trên → dưới
orb_values[col][row] // cùng index; >0 chỉ khi symbol == 10 (multiplier)
0
1
2
3
4
9
5
6
7
8
0
10
1
2
3
4
5
6
7
8
0
1
2
3
4
5
6
7
8
0

Hàng đầu = row 0. Ô vàng = scatter (9). Ô xanh = multiplier orb (10).

idSymbolPay?
0Crown
1Hourglass
2Ring
3Chalice
4Red gem
5Purple gem
6Yellow gem
7Green gem
8Blue gem
9Scatter (Zeus)Không pay — trigger FS
10Multiplier orbKhông pay — mang hệ số
-1Empty (nội bộ server)Client không nhận ô này trên board đã fill

Paytable

pay_table[symbol][band] là hệ số × bet. Band theo số lượng symbol:

CountBand index
8–90
10–111
12–302
< 8Không thắng

Default: Crown [10, 25, 50] … Blue gem [0.25, 0.75, 2]. Luôn dùng pay_table từ /gameplay, đừng hardcode.

Công thức 1 line: win_amount = round(bet × multiplier). Tổng base win = cộng mọi line trên mọi tumble. Orbs nhân sau khi tumble xong.

5. GET /api/v1/gameplay

Gọi 1 lần khi vào game. Set session initiated — thiếu bước này thì POST /spins trả code 69.

GET /api/v1/gameplay
FieldTypeClient dùng để
init_reelsint[6][5]Board preview idle (no-win). Không phải state đang chơi
init_orb_valuesint[6][5]Orb đi kèm preview
user_moneyint64Chip hiện tại
betint64Bet đang chọn trên server
betsint64[]Các mức bet hợp lệ (gửi đúng 1 giá trị này)
show_betsint64[]Giá trị hiển thị UI (cùng index với bets)
pay_tablefloat[9][3]Bảng trả thưởng 9 paying symbols
is_free_spinboolĐang trong FS → restore UI FS, disable đổi bet / buy
free_spinsintSố FS còn lại
max_free_spinsintMốc max để vẽ thanh FS
free_spin_potint64Tổng win đang cộng dồn trong FS (chưa credit)
total_multiplierintBank orb trong FS (0 nếu base)
buy_free_spins_priceint64Hệ số giá buy. Giá thật = field này × bet
max_win_multiplierintCap, mặc định 5000
{
  "code": 0,
  "msg": {
    "init_reels": [[8,7,6,5,4],[3,2,1,0,8],[7,6,5,4,3],[2,1,0,8,7],[6,5,4,3,2],[1,0,8,7,6]],
    "init_orb_values": [[0,0,0,0,0],[0,0,0,0,0],[0,0,0,0,0],[0,0,0,0,0],[0,0,0,0,0],[0,0,0,0,0]],
    "user_money": 500000,
    "bet": 1000,
    "bets": [200, 1000, 2000, 10000],
    "show_bets": [200, 1000, 2000, 10000],
    "pay_table": [[10,25,50],[2.5,10,25],[2,5,15],[1.5,4,12],[1,1.5,10],[0.8,1.2,8],[0.5,1,5],[0.4,0.9,4],[0.25,0.75,2]],
    "is_free_spin": false,
    "free_spins": 0,
    "max_free_spins": 0,
    "free_spin_pot": 0,
    "total_multiplier": 0,
    "buy_free_spins_price": 100,
    "max_win_multiplier": 5000
  }
}

6. POST /api/v1/spins

POST /api/v1/spins
{ "bet": 1000 }

SpinResult

FieldTypeÝ nghĩa
betint64Bet server đang dùng
priceint640 với spin thường; giá đã trừ nếu response tới từ buy
is_buyboolSpin này là kết quả buy FS
user_moneyint64Chip sau giao dịch của response này
win_amountint64Base: win đã nhân orb. FS giữa chừng: win spin này (chưa cộng vào tiền). FS xong: cả pot
base_win_amountint64Tổng line win trước khi nhân orb
collected_multiplierintTổng orb trên board cuối (0 nếu không thắng)
applied_multiplierintHệ số thực sự nhân vào win lần này (0/1 = không nhân)
total_multiplierintBank orb sau spin (FS). Base thường = collected hoặc 0
win_tierint0 none · 1 >10× · 2 >20× · 3 >30× · 4 >50× (theo win_amount / bet)
hit_max_winboolChạm 5000× — dừng FS, credit cap
is_free_spinbooltrue nếu spin vừa rồi nằm trong FS (không phải spin trigger)
free_spinsintSource of truth: số FS còn lại sau response
free_spin_potint64Pot sau spin. = 0 khi FS kết thúc
is_finish_free_spinboolSpin FS cuối (hoặc max-win). Credit pot vào user_money
is_bonus_free_spinboolSpin này vừa award thêm FS (base trigger hoặc retrigger)
number_of_bonus_free_spinsint+15 (base) hoặc +5 (retrigger). Dùng cho popup
max_free_spinsintMax để vẽ progress
scatter_countintSố scatter trên board cuối
win_resultWinResult[]Chuỗi tumble, play tuần tự

WinResult (một bước tumble)

FieldÝ nghĩa
reelsBoard trước khi xóa ô thắng của bước này
orb_valuesHệ số orb cùng index với reels
orbs[{col,row,value}] — list orb đang trên board, tiện highlight
linesCác cụm thắng. Rỗng = tumble dừng
positionspositions[col] = [row, ...] mọi ô thắng bước này
win_amountTổng line win của bước này (chưa nhân orb)
total_win_amountCộng dồn base win từ đầu spin tới bước này

WinLine

FieldÝ nghĩa
symbol0–8
line_sizeSố ô cùng symbol
multiplierHệ số paytable (vd 10 = 10× bet)
win_amountChip của line
positionspositions[col] = [row,...] — ô thuộc line
{
  "code": 0,
  "msg": {
    "bet": 1000,
    "price": 0,
    "is_buy": false,
    "user_money": 509000,
    "win_amount": 10000,
    "base_win_amount": 10000,
    "collected_multiplier": 0,
    "applied_multiplier": 0,
    "total_multiplier": 0,
    "win_tier": 0,
    "hit_max_win": false,
    "is_free_spin": false,
    "free_spins": 15,
    "free_spin_pot": 0,
    "is_finish_free_spin": false,
    "is_bonus_free_spin": true,
    "number_of_bonus_free_spins": 15,
    "max_free_spins": 15,
    "scatter_count": 4,
    "win_result": [
      {
        "lines": [{ "symbol": 0, "positions": [[0,1],[0],[0],[],[],[]], "win_amount": 10000, "line_size": 8, "multiplier": 10 }],
        "reels": [[0,0,1,7,6],[0,0,2,1,7],[0,0,3,2,1],[0,1,4,3,2],[4,6,5,4,3],[5,7,6,5,4]],
        "orb_values": [[0,0,0,0,0],[0,0,0,0,0],[0,0,0,0,0],[0,0,0,0,0],[0,0,0,0,0],[0,0,0,0,0]],
        "orbs": [],
        "win_amount": 10000,
        "total_win_amount": 10000,
        "positions": [[0,1],[0],[0],[0],[],[]]
      }
    ]
  }
}

Sau bước có lines, phần tử tiếp theo trong mảng là board đã tumble. Phần tử cuối thường lines: [].

7. POST /api/v1/buy-freespins

POST /api/v1/buy-freespins
{ "bet": 1000 }
Nút Buy chỉ enable khi !is_free_spinuser_money >= buy_free_spins_price * bet.

8. Tumble / animation

  1. Với mỗi win_result[i]: vẽ reels + orb_values.
  2. Nếu lines.length > 0: highlight positions / từng line, cộng win_amount vào counter base win.
  3. Xóa các ô thắng. Symbol còn lại rơi xuống (gravity theo cột, row lớn = dưới). Ô trống fill từ trên (row 0).
  4. Board mới = win_result[i+1].reels. Đừng tự random — tin server.
  5. Phần tử cuối thường lines = []: hết tumble. Đây là board để tính orb.
  6. Nếu applied_multiplier > 1: animation nhân base_win_amount × applied_multiplier → win_amount.
  7. Cập nhật money / FS UI từ field top-level, không suy ra từ tumble.
Scatter và orb không bị xóa khi paying symbol thắng. Chúng ở lại, rơi theo gravity cùng cột.

9. Multiplier orbs

Symbol 10. Value hợp lệ: 2, 3, 4, 5, 6, 8, 10, 12, 15, 20, 25, 50, 100, 250, 500. Hiển thị số trên orb, lấy từ orb_values[col][row] hoặc orbs[].value.

ModeKhi nào applyCông thức
BaseSpin có base_win_amount > 0applied = sum(orbs trên board cuối). Chỉ nhân spin này, không bank
Free spinSpin thắngBank total_multiplier += collected. Rồi applied = total_multiplier (cả bank cũ + mới)
FS không thắngKhông cộng bank, không nhân. Orb trên board không thắng bị bỏ

applied_multiplier ≤ 1 nghĩa là không nhân (giữ nguyên base win). Client chỉ play anim nhân khi > 1.

final_win = base_win                    // applied ≤ 1
final_win = base_win * applied_multiplier  // applied > 1

10. Free spins

TriggerĐiều kiệnAward
Base / buy≥ 4 scatter trên board cuối+15 FS
Trong FS≥ 3 scatter+5 FS (retrigger)
Source of truth số lượt còn lại là free_spins trên response. Popup +N dùng number_of_bonus_free_spins.

11. Cheat (chỉ non-prod / cheater)

Trên môi trường không phải production, POST /spins nhận board cố định:

{
  "bet": 1000,
  "reels": [
    [0, 0, 1, 7, 6],
    [0, 0, 2, 1, 7],
    [0, 0, 3, 2, 1],
    [0, 1, 4, 3, 2],
    [4, 6, 5, 4, 3],
    [9, 9, 9, 9, 10]
  ],
  "orb_values": [
    [0,0,0,0,0],[0,0,0,0,0],[0,0,0,0,0],[0,0,0,0,0],[0,0,0,0,0],[0,0,0,0,25]
  ]
}

12. Error & tiền

Tình huốngHành vi client
HTTP 401Token hết hạn / thiếu — relogin
code 69Gọi lại GET /gameplay, sync state, đừng spam spin
code 1000Hiện popup msg (string i18n)
code -1 khi spin/buyCó thể hết tiền hoặc lỗi nội bộ — refresh money qua /gameplay
Spin khi user đang room PvP khácHTTP 400 body "blocked"

Khi nào chip đổi

EventChip
Base spin−bet ngay; +win_amount nếu thắng (cùng response)
Buy FS−price ngay; +win của spin buy nếu có
FS giữa chừngKhông đổi. Chỉ tăng free_spin_pot
FS kết thúc / max win+pot vào user_money

13. Checklist client