### GET /api/wallet/balance Source: https://context7.com/boppyorca/taixiu-game/llms.txt Retrieves the current balance and KYC level for the authenticated user. ```APIDOC ## GET /api/wallet/balance ### Description Retrieves the current balance and KYC level for the authenticated user. ### Method GET ### Endpoint /api/wallet/balance ### Parameters #### Query Parameters None #### Request Body None ### Response #### Success Response (200) - **balance** (decimal) - The current balance of the user. - **kycLevel** (integer) - The KYC verification level of the user. #### Response Example ```json { "balance": 150000.50, "kycLevel": 1 } ``` ``` -------------------------------- ### GET /api/game/current Source: https://context7.com/boppyorca/taixiu-game/llms.txt Retrieves the current active game session details, including its status, current betting totals, time remaining, and the provably fair seed hash. ```APIDOC ## GET /api/game/current ### Description Retrieves the current active game session details, including its status, current betting totals, time remaining, and the provably fair seed hash. ### Method GET ### Endpoint /api/game/current ### Parameters #### Query Parameters None #### Request Body None ### Response #### Success Response (200) - **sessionId** (integer) - The unique identifier for the current game session. - **status** (string) - The current status of the game session (e.g., BETTING, LOCKING, ROLLING, COMPLETED). - **totalTai** (integer) - The total amount bet on 'Tai'. - **totalXiu** (integer) - The total amount bet on 'Xiu'. - **timeRemaining** (integer) - The time remaining in the current phase of the round (in seconds). - **seedHash** (string) - The commitment hash for the provably fair system. #### Response Example ```json { "sessionId": 12345, "status": "BETTING", "totalTai": 500000000, "totalXiu": 450000000, "timeRemaining": 35, "seedHash": "a1b2c3..." } ``` ``` -------------------------------- ### GET /api/game/history Source: https://context7.com/boppyorca/taixiu-game/llms.txt Retrieves a history of recent game results, including dice rolls, total sum, result type (Tai/Xiu/Triple), and whether it was a Triple. ```APIDOC ## GET /api/game/history ### Description Retrieves a history of recent game results, including dice rolls, total sum, result type (Tai/Xiu/Triple), and whether it was a Triple. ### Method GET ### Endpoint /api/game/history ### Parameters #### Query Parameters - **count** (integer) - Optional - The number of historical results to retrieve. Defaults to 50. #### Request Body None ### Response #### Success Response (200) - An array of game result objects, each containing: - **sessionId** (integer) - The session ID for the game result. - **dice** (array of integers) - An array representing the three dice rolls. - **total** (integer) - The sum of the three dice rolls. - **result** (string) - The outcome of the round ('TAI', 'XIU', or 'TRIPLE'). - **isTriple** (boolean) - Indicates if the result was a Triple. #### Response Example ```json [ { "sessionId": 12344, "dice": [5, 3, 4], "total": 12, "result": "TAI", "isTriple": false }, { "sessionId": 12343, "dice": [1, 1, 1], "total": 3, "result": "TRIPLE", "isTriple": true } ] ``` ``` -------------------------------- ### Initialize React App with Vite (Bash) Source: https://github.com/boppyorca/taixiu-game/blob/main/implementation_plan.md Command to initialize a new React frontend application using Vite. Vite is a modern build tool that provides a fast development experience. ```bash npx create-vite@latest taixiu-client --template react ``` -------------------------------- ### User Registration and Login API (.NET/C#) Source: https://context7.com/boppyorca/taixiu-game/llms.txt Handles user registration with password hashing and JWT-based login. It takes user credentials as input and returns a JWT token upon successful authentication. Dependencies include BCrypt.Net for password hashing and JWT generation logic. ```csharp // AuthController.cs - User Registration & Login // POST /api/auth/register [HttpPost("register")] public async Task Register(RegisterRequest request) { // Request body // { // "username": "hoang123", // "password": "SecurePass123!", // "captchaToken": "recaptcha-token" // } var user = new User { Username = request.Username, PasswordHash = BCrypt.Net.BCrypt.HashPassword(request.Password, 12), Balance = 0, KYCLevel = 0, IsActive = true, CreatedAt = DateTime.UtcNow }; await _db.Users.AddAsync(user); await _db.SaveChangesAsync(); var token = GenerateJwtToken(user); return Ok(new { token, userId = user.Id, username = user.Username }); // Response: { "token": "eyJhbG...", "userId": 1, "username": "hoang123" } } // POST /api/auth/login [HttpPost("login")] public async Task Login(LoginRequest request) { var user = await _db.Users.FirstOrDefaultAsync(u => u.Username == request.Username); if (user == null || !BCrypt.Net.BCrypt.Verify(request.Password, user.PasswordHash)) return Unauthorized(new { error = "Invalid credentials" }); var token = GenerateJwtToken(user); return Ok(new { token, userId = user.Id, balance = user.Balance, kycLevel = user.KYCLevel }); } ``` -------------------------------- ### Manage Wallet Balance and Transactions (C#) Source: https://context7.com/boppyorca/taixiu-game/llms.txt This C# code defines endpoints for managing user wallet balances and processing transactions. It includes functionality for retrieving balances, initiating VietQR deposits with QR code generation, handling VietQR deposit callbacks, and requesting bank withdrawals after KYC verification. It utilizes Entity Framework for database operations and SignalR for real-time balance updates. ```csharp // WalletController.cs - Deposits & Withdrawals // GET /api/wallet/balance [HttpGet("balance")] [Authorize] public async Task GetBalance() { var userId = GetCurrentUserId(); var user = await _db.Users.FindAsync(userId); return Ok(new { balance = user.Balance, kycLevel = user.KYCLevel }); } // POST /api/wallet/deposit/vietqr [HttpPost("deposit/vietqr")] [Authorize] public async Task CreateVietQRDeposit(decimal amount) { if (amount < 10000) return BadRequest(new { error = "Minimum deposit: 10,000 VND" }); var userId = GetCurrentUserId(); var reference = GenerateReference(); // Unique transaction ref var transaction = new Transaction { UserId = userId, Type = "DEPOSIT", Amount = amount, Status = "PENDING", PaymentMethod = "VIETQR", Reference = reference, CreatedAt = DateTime.UtcNow }; await _db.Transactions.AddAsync(transaction); await _db.SaveChangesAsync(); // Generate QR code URL var qrData = _vietQRService.GenerateQR( bankId: "970415", // VietinBank accountNo: "123456789", amount: amount, description: $"NAP {reference}" ); return Ok(new { transactionId = transaction.Id, qrCode = qrData.QrCodeUrl, reference = reference, expiresIn = 900 // 15 minutes }); } // POST /api/wallet/deposit/callback (VietQR webhook) [HttpPost("deposit/callback")] public async Task VietQRCallback([FromBody] VietQRCallback callback) { var transaction = await _db.Transactions .FirstOrDefaultAsync(t => t.Reference == callback.Description); if (transaction == null || transaction.Status != "PENDING") return BadRequest(); using var tx = await _db.Database.BeginTransactionAsync(); var user = await _db.Users.FindAsync(transaction.UserId); transaction.BalanceBefore = user.Balance; user.Balance += transaction.Amount; transaction.BalanceAfter = user.Balance; transaction.Status = "COMPLETED"; transaction.CompletedAt = DateTime.UtcNow; await _db.SaveChangesAsync(); await tx.CommitAsync(); // Notify user via SignalR await _hubContext.Clients.User(user.Id.ToString()) .SendAsync("BalanceUpdated", new { balance = user.Balance }); return Ok(); } // POST /api/wallet/withdraw [HttpPost("withdraw")] [Authorize] public async Task RequestWithdraw(WithdrawRequest request) { var userId = GetCurrentUserId(); var user = await _db.Users.Include(u => u.KYC).FirstAsync(u => u.Id == userId); // Require KYC Level 1 if (user.KYCLevel < 1 || user.KYC?.IsVerified != true) return BadRequest(new { error = "KYC verification required for withdrawal" }); if (request.Amount > user.Balance) return BadRequest(new { error = "Insufficient balance" }); using var tx = await _db.Database.BeginTransactionAsync(); // Deduct balance immediately user.Balance -= request.Amount; var transaction = new Transaction { UserId = userId, Type = "WITHDRAW", Amount = request.Amount, BalanceBefore = user.Balance + request.Amount, BalanceAfter = user.Balance, Status = "PENDING", // Requires admin approval PaymentMethod = "BANK", Note = $"Bank: {user.KYC.BankName}, Account: {user.KYC.BankAccount}, Name: {user.KYC.RealName}" }; await _db.Transactions.AddAsync(transaction); await _db.SaveChangesAsync(); await tx.CommitAsync(); return Ok(new { transactionId = transaction.Id, newBalance = user.Balance }); } ``` -------------------------------- ### POST /api/wallet/deposit/vietqr Source: https://context7.com/boppyorca/taixiu-game/llms.txt Initiates a deposit using VietQR. Generates a QR code for payment and returns transaction details. ```APIDOC ## POST /api/wallet/deposit/vietqr ### Description Initiates a deposit using VietQR. Generates a QR code for payment and returns transaction details. ### Method POST ### Endpoint /api/wallet/deposit/vietqr ### Parameters #### Path Parameters None #### Query Parameters None #### Request Body - **amount** (decimal) - Required - The amount to deposit. ### Request Example ```json { "amount": 50000.00 } ``` ### Response #### Success Response (200) - **transactionId** (string) - The unique identifier for the transaction. - **qrCode** (string) - The URL for the generated QR code. - **reference** (string) - A unique reference for the transaction. - **expiresIn** (integer) - The time in seconds until the QR code expires (e.g., 900 seconds for 15 minutes). #### Response Example ```json { "transactionId": "a1b2c3d4-e5f6-7890-1234-567890abcdef", "qrCode": "https://api.vietqr.vn/v2/image?data=...", "reference": "NAP123456789", "expiresIn": 900 } ``` #### Error Response (400) - **error** (string) - Description of the error, e.g., "Minimum deposit: 10,000 VND" #### Error Response Example ```json { "error": "Minimum deposit: 10,000 VND" } ``` ``` -------------------------------- ### POST /api/wallet/deposit/callback Source: https://context7.com/boppyorca/taixiu-game/llms.txt Webhook endpoint for VietQR to confirm a deposit. Updates transaction status and user balance. ```APIDOC ## POST /api/wallet/deposit/callback ### Description Webhook endpoint for VietQR to confirm a deposit. Updates transaction status and user balance. This endpoint is called automatically by VietQR upon successful payment. ### Method POST ### Endpoint /api/wallet/deposit/callback ### Parameters #### Path Parameters None #### Query Parameters None #### Request Body - **callback** (object) - Required - Contains details of the VietQR callback. - **Description** (string) - Required - The reference string matching the transaction. - **Amount** (decimal) - Required - The amount received. - **Status** (string) - Required - The status of the payment (e.g., "SUCCESS", "FAILED"). - **TransactionId** (string) - Required - The transaction ID from VietQR. ### Request Example ```json { "Description": "NAP123456789", "Amount": 50000.00, "Status": "SUCCESS", "TransactionId": "vt_tx_987654321" } ``` ### Response #### Success Response (200) Returns an empty OK response upon successful processing of the callback. #### Response Example ```json { "message": "OK" } ``` #### Error Response (400) Returned if the transaction is not found or already processed. #### Error Response Example ```json { "message": "Bad Request" } ``` ``` -------------------------------- ### User Authentication API Source: https://context7.com/boppyorca/taixiu-game/llms.txt Handles user registration, login, and KYC verification processes. ```APIDOC ## POST /api/auth/register ### Description Registers a new user with username, password, and a captcha token. Upon successful registration, a JWT token is generated for authentication. ### Method POST ### Endpoint /api/auth/register ### Parameters #### Request Body - **username** (string) - Required - The desired username for the new account. - **password** (string) - Required - The user's chosen password. - **captchaToken** (string) - Required - A token obtained from a CAPTCHA service (e.g., reCAPTCHA) to prevent automated registrations. ### Request Example ```json { "username": "hoang123", "password": "SecurePass123!", "captchaToken": "recaptcha-token" } ``` ### Response #### Success Response (200) - **token** (string) - The JWT token for authenticated user sessions. - **userId** (integer) - The unique identifier for the newly registered user. - **username** (string) - The username of the registered user. #### Response Example ```json { "token": "eyJhbG...", "userId": 1, "username": "hoang123" } ``` ## POST /api/auth/login ### Description Authenticates a user using their username and password. If credentials are valid, it returns a JWT token along with user details. ### Method POST ### Endpoint /api/auth/login ### Parameters #### Request Body - **username** (string) - Required - The user's username. - **password** (string) - Required - The user's password. ### Request Example ```json { "username": "hoang123", "password": "SecurePass123!" } ``` ### Response #### Success Response (200) - **token** (string) - The JWT token for authenticated user sessions. - **userId** (integer) - The unique identifier for the logged-in user. - **balance** (number) - The current balance of the user's account. - **kycLevel** (integer) - The current Know Your Customer (KYC) level of the user. #### Response Example ```json { "token": "eyJhbG...", "userId": 1, "balance": 100.50, "kycLevel": 0 } ``` #### Error Response (401) - **error** (string) - "Invalid credentials" if authentication fails. ## POST /api/auth/kyc/send-otp ### Description Sends a One-Time Password (OTP) to the user's registered phone number for KYC verification. This endpoint requires authentication. ### Method POST ### Endpoint /api/auth/kyc/send-otp ### Parameters #### Query Parameters - **phoneNumber** (string) - Required - The phone number to send the OTP to. ### Response #### Success Response (200) - **message** (string) - "OTP sent" indicating the OTP was successfully dispatched. - **expiresIn** (integer) - The time in seconds until the OTP expires (e.g., 300 seconds for 5 minutes). #### Response Example ```json { "message": "OTP sent", "expiresIn": 300 } ``` ``` -------------------------------- ### SignalR GameHub - Client Connection Source: https://context7.com/boppyorca/taixiu-game/llms.txt Handles client connections to the GameHub, broadcasting the initial game session state upon connection. ```APIDOC ## OnConnectedAsync ### Description This method is automatically called when a client connects to the GameHub. It retrieves the active game session and sends its current state to the newly connected client. ### Method Server-side event handler (part of SignalR Hub) ### Endpoint `/hubs/game` (WebSocket connection) ### Parameters None (invoked automatically on connection) ### Request Example (Not applicable - server-side event) ### Response #### Event Sent to Caller - **SessionState** (object) - Contains the current state of the game session. - **sessionId** (long) - The unique identifier for the current game session. - **status** (string) - The current status of the session (e.g., 'BettingOpen', 'BettingClosed', 'RoundEnded'). - **totalTai** (decimal) - The total amount bet on 'Tai'. - **totalXiu** (decimal) - The total amount bet on 'Xiu'. - **timeRemaining** (int) - The time remaining in the current session in seconds. #### Response Example ```json { "sessionId": 12345, "status": "BettingOpen", "totalTai": 1500.75, "totalXiu": 1200.50, "timeRemaining": 30 } ``` ``` -------------------------------- ### SignalR GameHub for Real-time Communication (C#) Source: https://context7.com/boppyorca/taixiu-game/llms.txt The SignalR GameHub class handles real-time, bidirectional communication for the Taixiu game. It manages client connections, broadcasts betting updates, dice results, and chat messages to all connected clients. It relies on an IGameService for game state and uses SignalR's Hub and Client methods for communication. ```csharp public class GameHub : Hub { private readonly IGameService _gameService; // Client connects to game public override async Task OnConnectedAsync() { var session = await _gameService.GetActiveSession(); await Clients.Caller.SendAsync("SessionState", new { sessionId = session.Id, status = session.Status, totalTai = session.TotalTai, totalXiu = session.TotalXiu, timeRemaining = CalculateTimeRemaining(session) }); await base.OnConnectedAsync(); } // Broadcast bet update to all clients public async Task BroadcastBetUpdate(long sessionId, decimal totalTai, decimal totalXiu) { await Clients.All.SendAsync("BetUpdated", new { sessionId, totalTai, totalXiu, timestamp = DateTime.UtcNow }); } // Broadcast dice result with reveal public async Task BroadcastResult(GameResult result) { await Clients.All.SendAsync("RoundResult", new { sessionId = result.SessionId, dice = new[] { result.Dice1, result.Dice2, result.Dice3 }, total = result.Total, result = result.Result, isTriple = result.IsTriple, serverSeed = result.ServerSeed, // Revealed for verification clientSeed = result.ClientSeed }); } // Send chat message [Authorize] public async Task SendChatMessage(string message) { if (message.Length > 200) return; var userId = Context.UserIdentifier; var username = await GetUsername(userId); await Clients.All.SendAsync("ChatMessage", new { userId, username, message, timestamp = DateTime.UtcNow }); } } ``` -------------------------------- ### LIFO Bet Balancing Algorithm in C# Source: https://context7.com/boppyorca/taixiu-game/llms.txt Implements the LIFO bet balancing algorithm to match 'TAI' and 'XIU' bets. It sorts bets by placement order in descending order to apply LIFO for refunds. The service calculates matched amounts, identifies overflow, and processes partial or full refunds based on the bet order. ```csharp // BetBalancingService.cs - LIFO Refund Algorithm public class BetBalancingService : IBetBalancingService { public BalanceResult ProcessBalancing(List bets, long sessionId) { // Separate and sort bets by placement order (descending for LIFO) var taiBets = bets.Where(b => b.BetType == "TAI") .OrderByDescending(b => b.PlaceOrder) .ToList(); var xiuBets = bets.Where(b => b.BetType == "XIU") .OrderByDescending(b => b.PlaceOrder) .ToList(); decimal totalTai = taiBets.Sum(b => b.Amount); decimal totalXiu = xiuBets.Sum(b => b.Amount); // Calculate matched amount (minimum of both sides) decimal matchedAmount = Math.Min(totalTai, totalXiu); // Determine overflow side List overflowBets; decimal overflowAmount; if (totalTai > totalXiu) { overflowBets = taiBets; overflowAmount = totalTai - totalXiu; } else { overflowBets = xiuBets; overflowAmount = totalXiu - totalTai; } // Process LIFO refunds var refunds = new List(); decimal remainingRefund = overflowAmount; foreach (var bet in overflowBets) { if (remainingRefund <= 0) break; decimal refundAmount = Math.Min(bet.Amount, remainingRefund); decimal acceptedAmount = bet.Amount - refundAmount; bet.AcceptedAmount = acceptedAmount; bet.RefundAmount = refundAmount; bet.Status = refundAmount > 0 ? "PARTIAL_REFUND" : "ACCEPTED"; if (refundAmount > 0) { refunds.Add(new Refund { BetId = bet.Id, UserId = bet.UserId, RefundAmount = refundAmount, AcceptedAmount = acceptedAmount }); } remainingRefund -= refundAmount; } return new BalanceResult { SessionId = sessionId, MatchedAmount = matchedAmount, TotalTai = totalTai - overflowAmount, TotalXiu = Math.Min(totalTai, totalXiu), Refunds = refunds }; } } // Example scenario: // TAI bets: [100K @t1, 200K @t2, 300K @t3, 400K @t4] = 1,000K total // XIU bets: [500K @t1, 300K @t2] = 800K total // Overflow: TAI side, 200K excess // LIFO refund: 400K bet @t4 refunded 200K (accepted 200K) // Final matched: 800K each side ``` -------------------------------- ### Send OTP for KYC Verification API (.NET/C#) Source: https://context7.com/boppyorca/taixiu-game/llms.txt Sends a One-Time Password (OTP) to a user's phone number for KYC verification. This endpoint requires authentication and uses a cache to store the OTP for a limited time. It depends on a caching mechanism and an SMS service. ```csharp // AuthController.cs - KYC Verification // POST /api/auth/kyc/send-otp [HttpPost("kyc/send-otp")] [Authorize] public async Task SendOtp(string phoneNumber) { var userId = GetCurrentUserId(); var otp = GenerateOtp(); // 6-digit code await _cache.SetAsync($"otp:{userId}", otp, TimeSpan.FromMinutes(5)); await _smsService.SendAsync(phoneNumber, $"Your OTP: {otp}"); return Ok(new { message = "OTP sent", expiresIn = 300 }); } ``` -------------------------------- ### POST /api/wallet/withdraw Source: https://context7.com/boppyorca/taixiu-game/llms.txt Requests a withdrawal of funds to the user's bank account. Requires KYC Level 1 verification. ```APIDOC ## POST /api/wallet/withdraw ### Description Requests a withdrawal of funds to the user's bank account. Requires KYC Level 1 verification and sufficient balance. ### Method POST ### Endpoint /api/wallet/withdraw ### Parameters #### Path Parameters None #### Query Parameters None #### Request Body - **request** (object) - Required - Contains withdrawal details. - **Amount** (decimal) - Required - The amount to withdraw. ### Request Example ```json { "Amount": 100000.00 } ``` ### Response #### Success Response (200) - **transactionId** (string) - The unique identifier for the withdrawal transaction. - **newBalance** (decimal) - The user's balance after the withdrawal. #### Response Example ```json { "transactionId": "f0e9d8c7-b6a5-4321-fedc-ba9876543210", "newBalance": 50000.50 } ``` #### Error Response (400) - **error** (string) - Description of the error, e.g., "KYC verification required for withdrawal" or "Insufficient balance". #### Error Response Example ```json { "error": "Insufficient balance" } ``` ``` -------------------------------- ### Bet Balancing Algorithm (C#) Source: https://github.com/boppyorca/taixiu-game/blob/main/implementation_plan.md Calculates the balance of bets for 'TAI' and 'XIU' sides. It determines matched amounts, identifies the overflow side, and calculates necessary refunds, prioritizing refunds from the last bets placed (LIFO). ```csharp // Pseudocode public BalanceResult BalanceBets(List bets) { decimal totalTai = bets.Where(b => b.Type == "TAI").Sum(b => b.Amount); decimal totalXiu = bets.Where(b => b.Type == "XIU").Sum(b => b.Amount); decimal matchedAmount = Math.Min(totalTai, totalXiu); decimal overflowSide = totalTai > totalXiu ? "TAI" : "XIU"; decimal overflowAmount = Math.Abs(totalTai - totalXiu); // Refund from LAST bets first (LIFO) var refunds = CalculateRefunds(bets, overflowSide, overflowAmount); return new BalanceResult(matchedAmount, refunds); } ``` -------------------------------- ### C# Game API Endpoints: Betting and Game State Source: https://context7.com/boppyorca/taixiu-game/llms.txt This C# code defines API endpoints for a Taixiu game. It includes methods to retrieve the current game session details (session ID, status, betting totals, time remaining, and seed hash), place bets (validating bet type, amount, and betting phase), and retrieve historical game results. It utilizes asynchronous operations and interacts with services for game logic and database access. Dependencies include ASP.NET Core framework, Entity Framework Core, and SignalR for real-time updates. ```csharp // GameController.cs - Betting & Game State // GET /api/game/current [HttpGet("current")] public async Task GetCurrentSession() { var session = await _gameService.GetActiveSession(); return Ok(new { sessionId = session.Id, status = session.Status, // BETTING, LOCKING, ROLLING, COMPLETED totalTai = session.TotalTai, totalXiu = session.TotalXiu, timeRemaining = CalculateTimeRemaining(session), seedHash = session.Result?.SeedHash // Provably Fair commitment }); // Response example: // { // "sessionId": 12345, // "status": "BETTING", // "totalTai": 500000000, // "totalXiu": 450000000, // "timeRemaining": 35, // "seedHash": "a1b2c3..." // } } // POST /api/game/bet [HttpPost("bet")] [Authorize] public async Task PlaceBet(BetRequest request) { // Request: { "betType": "TAI", "amount": 100000 } var userId = GetCurrentUserId(); var session = await _gameService.GetActiveSession(); if (session.Status != "BETTING") return BadRequest(new { error = "Betting is closed" }); if (request.Amount < 1000) return BadRequest(new { error = "Minimum bet is 1,000 VND" }); var result = await _betService.PlaceBet(userId, session.Id, request.BetType, request.Amount); if (!result.Success) return BadRequest(new { error = result.ErrorMessage }); // Broadcast via SignalR await _hubContext.Clients.All.SendAsync("BetPlaced", new { sessionId = session.Id, totalTai = session.TotalTai + (request.BetType == "TAI" ? request.Amount : 0), totalXiu = session.TotalXiu + (request.BetType == "XIU" ? request.Amount : 0) }); return Ok(new { betId = result.BetId, newBalance = result.NewBalance }); } // GET /api/game/history [HttpGet("history")] public async Task GetHistory(int count = 50) { var results = await _db.GameResults .OrderByDescending(r => r.SessionId) .Take(count) .Select(r => new { sessionId = r.SessionId, dice = new[] { r.Dice1, r.Dice2, r.Dice3 }, total = r.Total, result = r.Result, // TAI, XIU, or TRIPLE isTriple = r.IsTriple }) .ToListAsync(); return Ok(results); } ``` -------------------------------- ### C# Payout Calculation and Balance Update Source: https://context7.com/boppyorca/taixiu-game/llms.txt Calculates payouts for game rounds based on bet types and results, updates bet statuses (WON, LOST), and applies winnings or deducts losses. It also handles refunds and updates user balances, logging each transaction. Finally, it broadcasts individual bet results to users via SignalR. ```csharp // PayoutService.cs - Winner Calculation & Balance Updates public class PayoutService : IPayoutService { private const decimal PAYOUT_RATIO = 2.0m; // 1:1 = bet 1, win 1, receive 2 public async Task ProcessPayouts(long sessionId, GameResult result) { var bets = await _db.Bets .Where(b => b.SessionId == sessionId && b.Status != "REFUNDED") .ToListAsync(); using var tx = await _db.Database.BeginTransactionAsync(); foreach (var bet in bets) { decimal payout = 0; if (result.IsTriple) { // TRIPLE (Bão): House wins all bet.Status = "LOST"; bet.WinAmount = 0; } else if (bet.BetType == result.Result) { // Winner: AcceptedAmount * 2 (original bet + winnings) bet.Status = "WON"; bet.WinAmount = bet.AcceptedAmount * PAYOUT_RATIO; payout = bet.WinAmount; } else { // Loser: Bet already deducted bet.Status = "LOST"; bet.WinAmount = 0; } // Always return refunded amount (from bet balancing) payout += bet.RefundAmount; if (payout > 0) { await UpdateBalance(bet.UserId, payout, $"Payout session #{sessionId}"); } } await _db.SaveChangesAsync(); await tx.CommitAsync(); // Broadcast individual results to users foreach (var bet in bets) { await _hubContext.Clients.User(bet.UserId.ToString()) .SendAsync("BetResult", new { sessionId = sessionId, betType = bet.BetType, amount = bet.Amount, acceptedAmount = bet.AcceptedAmount, refundAmount = bet.RefundAmount, winAmount = bet.WinAmount, status = bet.Status, result = result.Result }); } } private async Task UpdateBalance(int userId, decimal amount, string note) { var user = await _db.Users.FindAsync(userId); var balanceBefore = user.Balance; user.Balance += amount; _db.Transactions.Add(new Transaction { UserId = userId, Type = amount > 0 ? "WIN" : "LOSS", Amount = Math.Abs(amount), BalanceBefore = balanceBefore, BalanceAfter = user.Balance, Status = "COMPLETED", Note = note }); } } // Result Examples: // Dice [3, 4, 4] = Total 11 = TAI // Dice [2, 3, 4] = Total 9 = XIU // Dice [5, 5, 5] = Total 15 = TRIPLE (not TAI!) // Dice [1, 1, 1] = Total 3 = TRIPLE (not XIU!) ``` -------------------------------- ### Concurrent Bet Handling with Optimistic Locking (C#) Source: https://github.com/boppyorca/taixiu-game/blob/main/algorithm_design.md Addresses race conditions in concurrent bet placement by using a SemaphoreSlim for locking and optimistic locking within database transactions. It ensures that user balances are correctly updated by acquiring an exclusive lock on the user record before modification. This prevents multiple bets from being processed simultaneously against an insufficient balance. ```csharp public class BetService { private readonly SemaphoreSlim _betLock = new(1, 1); public async Task PlaceBet(int userId, string betType, decimal amount) { await _betLock.WaitAsync(); try { // Trong transaction using var tx = await _db.Database.BeginTransactionAsync(); // Get user with lock var user = await _db.Users .FromSqlRaw("SELECT * FROM Users WITH (UPDLOCK) WHERE Id = {0}", userId) .FirstAsync(); // Validate if (user.Balance < amount) return BetResult.InsufficientBalance(); // Deduct user.Balance -= amount; // Create bet var bet = new Bet { ... }; _db.Bets.Add(bet); await _db.SaveChangesAsync(); await tx.CommitAsync(); return BetResult.Success(bet); } finally { _betLock.Release(); } } } ``` -------------------------------- ### React SignalR Hook for Game State Management (JavaScript) Source: https://context7.com/boppyorca/taixiu-game/llms.txt The useSignalR hook provides a React-friendly interface for connecting to the SignalR game hub. It manages the SignalR connection lifecycle, automatically reconnects, and updates the component's state based on messages received from the server, such as session state, bet updates, round results, and chat messages. ```javascript import * as signalR from "@microsoft/signalr"; export function useSignalR() { const [connection, setConnection] = useState(null); const [gameState, setGameState] = useState({}); useEffect(() => { const conn = new signalR.HubConnectionBuilder() .withUrl("/hubs/game", { accessTokenFactory: () => getToken() }) .withAutomaticReconnect() .build(); conn.on("SessionState", (state) => setGameState(state)); conn.on("BetUpdated", (data) => { setGameState(prev => ({ ...prev, totalTai: data.totalTai, totalXiu: data.totalXiu })); }); conn.on("RoundResult", (result) => { console.log("Dice:", result.dice, "Result:", result.result); // Trigger dice animation, update balance }); conn.on("ChatMessage", (msg) => console.log(`${msg.username}: ${msg.message}`)); conn.start().then(() => setConnection(conn)); return () => conn.stop(); }, []); return { connection, gameState }; } ``` -------------------------------- ### Game Session State Machine Logic (C#) Source: https://github.com/boppyorca/taixiu-game/blob/main/algorithm_design.md Implements the game session state machine, managing transitions between betting, locking, rolling, and completion phases. It handles delays for phase durations and calls services for balancing, result generation, and payouts. Dependencies include balancing and payout services. ```csharp public class GameSessionStateMachine { public async Task RunSession(long sessionId) { // BETTING PHASE (50 seconds) await SetState(sessionId, "BETTING"); await Task.Delay(TimeSpan.FromSeconds(50)); // LOCKING PHASE await SetState(sessionId, "LOCKING"); var bets = await GetBets(sessionId); var balanceResult = _balancingService.ProcessBalancing(bets, sessionId); await ApplyRefunds(balanceResult.Refunds); // ROLLING PHASE (10 seconds) await SetState(sessionId, "ROLLING"); var (dice, result) = GenerateResult(sessionId); await BroadcastResult(dice); await Task.Delay(TimeSpan.FromSeconds(10)); // Animation time // COMPLETED await SetState(sessionId, "COMPLETED"); await _payoutService.ProcessPayouts(sessionId, result, bets); // Start next round immediately _ = StartNewSession(); } } ``` -------------------------------- ### POST /api/game/bet Source: https://context7.com/boppyorca/taixiu-game/llms.txt Allows an authenticated user to place a bet on the current game session. Supports betting on 'Tai' or 'Xiu' with a specified amount. ```APIDOC ## POST /api/game/bet ### Description Allows an authenticated user to place a bet on the current game session. Supports betting on 'Tai' or 'Xiu' with a specified amount. ### Method POST ### Endpoint /api/game/bet ### Parameters #### Path Parameters None #### Query Parameters None #### Request Body - **betType** (string) - Required - The type of bet ('TAI' or 'XIU'). - **amount** (integer) - Required - The amount to bet. Minimum bet is 1,000 VND. ### Request Example ```json { "betType": "TAI", "amount": 100000 } ``` ### Response #### Success Response (200) - **betId** (integer) - The unique identifier for the placed bet. - **newBalance** (integer) - The user's updated balance after placing the bet. #### Error Response (400) - **error** (string) - Description of the error (e.g., 'Betting is closed', 'Minimum bet is 1,000 VND', or specific bet placement errors). #### Response Example (Success) ```json { "betId": 98765, "newBalance": 9900000 } ``` ``` -------------------------------- ### SignalR GameHub - Broadcast Bet Update Source: https://context7.com/boppyorca/taixiu-game/llms.txt Broadcasts betting updates to all connected clients, reflecting the current total bets on 'Tai' and 'Xiu'. ```APIDOC ## BroadcastBetUpdate ### Description This method is called to broadcast any changes in the total bets placed on 'Tai' and 'Xiu' to all connected clients in real-time. ### Method `BroadcastBetUpdate` (Server-invoked client method) ### Endpoint `/hubs/game` (WebSocket connection) ### Parameters - **sessionId** (long) - Required - The ID of the game session to which the bet update applies. - **totalTai** (decimal) - Required - The updated total amount bet on 'Tai'. - **totalXiu** (decimal) - Required - The updated total amount bet on 'Xiu'. ### Request Example (Server-side invocation) ### Response #### Event Sent to All Clients - **BetUpdated** (object) - Contains the latest betting totals for a session. - **sessionId** (long) - The ID of the game session. - **totalTai** (decimal) - The updated total amount bet on 'Tai'. - **totalXiu** (decimal) - The updated total amount bet on 'Xiu'. - **timestamp** (string) - The UTC timestamp when the update occurred. #### Response Example ```json { "sessionId": 12345, "totalTai": 1650.25, "totalXiu": 1300.00, "timestamp": "2023-10-27T10:30:00Z" } ``` ``` -------------------------------- ### SignalR GameHub - Send Chat Message Source: https://context7.com/boppyorca/taixiu-game/llms.txt Allows authenticated users to send chat messages to all connected clients. ```APIDOC ## SendChatMessage ### Description This method enables authenticated users to send chat messages that will be broadcast to all connected clients in the game hub. ### Method `SendChatMessage` (Client-invoked server method) ### Endpoint `/hubs/game` (WebSocket connection) ### Parameters - **message** (string) - Required - The content of the chat message. Maximum length is 200 characters. ### Request Example ```json { "message": "Hello everyone! Good luck!" } ``` ### Response #### Event Sent to All Clients - **ChatMessage** (object) - Contains details of a received chat message. - **userId** (string) - The unique identifier of the user who sent the message. - **username** (string) - The display name of the user. - **message** (string) - The content of the chat message. - **timestamp** (string) - The UTC timestamp when the message was sent. #### Response Example ```json { "userId": "user123", "username": "Gamer1", "message": "Hello everyone! Good luck!", "timestamp": "2023-10-27T10:35:00Z" } ``` ``` -------------------------------- ### Generate Verifiable Dice Rolls using C# Source: https://context7.com/boppyorca/taixiu-game/llms.txt Implements a provably fair Random Number Generation (RNG) service in C#. It generates server seeds and commitment hashes before a round, and then uses these seeds along with client seeds and round IDs to produce dice rolls. Includes client-side verification logic to ensure results are not tampered with. ```csharp // RNGService.cs - Provably Fair Random Number Generation using System; using System.Linq; using System.Security.Cryptography; using System.Text; using System.Threading.Tasks; public interface IRNGService { (string ServerSeed, string SeedHash) GenerateSeeds(); int[] GenerateDice(string serverSeed, string clientSeed, long roundId); bool VerifyResult(string serverSeed, string publishedHash, string clientSeed, long roundId, int[] claimedDice); } public class ProvablyFairRNG : IRNGService { // Generate seeds for a new session public (string ServerSeed, string SeedHash) GenerateSeeds() { // Generate 64-character random hex server seed var bytes = new byte[32]; using var rng = RandomNumberGenerator.Create(); rng.GetBytes(bytes); string serverSeed = Convert.ToHexString(bytes); // Create commitment hash (published BEFORE round) string seedHash = ComputeSHA256(serverSeed); return (serverSeed, seedHash); } // Generate dice results from seeds public int[] GenerateDice(string serverSeed, string clientSeed, long roundId) { string combined = $"{serverSeed}:{clientSeed}:{roundId}"; byte[] hash = SHA256.HashData(Encoding.UTF8.GetBytes(combined)); int[] dice = new int[3]; for (int i = 0; i < 3; i++) { // Use 4 bytes per dice for uniform distribution uint value = BitConverter.ToUInt32(hash, i * 4); dice[i] = (int)(value % 6) + 1; // Results: 1-6 } return dice; } // Client-side verification public bool VerifyResult(string serverSeed, string publishedHash, string clientSeed, long roundId, int[] claimedDice) { // Verify hash commitment if (ComputeSHA256(serverSeed) != publishedHash) return false; // Recalculate and compare dice int[] expectedDice = GenerateDice(serverSeed, clientSeed, roundId); return expectedDice.SequenceEqual(claimedDice); } // Helper to compute SHA256 hash private string ComputeSHA256(string input) { byte[] bytes = Encoding.UTF8.GetBytes(input); byte[] hash = SHA256.HashData(bytes); return Convert.ToHexString(hash); } } // Placeholder for GameResult and related methods (assuming they exist elsewhere) public class GameResult { public long SessionId { get; set; } public int Dice1 { get; set; } public int Dice2 { get; set; } public int Dice3 { get; set; } public int Total { get; set; } public string Result { get; set; } public bool IsTriple { get; set; } public string ServerSeed { get; set; } public string ClientSeed { get; set; } } public class GameService { private readonly IRNGService _rng; public GameService(IRNGService rng) { _rng = rng; } // Dummy method to get session, replace with actual implementation private Task GetSession(long sessionId) { // Replace with actual session retrieval logic return Task.FromResult(new object()); } // Usage in GameService public async Task GenerateRoundResult(long sessionId) { var session = await GetSession(sessionId); var (serverSeed, _) = _rng.GenerateSeeds(); // SeedHash already published string clientSeed = "public-random-string"; // Can be user-provided int[] dice = _rng.GenerateDice(serverSeed, clientSeed, sessionId); int total = dice.Sum(); string result; bool isTriple = dice[0] == dice[1] && dice[1] == dice[2]; if (isTriple) result = "TRIPLE"; // House wins all (Bão) else if (total >= 11) result = "TAI"; // Over: 11-17 else result = "XIU"; // Under: 4-10 return new GameResult { SessionId = sessionId, Dice1 = dice[0], Dice2 = dice[1], Dice3 = dice[2], Total = total, Result = result, IsTriple = isTriple, ServerSeed = serverSeed, ClientSeed = clientSeed }; } } ```