taixiu

package
v1.1.2 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 11, 2026 License: MIT Imports: 7 Imported by: 0

README

Tài-Xỉu (Sic Bo / Over-Under) Sample Game Module

This directory contains a complete, production-ready sample module for an authoritative Tài-Xỉu (Sic Bo / Over-Under) betting game built on top of Ultimate Game Engine.


Game Overview & Rules

Tài-Xỉu is a popular multiplayer casino/betting game where players guess the sum of 3 six-sided dice:

  • Tài (Over): Total dice sum is 11 to 17. Win payout is 1:1.
  • Xỉu (Under): Total dice sum is 4 to 10. Win payout is 1:1.
  • Triples (3 of a Kind): Sum is 3 (1-1-1) or 18 (6-6-6). Special outcome (House retains standard bets).

Game Round Lifecycle (State Machine)

[BETTING] ──(Countdown Timer)──> [ROLLING] ──(Dice Roll)──> [RESULT] ──(Payouts)──> [INTERMISSION] ──> [BETTING (Next Round)]
  1. BETTING Phase (Default: 10 seconds):
    • Players join the match room and place bets (TAI or XIU) using their wallet balance.
    • Bet amounts are deducted immediately from the player's wallet via nk.WalletUpdate.
    • Real-time countdowns and total pool stats are broadcast to all connected clients.
  2. ROLLING Phase (2 seconds):
    • Betting closes.
    • The authoritative server rolls 3 random dice (1..6).
  3. RESULT Phase (3 seconds):
    • Dice outcome and total sum are computed.
    • Winning bets receive a 1:1 payout (credited back to player wallet via nk.WalletUpdate).
    • Round summary event (OpCodeRoundResult) is broadcast with dice values, winning side, and individual win/loss amounts.
  4. INTERMISSION Phase (2 seconds):
    • Active pools and bets reset.
    • Round number increments and the match transitions back to BETTING.

WebSocket / Match Data Protocol

OpCodes
OpCode Name Direction Description
1 OpCodeBet Client -> Server Place a bet on TAI or XIU
2 OpCodeStateUpdate Server -> Client Periodic round state and timer broadcast
3 OpCodeBetAck Server -> Client Confirmation response for bet placement
4 OpCodeRoundResult Server -> Client Final round result, dice roll, and payout summary
Request & Response Formats
Place Bet (OpCodeBet - OpCode 1)
{
  "choice": "TAI",
  "amount": 100
}
Bet Response (OpCodeBetAck - OpCode 3)
{
  "success": true,
  "choice": "TAI",
  "amount": 100,
  "balance": 900
}
Round Result Broadcast (OpCodeRoundResult - OpCode 4)
{
  "round_number": 1,
  "dice": [4, 5, 6],
  "sum": 15,
  "winning_side": "TAI",
  "payouts": [
    {
      "user_id": "user-1",
      "choice": "TAI",
      "bet_amount": 100,
      "payout": 200,
      "net_profit": 100,
      "new_balance": 1100
    }
  ]
}

Registration & Server Integration

To register the TaiXiuMatch handler in your Ultimate Game Engine Go runtime module:

package main

import (
    "context"
    "database/sql"
    "github.com/BornToBuildGame/ultimate-game-server/examples/taixiu"
    "github.com/BornToBuildGame/ultimate-game-server/internal/runtime"
)

func InitModule(ctx context.Context, logger runtime.Logger, db *sql.DB, nk runtime.RuntimeModule, initializer runtime.Initializer) error {
    // Register authoritative match
    err := initializer.RegisterMatch("tai_xiu", func(ctx context.Context, logger runtime.Logger, db *sql.DB, nk runtime.RuntimeModule) (runtime.Match, error) {
        return taixiu.NewTaiXiuMatch(), nil
    })
    if err != nil {
        return err
    }
    logger.Info("Successfully registered TaiXiuMatch module!")
    return nil
}

Running Automated Tests

Run the sample unit tests using Go:

go test -v ./examples/taixiu/...

Documentation

Index

Constants

View Source
const (
	OpCodeBet         int64 = 1 // Client -> Server: Place a bet
	OpCodeStateUpdate int64 = 2 // Server -> Client: Match state update / timer tick broadcast
	OpCodeBetAck      int64 = 3 // Server -> Client: Bet acknowledgement / status response
	OpCodeRoundResult int64 = 4 // Server -> Client: Round dice result & win/loss payout notification
)

OpCodes for WebSocket / MatchData messages

View Source
const (
	ChoiceTai    = "TAI"    // Sum 11 - 17 (Over)
	ChoiceXiu    = "XIU"    // Sum 4 - 10 (Under)
	ChoiceTriple = "TRIPLE" // 3 of a kind (1-1-1 or 6-6-6)
)

Bet Choice Constants

View Source
const (
	PhaseBetting      = "BETTING"
	PhaseRolling      = "ROLLING"
	PhaseResult       = "RESULT"
	PhaseIntermission = "INTERMISSION"
)

Phase Constants

Variables

This section is empty.

Functions

func NewTaiXiuMatch

func NewTaiXiuMatch() runtime.Match

NewTaiXiuMatch creates a new instance of the match handler.

Types

type BetAckResponse

type BetAckResponse struct {
	Success bool   `json:"success"`
	Error   string `json:"error,omitempty"`
	Choice  string `json:"choice,omitempty"`
	Amount  int64  `json:"amount,omitempty"`
	Balance int64  `json:"balance,omitempty"`
}

BetAckResponse payload sent to client in OpCodeBetAck

