NON-PRODUCTION

Mahjong Ways — tài liệu ghép client

Slot Megaways cố định 5 cột × 4 hàng. Tumble cascade, multiplier tăng theo lượt nổ. 3 scatter = 12 free spin. Không jackpot, không mua bonus. 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ị
Grid5 cột × 4 hàng. reels[col][row], row 0 = trên. Mỗi ô là packed int: 4 bit thấp là symbol id, bit 4 là golden
PayMegaways trái → phải, tối thiểu 3 cột. Wild (8) thay paying symbol, không thay Scatter (9)
WaysTích số ô khớp mỗi cột liên tiếp. Board đầy = 4^5 = 1024 ways
Scatter≥3 trên board sau mọi tumble → 12 FS. Trong FS không rơi scatter, không retrigger
MultiplierMỗi spin bắt đầu ×1. Base: 1→2→3→4→5. FS: 1→2→4→6→8→10
Không cóJackpot, buy bonus, mallet, top reel, size symbol
Base path/api/v1
Session in-memory. Bắt buộc GET /api/v1/gameplay trước khi spin. Reconnect đọc lại mini_game_type / FS từ /gameplay.

2. Luồng ghép

  1. Vào game → GET /gameplay. Render board preview 5×4, bet, paytable, FS nếu đang dở.
  2. Base: POST /spins {"bet":...}. Play tuần tự win_result[].
  3. is_bonus_free_spin → popup +12 FS, mini_game_type = 1.
  4. FS: is_free_spin, không trừ bet. Line win cộng pot; chỉ credit chip khi is_finish_free_spin.
  5. Luôn lấy số dư từ user_money của response vừa nhận.

3. Auth & envelope

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

Mọi API dưới /api/v1 cần JWT. Trang docs không cần token. HTTP 200 + code = 0 là thành công; payload nằm trong msg.

{ "code": 0, "msg": { ... } }
codeÝ nghĩa
0OK
-1Unknown / thiếu JWT / hết tiền / lỗi nội bộ
-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)

4. Grid & symbol

Mỗi ô là packed int. 4 bit thấp là symbol id (0–9); bit 4 là cờ golden. Cột dọc, hàng 0 ở trên.

0
0
8
3
6
1
4
2
5
7
9
1
9
4
9
2
3
5
6
7
idSymbolidSymbol
0發 Fa52 đồng
1白板 Baiban65 sách
2中 Zhong72 sách
3八萬8Wild
45 đồng9Scatter
BitÝ nghĩa
0..3Symbol id. Lấy bằng value & 0b1111.
4Golden flag. 1 = golden; lấy bằng value & 0b10000.

Ví dụ: 0 là Fa thường, 16 là Fa golden; 8 là Wild thường, 24 là Wild golden. Field lines[].symbol vẫn luôn là symbol id thô.

JavaScript: pack / unpack

const SYMBOL_MASK = 0b0000_1111;
const GOLDEN_MASK = 0b0001_0000;

function packSymbol(symbol, isGolden = false) {
  return (symbol & SYMBOL_MASK) | (isGolden ? GOLDEN_MASK : 0);
}

function unpackSymbol(value) {
  return {
    symbol: value & SYMBOL_MASK,
    isGolden: (value & GOLDEN_MASK) !== 0,
  };
}

// 16 → { symbol: 0, isGolden: true }
// 24 → { symbol: 8, isGolden: true }
const { symbol, isGolden } = unpackSymbol(reels[col][row]);
Dùng symbol để chọn sprite và isGolden để vẽ khung/hiệu ứng vàng. Không so sánh trực tiếp packed value với id, ví dụ dùng unpackSymbol(cell).symbol === 8 thay vì cell === 8.

mini_game_type: 0 base · 1 free spin. Dùng pay_table từ /gameplay, đừng hardcode. pay_table[symbol][n-1] = hệ số khi ăn n cột (n ≥ 3 mới > 0).

5. Multiplier

Mỗi spin (kể cả FS) bắt đầu multiplier = 1. Lượt nổ sau nhân với multiplier của lượt trước. Tăng sau tumble thắng.

ModeChuỗi (tumble 1, 2, 3…)Max
Base×1, ×2, ×3, ×4, ×55
Free spin×1, ×2, ×4, ×6, ×8, ×1010

6. GET /api/v1/gameplay

Gọi 1 lần khi vào game. Thiếu bước này thì spin trả code 69.

GET /api/v1/gameplay
FieldTypeClient dùng để
user_moneyint64Chip hiện tại
bet / bets / show_betsint64 / []Chỉ gửi bet nằm trong bets
pay_tablefloat[][]10 symbol × 5 bậc (1-of … 5-of)
is_free_spin / free_spins / max_free_spins / free_spin_pot—Restore FS UI
free_spin_multiplierint64Luôn 1 (reset mỗi spin)
init_reelsint[][]Preview 5 cột, mỗi cột 4 hàng; packed symbol
mini_game_typeint0 base · 1 FS
{
  "code": 0,
  "msg": {
    "user_money": 500000, "bet": 1000, "bets": [200,1000,2000,10000], "show_bets": [200,1000,2000,10000],
    "pay_table": [[0,0,2,4,8],[0,0,1.5,3,5],[0,0,1.2,2.5,4],[0,0,1,2,3],[0,0,0.8,1.5,2.5],[0,0,0.6,1.2,2],[0,0,0.4,0.8,1.5],[0,0,0.4,0.8,1.5],[0,0,0,0,0],[0,0,0,0,0]],
    "is_free_spin": false, "free_spins": 0, "max_free_spins": 0, "free_spin_pot": 0, "free_spin_multiplier": 1,
    "init_reels": [[0,1,2,3],[1,4,5,6],[2,8,0,7],[3,5,6,1],[9,7,4,2]],
    "mini_game_type": 0
  }
}

7. POST /api/v1/spins

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

SpinResult

FieldÝ nghĩa
bet / user_money / win_amountBet server; chip sau giao dịch; win (FS giữa chừng = win spin, chưa credit; FS xong = cả pot)
mini_game_typeState sau spin (0 / 1)
is_free_spin / free_spins / free_spin_potFS flags. Source of truth số lượt = free_spins
is_finish_free_spinCredit pot vào user_money
is_bonus_free_spin / number_of_bonus_free_spinsPopup +12 FS
bonus_mini_game_type1 khi vừa trigger FS
max_free_spins / multiplierTổng lượt FS khi award / hệ số tumble cuối
win_resultChuỗi tumble, play tuần tự

WinResult / WinLine

FieldÝ nghĩa
lines[]symbol, positions[col]=[row…], win_amount, line_size, number_of_lines
reels / positionsBoard packed trước khi xóa ô thắng; hợp mọi ô thắng bước này
top_symbolsSymbol packed sẽ rơi từ trên ở tumble kế tiếp; chỉ dùng để preview animation, board kế tiếp là source of truth
multiplier / bonus_multiplier / mega_waysHệ số tumble / bước tăng / ways

lines[].positions luôn 5 cột (cột không ăn = []). Row 0-indexed.

{
  "code": 0,
  "msg": {
    "bet": 1000, "user_money": 499000, "win_amount": 8000,
    "mini_game_type": 0, "is_free_spin": false, "free_spins": 0, "free_spin_pot": 0,
    "is_finish_free_spin": false, "is_bonus_free_spin": false, "bonus_mini_game_type": 0,
    "number_of_bonus_free_spins": 0, "max_free_spins": 0, "multiplier": 2,
    "win_result": [{
      "lines": [{ "symbol": 0, "positions": [[0,1],[0],[2],[],[]], "win_amount": 8000, "line_size": 3, "number_of_lines": 2 }],
      "reels": [[16,0,1,2],[0,4,5,6],[8,5,0,7],[3,5,6,1],[7,4,2,2]],
      "positions": [[0,1],[0],[2],[],[]], "multiplier": 1, "bonus_multiplier": 1, "mega_ways": 1024
    }]
  }
}
Trigger FS: is_bonus_free_spin = true, number_of_bonus_free_spins = 12, mini_game_type = 1. Scatter không biến mất khi tumble (không thuộc pay line).

8. Animation / tumble

  1. Vẽ win_result[i].reels (5×4, row 0 trên).
  2. Highlight positions / từng lines. Hiện multiplier của bước.
  3. Ô thắng thường biến mất. Ô thắng có isGolden = true phải animate thành Wild (id 8), không biến mất. Sau đó gravity theo cột; board mới = phần tử tiếp theo — đừng tự random.
  4. Nếu bonus_multiplier > 0: animate tăng multiplier (base +1, FS nhảy 1→2 rồi +2).
  5. FS: cộng line win vào counter pot; chỉ credit chip khi is_finish_free_spin.

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

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

5 cột × 4 hàng. Có thể gửi symbol id thô 0–9 hoặc packed value từ packSymbol để test golden, ví dụ packSymbol(0, true) = 16. Ô trống / thiếu → server random. Production bỏ qua reels.

10. Error & tiền

Tình huốngHành vi client
HTTP 401Token hết hạn — relogin
code 69GET /gameplay lại, sync state, đừng spam spin
code 1000Popup msg (string i18n)
code -1 khi spinHết tiền hoặc lỗi nội bộ — refresh qua /gameplay
Spin khi user đang room PvP khácHTTP 400 body "blocked"
EventChip
Base spin−bet ngay; +win cùng response
FS giữa chừngKhông đổi chip. Tăng free_spin_pot
FS kết thúc+pot vào user_money

11. Checklist client