type BetRequest

type BetRequest struct {
	Choice string `json:"choice"` // "TAI" or "XIU"
	Amount int64  `json:"amount"` // Bet amount in coins/chips
}

BetRequest payload sent from client in OpCodeBet

type PlayerBet

type PlayerBet struct {
	UserID    string `json:"user_id"`
	SessionID string `json:"session_id"`
	Choice    string `json:"choice"` // TAI or XIU
	Amount    int64  `json:"amount"`
}

PlayerBet stores an individual player's bet for the current round.

type PlayerPayout

type PlayerPayout struct {
	UserID     string `json:"user_id"`
	Choice     string `json:"choice"`
	BetAmount  int64  `json:"bet_amount"`
	Payout     int64  `json:"payout"`     // Total payout credited (0 if lost)
	NetProfit  int64  `json:"net_profit"` // Payout - BetAmount
	NewBalance int64  `json:"new_balance"`
}

PlayerPayout summary included in round result

type RoundResultBroadcast

type RoundResultBroadcast struct {
	RoundNumber int64          `json:"round_number"`
	Dice        [3]int         `json:"dice"`
	Sum         int            `json:"sum"`
	WinningSide string         `json:"winning_side"` // "TAI", "XIU", or "TRIPLE"
	Payouts     []PlayerPayout `json:"payouts"`
}

RoundResultBroadcast payload sent to all players in OpCodeRoundResult

type StateUpdateBroadcast

type StateUpdateBroadcast struct {
	Phase          string `json:"phase"`
	RoundNumber    int64  `json:"round_number"`
	RemainingTicks int    `json:"remaining_ticks"`
	TotalPoolTai   int64  `json:"total_pool_tai"`
	TotalPoolXiu   int64  `json:"total_pool_xiu"`
	ActivePlayers  int    `json:"active_players"`
}

StateUpdateBroadcast payload sent to all players in OpCodeStateUpdate

type TaiXiuMatch

type TaiXiuMatch struct{}

TaiXiuMatch implements runtime.Match for the Tài-Xỉu game room.

func (*TaiXiuMatch) MatchInit

func (m *TaiXiuMatch) MatchInit(ctx context.Context, logger runtime.Logger, db *sql.DB, nk runtime.RuntimeModule, params map[string]interface{}) (interface{}, int, string)

MatchInit initializes match state when the room is created.

func (*TaiXiuMatch) MatchJoin

func (m *TaiXiuMatch) MatchJoin(ctx context.Context, logger runtime.Logger, db *sql.DB, nk runtime.RuntimeModule, dispatcher interface{}, tick int64, state interface{}, presences []runtime.Presence) interface{}

MatchJoin records newly joined presences.

func (*TaiXiuMatch) MatchJoinAttempt

func (m *TaiXiuMatch) MatchJoinAttempt(ctx context.Context, logger runtime.Logger, db *sql.DB, nk runtime.RuntimeModule, dispatcher interface{}, tick int64, state interface{}, presence runtime.Presence, metadata map[string]string) (interface{}, bool, string)

MatchJoinAttempt approves or denies player join requests.

func (*TaiXiuMatch) MatchLeave

func (m *TaiXiuMatch) MatchLeave(ctx context.Context, logger runtime.Logger, db *sql.DB, nk runtime.RuntimeModule, dispatcher interface{}, tick int64, state interface{}, presences []runtime.Presence) interface{}

MatchLeave handles presences departing from the match room.

func (*TaiXiuMatch) MatchLoop

func (m *TaiXiuMatch) MatchLoop(ctx context.Context, logger runtime.Logger, db *sql.DB, nk runtime.RuntimeModule, dispatcher interface{}, tick int64, state interface{}, messages []runtime.MatchData) interface{}

MatchLoop is the tick loop driving the game state machine.

func (*TaiXiuMatch) MatchSignal

func (m *TaiXiuMatch) MatchSignal(ctx context.Context, logger runtime.Logger, db *sql.DB, nk runtime.RuntimeModule, dispatcher interface{}, tick int64, state interface{}, data string) (interface{}, string)

MatchSignal handles external control signals.

func (*TaiXiuMatch) MatchTerminate

func (m *TaiXiuMatch) MatchTerminate(ctx context.Context, logger runtime.Logger, db *sql.DB, nk runtime.RuntimeModule, dispatcher interface{}, tick int64, state interface{}, graceSeconds int) interface{}

MatchTerminate cleans up the room.

type TaiXiuState

type TaiXiuState struct {
	sync.RWMutex

	RoundNumber       int64                       `json:"round_number"`
	Phase             string                      `json:"phase"`
	PhaseTicks        int                         `json:"phase_ticks"`
	BettingTicks      int                         `json:"betting_ticks"`
	RollingTicks      int                         `json:"rolling_ticks"`
	ResultTicks       int                         `json:"result_ticks"`
	IntermissionTicks int                         `json:"intermission_ticks"`
	Presences         map[string]runtime.Presence `json:"-"`
	Bets              map[string]*PlayerBet       `json:"bets"` // key: userID
	TotalPoolTai      int64                       `json:"total_pool_tai"`
	TotalPoolXiu      int64                       `json:"total_pool_xiu"`

	LastDice    [3]int         `json:"last_dice"`
	LastSum     int            `json:"last_sum"`
	LastWinning string         `json:"last_winning"`
	LastPayouts []PlayerPayout `json:"last_payouts"`
	// contains filtered or unexported fields
}

TaiXiuState holds the authoritative state of a Tài-Xỉu match room.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL