title: 'Yahu Server架構'
-

Server 架構:

程式流程

GameLobby配桌完後先執行

tableObj.gameStatus = STATUS_BET_TIME; // 切成押注
tableObj.readFishScript(tableObj.gameMode); // 產魚腳本
tableObj.runLogicStart(); // 啟動計時器

進入遊戲和離開遊戲, 使用的檔案

檔案名稱 動作 觸發函式 內容
gameHandler 遊戲封包 Handler.prototype.enterGame = function(msg, session, callback) 進入遊戲封包=>檢查服務器是否在維護中
檢查是否已在遊玩其他遊戲
查詢或創建是否有gameuser
更新登入紀錄
寫入進入遊戲紀錄
取得玩家金幣數量
建立gameUser
gameUserManager 會員管理 GameUserManager.join = function(userId, yahuId, money) 進入大廳時, 建立會員物件和寫入Redis => 'GameUsers', 離開遊戲刪除redis
gameUser 會員管理 var GameUser = function(pomelo_app, userId, yahuId, money) 創造GameUser 物件
gameLobby 配桌系統 GameLobby.enterGame = function(yahuId, lobbyId, callback) 進入遊戲時, 將玩家放入配對列表 self.enterGameList[yahuId] = lobbyId
gameLobby 配桌系統 GameLobby.matchHandling = function(self) 定時排程, 處理配桌工作(不斷呼叫自己的遞迴函式)
gameTable 桌內活動 GameTable.joinTable = function(gameUser, pos, callback) 處理加入此桌工作
gameTable 桌內活動 GameTable.RandGrpID = function() 產生局號(格式為隨機26+9), grpId=IK-K7V9Y
gameHandler 遊戲封包 Handler.bet = function(msg, session, callback) 發射子彈, 單純蒐集封包資料, 沒處理邏輯
gameTable 桌內活動 GameTable.playerBet = function(pos, fortAngle, betLevel, bulletKind, lockFishId, callback) 處理射子彈邏輯, 扣除金額產生,子彈編號, USER_SHOT
gameTable 桌內活動 GameTable.playerCatchFish = function(pos, bulletId, fishIdList, linkFishList, callback) 玩家中魚處理邏輯, BULLET_HIT_FISH
gameTable 桌內活動 GameTable.runResult = function(pos, useMinPool, bulletIsNull, bulletData, validFishId, validFishDataId, validLinkFishId, validLinkFishDataId, linkageExplosionEnabled, linkFishEnabled, pirateShipEnabled) 進行中魚計算
gameHandler 遊戲封包 Handler.leaveTable = function(msg, session, callback) 呼叫大廳的函式 gameLobby.leaveGame
gameTable 桌內活動 GameTable.userLeave = function (pos) userLeaveFlag[pos]=true, 最後一個離開玩家,先將table上鎖
gameUserManager 會員管理 GameUserManager.join = function(userId, yahuId, money) 離開大廳時, 刪除Redis => 'GameUsers'

程式物件說明

GameUser(描述登入會員的基本物件) 說明

欄位名稱 資料型態 細節 內容
app Obj pomelo_app 跟pomelo物件綁定, 可以取得相關物件 this.app.get("key");
pushService Obj this.PushService = this.app.get('PushService') 配置訊息管理器
gameId Obj this.app.get("name") (遊戲配置)server name
gameConfig Obj conf_sys.GAME_DATA[this.gameId] Game Configuration
gameCategoryId Obj this.gameConfig.game_category_id 待補
userId string this.userId = userId GPK平台的userId
yahuId int this.yahuId = yahuId yahu自己生產的userId
money float this.money = money 看資料庫存的是float
lobbyId Obj this.lobbyId = 0 User所在的大廳編號
tableId string this.tableId = "" User所在桌子編號

GameUser(函式說明)
| 函式名稱 | 細節 | 內容 |
| | | | |
| charge | function(data, callback) | 線上充值
會送出 self.pushService.pushByUID(self.yahuId, "CHARGE", message, utils.doNothing) |

GameUserManager(管理登入會員的物件) 說明

欄位名稱 資料型態 細節 內容
app Obj this.app = pomelo_app 同上
gameId Obj this.gameId = gameId 登入的遊戲
gpkRedis Obj this.gpkRedis = this.app.get('gpkRedis') redis控制器
gameUsers Obj this.gameUsers = {} 登入的會員物件

GameUserManager(函式說明)
| 函式名稱 | 細節 | 內容 |
| | | | |
| clearRds | function() | 清除redis內的資料 key='GameUsers', 依照該玩家的yahuId去清除所有資料 |
| join | function(userId, yahuId, money) | 會員加入 |
| get | function(yahuId) | 取得會員 |
| leave | function(yahuId, callback) | 會員離開, 清除redis內的資料 key=‘GameUsers’, 依照該玩家的yahuId去清除資料 |
| count | function() | 取得目前會員數 |

GameLobby(描述進入和離開遊戲的基本物件) 說明

欄位名稱 資料型態 細節 內容
app Obj pomelo_app 同上
pushService Obj this.PushService = this.app.get('PushService') 配置訊息管理器
gameId Obj this.gameId = this.app.get("name") server id
gameConfig Obj this.gameConfig = conf_sys.GAME_DATA[this.gameId] Game Configuration
theme Obj this.theme = theme;
gpkRedis Obj this.gpkRedis = this.app.get("gpkRedis") redis控制器
enterGameList Obj this.enterGameList = {} 遊戲配對相關結構
redisKey Obj this.redisKey = this.gameConfig.redisKey Table ID相關設定
tableIDPrefix Obj this.tableIDPrefix = utils.dateFormat(new Date(), "yyyyMMdd") Table ID相關設定
tableIdx int this.tableIdx = 0 Table ID相關設定
lobbyNum int this.lobbyNum = this.gameConfig.lobbyNum config/system.json 內的描述的大廳數量
lobbyUsers Obj this.lobbyUsers[i] = [] 大廳內的User陣列
lobbyTables Obj this.lobbyTables[i] = {} 大廳內的桌子陣列
match_mate_timer Obj this.match_mate_timer = null

GameLobby(函式說明)
| 函式名稱 | 細節 | 內容 |
| | | | |
| start | function() | 啟動定時器, 一秒觸發 'matchHandling' 函式 一次 |
| enterGame | function(yahuId, lobbyId, callback) | 將玩家放入配對列表, self.enterGameList[yahuId] = lobbyId; |
| matchHandling | function(self) | 處理配對列表 1: 使用 TABLE_LIMIT:+GameId 搜尋redis內的資料
2:取得目前進入遊戲結構列表
3:處理配對 tableObj.joinTable
|
| leaveGame | function(yahuId, lobbyId, tableId, callback) | 離開遊戲檢查相關結構做清除
1: tableObj.userLeave(pos); // 清除GameTable的玩家
2: self.lobbyUsers[lobbyId] //清除玩家在廳內的狀態
3: 清除玩家GameUserManager資料 |
| broadcastHandling | function(lobbyId, data) | 處理廣播訊息
訊息塞入 self.PushService.pushByList(self.lobbyUsers[lobbyId], "GAME_BROADCAST", data, utils.doNothing) |
| charge | function (yahuId, lobbyId, tableId, amount, callback) | 線上充值, 呼叫GameTable的儲值函式 tableObj.charge(pos, amount) |

GameTable(遊戲實際流程和邏輯的物件) 說明

欄位名稱 資料型態 細節 內容
app Obj pomelo_app 同上
pushService Obj this.PushService = this.app.get('PushService') 配置訊息管理器
gameLogManager Obj this.gameLogManager = this.app.get('GameLogManager') log物件, 寫賽果進logDb功能
tableId Obj this.tableId = tableId 設定桌號
lobbyId Obj this.lobbyId = lobbyId 設定大廳Id
theme Obj this.theme = theme 關卡描述物件
gameStatus int this.gameStatus = STATUS_WAIT_PLAY 遊戲狀態 1:遊戲中 2:押注時間 3:新局 4:等待展演轉場表演
betTime int this.betTime = 300 押注時間
showTime int this.showTime = 3 展演時間, 目前三秒
maxLaserGun Obj this.maxLaserGun = this.theme.Controll_Value.Laser_max_execute_rate 雷射砲啟動倍數上限
laserGunRate Obj this.laserGunRate = this.theme.Controll_Value.Laser_pool_ratio 雷射砲抽取%數
poolProb Obj this.poolProb = this.theme.Controll_Value.All_pool_rate 進水量
normalPoolRate Obj this.normalPoolRate = this.theme.Controll_Value.Normal_pool_rate 一般水池%數
armsPoolRate Obj this.armsPoolRate = this.theme.Controll_Value.Weapon_pool_rate 特殊武器水池%數
maxWin Obj this.maxWin = this.theme.Controll_Value.Max_win 最大贏分限制
betRange Obj this.betRange = this.theme.Bet_Ratio[lobbyId.toString()] 注碼範圍
betHitValue Obj this.betHitValue = this.theme.Bet_Hit_Value[lobbyId.toString()] 注碼對應魚量
broadcastList Obj self.broadcastList.push(data) 廣播列表陣列, 分全廳和該桌廣播
maxUserNum int this.maxUserNum = 4 玩家上限, 魚機四人
YahuIdList Array this.YahuIdList = [] 同桌玩家ID陣列
lockState bool this.lockState = false Table上鎖狀態(若上鎖中,不分配玩家進入此桌)(BOSS出場,海盜船出場都會上鎖)
leaveLockState bool this.leaveLockState = false 最後一個玩家離開時的上鎖狀態
fishIdCount int this.fishIdCount = 0 魚ID Count計數器
gameMode int this.gameMode = NORMAL_MODE 遊戲模式 1:普通 2:陣型 3:海盜船
gameNextMode int this.gameNextMode = 0 下一局遊戲模式
gameMap Obj this.gameMap = parseInt(Math.random() * this.theme.BG_Control.formation.BG_count + 1) 遊戲地圖
gameNextMap Obj this.gameNextMap = 0 下一局遊戲地圖
gmaeSong Obj this.gmaeSong = parseInt(Math.random() * this.theme.BG_Control.formation.BGM_count + 1) 遊戲音樂
gameNextSong int this.gameNextSong = 0 下一局遊戲音樂
gameModeCount int this.gameModeCount = 0 局數計數
bossEndTime int this.bossEndTime = 0 BOSS結束時間
bossCome bool this.bossCome = false BOSS出現
onRunTimer Obj this.onRunTimer = null 遊戲計時器
runCount int this.runCount = 1 timer count
fishScriptTime int this.fishScriptTime = 0 出魚腳本timer count的最大值
fishScript Obj this.fishScript = {} 出魚腳本
fishScriptWithTime Obj this.fishScriptWithTime = {} 出魚腳本ID對應上的時間
allDeadFish Obj this.allDeadFish = {} 全部死魚物件
bossScript Obj this.bossScript = {} boss腳本物件
userPoolMin Obj this.userPoolMin = {} 玩家個人小水池
userPoolMax Obj this.userPoolMax = {} 玩家個人大水池
userBulletData Obj this.userBulletData = {}; 玩家所有子彈輸贏資訊(統計這發子彈打死什麼魚, 贏分和賠率多少, 最後定時寫入賽果內)
userBulletDataNow Obj this.userBulletDataNow = {} 玩家目前場上子彈資訊(單純紀錄場上存在的子彈資訊)
userCatchFishData Obj this.userCatchFishData = {} 玩家打死魚相關資訊

GameTable(函式說明)
| 函式名稱 | 細節 | 內容 |
| | | | |
| findTableEmptyPos | function() | 尋找Table空位 |
| findTablePos | function (yahuId) | 尋找玩家在Table中的Pos |
| joinTable | function (gameUser, pos, callback) | 玩家進入Table
1:加入前最後確認是否有上鎖
2:取得小水池
3:取得大水池
4:入桌基本資料初始化
5:PushService.pushByUID Key='USER_JOIN_TABLE'
6:廣播給所有user table中所有user狀況 key='TABLE_USER_INFO' |
| charge | function (pos, amount) | 玩家遊戲內充值 PushService.pushByUID Key='CHARGE'
|
| userLeave | function (pos) | 玩家離開遊戲
1:移除self.YahuIdList陣列
2:設定離開旗標 self.userLeaveFlag[pos] = true
|
| userLeaveHandling | function (pos) | 玩家離開遊戲實際處理
1:檢查中魚狀態, 如果有不允許往下執行
2:檢查寫賽果狀態, 如果有不允許往下執行
3:刪除場上子彈+同步給場上玩家
4:冰凍狀態解除+同步給場上玩家
5:緩速狀態解除+同步給場上玩家
6:寫賽果
7:清除剩餘資料 self.userXXXXX = xxxx
8:PushService 通知玩家離開 key='USER_LEAVE_TABLE'
9:PushService 通知其他玩家哪個位置的玩家離開 key='LEAVE_POS'
|
| playerUseItem | function (pos, itemType, callback) | 玩家使用道具
1:確認gameStatus是否為可以使用道具狀態
2:確認gameMode是否為可以使用道具狀態
3:確認道具是否為可使用狀態,若是處於冰凍狀態所有道具都不可使用
4:緩速要是玩家自己的在啟用中則不能再次使用
5:確認道具數量
6:減去道具數量
7:啟用效果
8:同步給場上玩家 PushService.pushByList key='USE_ITEM'
|
| playerBet | function (pos, fortAngle, betLevel, bulletKind, lockFishId, callback) | 玩家發射子彈 |
| playerCatchFish | function (pos, bulletId, fishIdList, linkFishList, callback) | 玩家中魚處理 |
| runResult | function (pos, useMinPool, bulletIsNull, bulletData, validFishId, validFishDataId, validLinkFishId, validLinkFishDataId, linkageExplosionEnabled, linkFishEnabled, pirateShipEnabled) | 中魚計算 |
| readFishScript | function (fishScriptMode) | 產出出魚腳本 |
| randomRunFish | function (timeCount, kindAppearList) | 產出魚物件 (一般模式) |
| onRunLogic | function (self) | 遊戲計時器 |
| runLogicStart | function() | 啟動計時器 |
| broadcastHandling | function() | 處理廣播訊息 |
| writeRecord | function(pos) | 寫賽果 |
| userPointCompute | function(pos, computePoint, action) | user point 更動 |
| userDisplayPointCompute | function((pos, computePoint, action) | user display point 更動 |
| RandGrpID | function() | 產生局號 |

傳給Client的封包

Cmd=TABLE_USER_INFO (玩家進入遊戲)

欄位名稱 資料型態 內容
tablePosNickName Obj 登入會員資料(有四個玩家資料)
tablePosPoint Obj 登入會員點數(有四個玩家資料)
tableLaserGunValue Obj 雷射炮充能狀態轉為百分比(有四個玩家資料)
{
	"tablePosNickName":{
		"1":"Leo",
		"2":null,
		"3":null,
		"4":null},
	"tablePosPoint":{
		"1":998194,
		"2":0,
		"3":0,
		"4":0},
	"tableLaserGunValue":{
		"1":0,
		"2":0,
		"3":0,
		"4":0}
}

Cmd=FISH_APPEAR (生產一般魚)

欄位名稱 資料型態 內容
id int 腳本中的ID
data_id int 產魚腳本資料中的Fish_id
fish_kind string 魚種
time int 魚出現的時間
speed int 游動速度
sub_id int 魚群用ID
path_id int 游動路徑Id
{
	"appearFishData":[
		{"id":1,"data_id":4,"fish_kind":"F","time":2,"speed":103,"sub_id":0,"path_id":1010},
		{"id":2,"data_id":8,"fish_kind":"F","time":2,"speed":114,"sub_id":1,"path_id":1071},
		{"id":3,"data_id":8,"fish_kind":"F","time":2,"speed":109,"sub_id":2,"path_id":1071},
		{"id":4,"data_id":8,"fish_kind":"F","time":2,"speed":110,"sub_id":3,"path_id":1071},
		{"id":5,"data_id":8,"fish_kind":"F","time":2,"speed":115,"sub_id":4,"path_id":1071},
		{"id":6,"data_id":8,"fish_kind":"F","time":2,"speed":114,"sub_id":5,"path_id":1071}
	]
}

Cmd=USER_SHOT (射子彈)

欄位名稱 資料型態 內容
userPos int 座位Id
bulletId int 子彈ID 規則是 yahuId + 流水號
fortAngle int 子彈角度
betLevel int 子彈等級
userPoint int 腳本中的ID
userLaserGunValue int 雷射槍的百分比能量
dirllMaxCount int 鑽頭炮相關資料(待查)
lockFishId int 待查
{
	"userPos":"1",
	"bulletId":"2-1",
	"fortAngle":1.0061878773508814,
	"betLevel":1,"bulletKind":1,
	"userPoint":999999,
	"userLaserGunValue":0,
	"dirllMaxCount":0,
	"lockFishId":-1
}

Cmd=BULLET_HIT_FISH (玩家中魚處理)

欄位名稱 資料型態 內容
userPos int 座位Id
bulletId int 子彈ID 規則是 yahuId + 流水號
FishId int 打中魚的FishId
{
	"userPos":"1",
	"bulletId":"2-1",
	"fishId":4
}

Cmd=CATCH_FISH (中魚計算, 魚死會收到此封包)

欄位名稱 資料型態 內容
userPos int 座位Id
fishDeadCount int 魚死資料的數量
fishDeadData int 魚死資料
userPoint int 座位目前的金額
{
	"userPos":"1",
	"fishDeadCount":1,
	"fishDeadData":{
		"4":{"data_id":8,"fish_kind":"F","win":8,"dead_odds":0,"get_item":false}
	},
	"userPoint":1000007
}

Cmd=READY_NEXT_MODE (出魚結束,同步換關訊息)

一秒會送一次, 共五次給Client, nextSec 5~1 的封包

欄位名稱 資料型態 內容
nextMode int 下一關的出魚模式 遊戲模式 1:普通魚 2:陣型魚 3:海盜船
nextSec int 下一關的秒數
{
	"nextMode": 1,
	"nextSec": 5
}

產魚流程:

遊戲狀態

名稱 資料內容 內容
STATUS_WAIT_PLAY 1 剛從大廳進入桌時, 會瞬間切到此狀態
STATUS_BET_TIME 2 玩家可以押注, 此流程也會按照產魚腳本來產魚
STATUS_NEW_GAME 3 另起新局
STATUS_WAIT_SHOW 4 展演時間

遊戲模式

名稱 資料內容 內容
NORMAL_MODE 1 一般產魚
FORMATION_MODE 2 生產魚陣
PIRATE_SHIP_MODE 3 生產海盜船
其實就是 gameMode, 或是產魚模式

遊戲最大單局時間為300秒, 爾後會根據最後一隻魚來動態調整每局時間

/* 產出出魚腳本
 * fishScriptMode 是目前的產魚模式
*/  
function readFishScript( fishScriptMode ){

    this.betTime和, 來決定每一秒要出什麼魚 = 300;    // 起始產魚腳本最大秒數

    // 根據betTime和theme.json, 來決定每一秒要出什麼魚 (i=偏移秒數)
    for (var i = 1; i <= self.betTime; i++) {

        // 根據模式來決定目前產魚的方式
        switch(fishScriptMode) {
        case NORMAL_MODE:           // 一般產魚
            // 決定出魚邏輯區 (根據 Group_Appear_Data 來決定)

            // 使用randomRunFish函式, 塞入該秒數的出魚陣列(randomRunFish)

            // 將魚物件塞入腳本結構中
            self.fishScript[fishIds[j]] = randomRunFish[fishIds[j]];
            
             // 將fish id塞入產魚時間計數結構中 
            self.fishScriptWithTime[randomRunFish[fishIds[j]].time].push(fishIds[j]);

            // 預製產魚腳本時, 不斷更新該腳本最大時間計數, 方便判斷何時要進入下一局 'STATUS_NEW_GAME'
            if (randomRunFish[fishIds[j]].time > self.fishScriptTime)
                self.fishScriptTime = randomRunFish[fishIds[j]].time;
            break;    
        case FORMATION_MODE:        // 陣型魚模式
            break;
        case PIRATE_SHIP_MODE:      // 海盜船模式
            break;
        }
    }
}

剛進遊戲桌內時, 設定遊戲邏輯計時器, 每秒觸發一次

tableObj.readFishScript(tableObj.gameMode); // 產魚腳本
tableObj.runLogicStart();                   // 啟動計時器(1秒觸發一次)

計時器內的 onRunLogic 主流程(簡化)

// step-1 如果有玩家, 就開始遊戲流程的切換
if (self.YahuIdList.length) {
    if (self.gameStatus == STATUS_BET_TIME) {
        // 一般押注時間
        
        // step-a 執行廣播訊息 (每兩個count才執行一次)
        if (self.runCount % 2 == 0)
            self.broadcastHandling();
            
        // 檢查道具狀態(如果狀態時間超過,就同步給場上玩家)
        if (道具時間超過) {
            self.PushService.pushByList(self.YahuIdList, "ITEM_EFFECT_END", msg, utils.doNothing);
        }

        
        // 冰凍狀態下停止出魚
        if ( isFrozen == false ){
        
            // 丟骰子隨機決定是否要出 'FISH_BOSS' 魚(有符合基本出boss魚條件)
            if (如果要出Boss魚) {
                // 出BOSS時, 房間會上鎖, 不允許其他玩家進入
                self.lockState = true;
                self.bossCome  = true; 
                //  塞入BOSS專用腳本
                self.bossScript[scriptData.id] = scriptData; 
            }
            
            // 出魚腳本出魚
            var scriptFishIds = self.fishScriptWithTime[self.runCount]; // 取得目前秒數的出魚資料    
            for (var i = 0; i < scriptFishIds.length; i++) {
                // 讀取產魚陣列
                var scriptFishId = scriptFishIds[i];
                var scriptFishData = self.fishScript[scriptFishId];
                
                // 塞入準備出魚陣列
                appearFishData.push(scriptFishData);
            }
            
            // 有出普通魚,同步給場上玩家
            if (appearFishData.length) {
                var msg = {
                    "appearFishData": appearFishData
                    }
                self.PushService.pushByList(self.YahuIdList, "FISH_APPEAR", msg, utils.doNothing);
            }
                // 有出BOSS,同步給場上玩家
            if (appearBossData.length) {
                var msg = {
                    "appearBossData": appearBossData
                }
                self.PushService.pushByList(self.YahuIdList, "BOSS_APPEAR", msg, utils.doNothing);
            }            
        
            // 準備出下一次關卡, 
            if (self.runCount == self.fishScriptTime) {
                
                // 如果 腳本計時器 == 最後一隻魚的生產時間, 就準備換下一關卡
                
                if (self.gameMode == NORMAL_MODE) {
                    // 如果目前是一般出魚模式時
                    if (8局以上有20%機率出海盜船 或 15局以上的話必出海盜船){
                        // 20%出海盜船
                        self.gameNextMode = PIRATE_SHIP_MODE;
                    }else{
                        // 80%出陣型魚
                        self.gameNextMode = FORMATION_MODE;
                    }            
                }else{
                    // 一般模式外下一局都會回到一般模式
                    self.gameNextMode = NORMAL_MODE;
                }

                // 出魚結束,同步換關訊息
                if (self.runCount > self.fishScriptTime) {
                    self.PushService.pushByList(self.YahuIdList, "READY_NEXT_MODE", msg, utils.doNothing);
                }
                
                // 這邊等待5個tick讓client做結束處理
                if (self.runCount >= self.fishScriptTime + 5) {
                    self.gameStatus = STATUS_NEW_GAME;  // 切成開新局狀態
                }                
            }
        }
        
        self.runCount++;    // 腳本計時器累加
        
    } else if (self.gameStatus == STATUS_NEW_GAME) {
        // 另啟新局
        
        // 根據桌內人數, 依序寫入賽果跟判斷是否廣播
        for (var i = 1; i <= self.maxUserNum; i++) {
            // step-a 寫賽果(如果沒有玩家要離開)
            self.writeRecord(i);
            
            // step-b 如果上一關是海盜船, 且玩家贏分 > x分, 就廣播 
            self.broadcastList.push(data);
            // 如果是海盜船關卡結束, 進行房間解鎖
            self.lockState = false;
        }
        // step-c 通知切換下一局 self.gameMode = self.gameNextMode
        self.PushService.pushByList(self.YahuIdList, "NEXT_MODE", msg, utils.doNothing);
        
        // step-d 強制解除冰凍和緩速狀態, 並且廣播給同桌玩家
        self.PushService.pushByList(self.YahuIdList, "ITEM_EFFECT_END", msg, utils.doNothing);
        // step-e 讀取下一次的產魚腳本
        self.readFishScript(self.gameMode);
        // step-f 切成 展演時間 流程
        self.gameStatus = STATUS_WAIT_SHOW; // 切成展演狀態
        
    } else if (self.gameStatus == STATUS_WAIT_SHOW) {
        // 展演時間 (刷海浪)
        
        self.runCount++;    // 腳本計時器累加
        
        // step-a 五秒後切到 STATUS_BET_TIME 流程
        self.gameStatus = STATUS_BET_TIME;
    }
}

// step-2 如果有玩家的 userLeaveFlag(離開旗標), 就執行userLeaveHandling(離開函式)
for (var i = 1; i <= self.maxUserNum; i++) {
    if (self.userLeaveFlag[i]) {
        self.userLeaveHandling(i);
    }
}

// step-3 此桌都沒玩家時的table初始化

// step-4 重新設定計時器
self.onRunTimer = setTimeout(self.onRunLogic, ON_RUN_TIME, self);

產魚過程

1.Server啟動時, 程式讀取產魚描述檔案 theme.json

2.readFishScript 來決定, 每秒出什麼魚, 
  出魚資料塞入 self.fishScript 陣列內
  出魚時間塞入 self.fishScriptWithTime 陣列內
  
3.冰凍狀態下不出魚  

4.在主流程(onRunLogic) 遊戲狀態是 STATUS_BET_TIME 且 非冰凍狀態下, 
  會根據 self.fishScript 和 self.fishScriptWithTime 來決定出魚資料
  出魚資料塞入 appearFishData 陣列
  
5.根據 appearFishData 來依序出魚, 並且廣播給Client 
  self.PushService.pushByList(self.YahuIdList, "FISH_APPEAR", msg, utils.doNothing);

6 FISH_BOSS魚有點特例, 在主流程中再另行隨機判斷
  塞入 self.bossScript 陣列內
  // Boss預告
  self.PushService.pushByList(self.YahuIdList, "BOSS_COME", {"fishDataId": fishDataId}, utils.doNothing);
  // 生產Boss
  self.PushService.pushByList(self.YahuIdList, "BOSS_APPEAR", msg, utils.doNothing);
  

魚種表

魚種名稱 資料內容 內容
FISH_BOSS A BOSS魚
FISH_SMALL_BOSS B 小BOSS魚
FISH_BIG C 大型魚(含組合魚)
FISH_ODDS D 倍率魚
FISH_MIDDLE E 中型魚
FISH_SMALL F 小型魚
FISH_SPECIAL G 特殊魚種(道具魚)
FISH_PIRATE_SHIP S 海盜船

theme.json 介紹

打中魚流程:

收到封包時,會呼叫玩家中魚處理函式

玩家中魚處理

/*
 * @param  {Number} pos             玩家座位Id
 * @param  {String} bulletId        子彈ID        
 * @param  {Array} fishIdList       打中的魚的列表 (魚的流水ID)
 * @param  {Array} linkFishList     待查
 * @param  {function} callback      有錯誤時的 ErrorCallBackFunction
 */
GameTable.prototype.playerCatchFish = function (pos, bulletId, fishIdList, linkFishList, callback) {

    // 移除重複的fish id
    
    // 取出第一隻魚的data id
    
    // 檢查是否為有效子彈
    bulletData = self.userBulletDataNow[pos][bulletId];
    if (bulletData) {
        // 有效子彈
        
        // 確認使用的水池(根據押注等級數值來判斷使用大小水池)
        if (betLevel > BET_5)
            useMinPool = false;     
            
        // 子彈命中廣播
        var msg = {
            "userPos": pos,
            "bulletId": bulletId,
            "fishId": fishIdList[0],
        };
        self.PushService.pushByList(self.YahuIdList, "BULLET_HIT_FISH", msg, utils.doNothing);
        
        if (如果是鑽頭炮) {
            // 水池進水(有分大小水池,只貼局部)
            // 小水池
            // 一般水池進水公式 
            self.userPoolMin[pos].normal_values += (betPoint * self.poolProb / 100) * self.normalPoolRate / 100;
            // 武器水池進水公式
            self.userPoolMin[pos].arms_values += (betPoint * self.poolProb / 100) * self.armsPoolRate / 100;
        }
        
        .... 省略
    }
    
    // 針對無效子彈清除結構
    
    // 針對抓取魚資料做處理(製作有效魚陣列 validFishId 和 validFishDataId)
    for (var i = 0; i < fishIdList.length; i++) {
    
        .... 省略
        
        // 加入有效魚中
        validFishId.push(fishScriptData.id);
        validFishDataId.push(fishScriptData.data_id);
    }
    
    .... 省略
    
    // 進行 ********* 中魚計算 *********
    var result = self.runResult( ... )
    
    // 鑽頭砲相關處理
    if ( 如果是鑽頭炮 ){
        // 武器贏分累加
        self.drillStatus[pos].win += result.armsWin;
        
        // 武器水池扣水
        if (useMinPool)
            self.userPoolMin[pos].arms_values -= result.armsWin;
        else
            self.userPoolMax[pos].arms_values -= result.armsWin;   
            
        // 鑽頭砲結束
        if (self.drillStatus[pos].count == self.drillStatus[pos].max) {
            // 製作廣播結構
            var data = {
                "broadcastId": DRILL_WIN_POINT,
                "msgData": {
                "user_id": "******" + self.userUserIds[pos].slice(self.userUserIds[pos].length - 3),
                "win": self.drillStatus[pos].win
                }
            }
            self.broadcastList.push(data);
            
        }
    }
    
    // 連環炸彈相關處理
    if (self.linkageExplosionStatus[pos][bulletId]) {
        // 武器贏分累加
        self.linkageExplosionStatus[pos][bulletId].win += result.armsWin;
        
        // 武器水池扣水
        if (useMinPool)
            self.userPoolMin[pos].arms_values -= result.armsWin;
        else
            self.userPoolMax[pos].arms_values -= result.armsWin;    
            
        // 連環炸彈結束    
        if( 目前的count == 最大的max ){
            // 製作廣播結構
            var data = {
                "broadcastId": CHAIN_BOMB_WIN_POINT,
                "msgData": {
                    "user_id": "******" + self.userUserIds[pos].slice(self.userUserIds[pos].length - 3),
                    "win": self.linkageExplosionStatus[pos][bulletId].win
                }
            }
            self.broadcastList.push(data);        
        }
    }
    
    // 沒死魚不送封包
    
    // 雷射砲廣播處理
    
    // 針對死魚做處理
    for (var i = 0; i < deadFishIds.length; i++) {
        // 製作死魚資料
        var data = {
            "bullet_id": bulletId,
            "bet_point": betPoint,
            "bullet_kind": bulletKind,
            "data_id": result.fishDeadData[deadFishIds[i]].data_id,
            "fish_kind": result.fishDeadData[deadFishIds[i]].fish_kind,
            "win": result.fishDeadData[deadFishIds[i]].win,
            "kill_pos": pos
        }
        
        // 記錄整桌死魚物件排除海盜船
        
        // 記錄玩家個人打死魚
        self.userCatchFishData[pos][deadFishIds[i]] = data;
        
        // 道具處理
        if (result.fishDeadData[deadFishIds[i]].get_item) {
            isGetItem = true; // 獲得道具旗標
            if (data.data_id == SP_FROZEN) {
                //SP_FROZEN 最多使用次數 = 3
                self.userItemNum[pos][FROZEN]++;
            } else if (data.data_id == SP_SLOW) {
                //SLOW 最多使用次數 =3
                self.userItemNum[pos][SLOW]++;
            } else if (data.data_id == SP_DRILL) {
                //DRILL 最多使用次數 = 1
                self.userItemNum[pos][DRILL]++;            
            }

        }
        
        // 打死small boss
        var msg = {
            "broadcastId": SMALL_BOSS_DEAD,
            "msgData": {
                "user_id": "******" + self.userUserIds[pos].slice(self.userUserIds[pos].length - 3),
                "fish_id": data.data_id,
                "win": result.allWin + result.armsWin
            }
        }
        self.broadcastList.push(msg);
        
        // 打中海盜船
        if (data.fish_kind == FISH_PIRATE_SHIP) {
            // 武器水池扣水
            if (useMinPool)
                self.userPoolMin[pos].arms_values -= result.armsWin;
            else
                self.userPoolMax[pos].arms_values -= result.armsWin;
        }        
        
        // 打死水雷
        if (data.data_id == SP_PARTIAL_BOMB) {
            var msg = {
            "broadcastId": PARTIAL_BOMB_WIN_POINT,
            "msgData": {
            "user_id": "******" + self.userUserIds[pos].slice(self.userUserIds[pos].length - 3),
                    "win": result.armsWin
                }
            }
            self.broadcastList.push(msg);
        }
        
        // 打死魚雷
        if (data.data_id == SP_FULL_SCREEN_BOMB) {
            var msg = {
                "broadcastId": PARTIAL_BOMB_WIN_POINT,
                "msgData": {
                    "user_id": "******" + self.userUserIds[pos].slice(self.userUserIds[pos].length - 3),
                    "win": result.armsWin
                }
            }
            self.broadcastList.push(msg);     
            
            // 武器水池扣水
            if (useMinPool)
                self.userPoolMin[pos].arms_values -= result.armsWin;
            else
                self.userPoolMax[pos].arms_values -= result.armsWin;
        }
        
        // 打死BOSS
        if (data.fish_kind == FISH_BOSS) { 
            // 清除房間鎖狀態
            self.lockState = false;
            // BOSS來襲結束,同步給場上玩家
            self.PushService.pushByList(self.YahuIdList, "BOSS_COME_END", {}, utils.doNothing);
            
            if (一定贏分以上進行廣播){
                var msg = {
                    "broadcastId": BOSS_DEAD,
                    "msgData": {
                        "user_id": "******" + self.userUserIds[pos].slice(self.userUserIds[pos].length - 3),
                        "fish_id": data.data_id,
                        "win": result.allWin
                    }
                }
                self.broadcastList.push(msg);            
            }
            
        }
        
        // 打死連環炸彈
        if (data.data_id == SP_CHAIN_BOMB) {
            ... 省略
            
            // 同步給場上玩家
            var msg = {
                "userPos": pos,
                "bulletId": bulletId,
                "fishId": parseInt(deadFishIds[i]),
                "explosionCount": explosionCount,
                "explosionX": explosionX,
                "explosionY": explosionY
            };
            self.PushService.pushByList(self.YahuIdList, "CHAIN_BOMB_DATA", msg, utils.doNothing);        
        }
        
        // 有取得道具,同步玩家自己的道具狀態
        if (isGetItem) {
            var msg = {
                "userItemNum": self.userItemNum[pos]
            };
            self.PushService.pushByUID(self.userYahuIds[pos], "USER_ITEM_NUM", msg, utils.doNothing);
        }        
        
    }
 
    // 贏分加入本金
    self.userPointCompute(pos, result.allWin + result.armsWin, ADD);
    self.userDisplayPointCompute(pos, result.allWin + result.armsWin, ADD);
    // 總贏分
    self.userTotalWin[pos] += result.allWin + result.armsWin;
    // 將總倍率以及總贏分放入有效子彈資料
    self.userBulletData[pos][bulletId].win += result.allWin + result.armsWin;
    self.userBulletData[pos][bulletId].total_odds += result.totalOdds;

    // 死魚同步給場上玩家
    var msg = {
        "userPos": pos,
        "fishDeadCount": deadFishIds.length,
        "fishDeadData": result.fishDeadData,
        "userPoint": self.userDisplayPoint[pos],
    };
    self.PushService.pushByList(self.YahuIdList, "CATCH_FISH", msg, function(err,data) {
        return utils.invokeCallback(callback, null, null);
    }); 
 
}

備註1: 水池進水公式請參考水庫進水公式

中魚計算

/*
 * @param  {int} pos                            玩家座位Id
 * @param  {Number} useMinPool                  玩家小水池
 * @param  {bool} bulletIsNull                  是否為無效子彈旗標
 * @param  {Object} bulletData                  子彈資料
 * @param  {Number} validFishId                 這一發子彈打中的..所有魚的流水Id(有效魚)
 * @param  {Number} validFishDataId             這一發子彈打中的..所有魚的Fish_id(有效魚)
 * @param  {Number} validLinkFishId             待補
 * @param  {Number} validLinkFishDataId         待補
 * @param  {Number} linkageExplosionEnabled     待補
 * @param  {Number} linkFishEnabled             待補 
 * @param  {Number} pirateShipEnabled           待補 打到海盜船旗標
 * @return {Object} result                      回傳此次擊殺的結果
 */
GameTable.prototype.runResult = function( ... 省略 ){

    // 無效子彈,直接送回無效狀況
    
    // 整理所有魚的賠率
    for (var i = 0; i < validFishDataId.length; i++) {
        fishRatio[validLinkFishDataId[i]] = parseInt(Math.random() * self.theme.Fish_Data[validLinkFishDataId[i]].Ratio_Random + self.theme.Fish_Data[validLinkFishDataId[i]].Bet_ratio);
    }
    
    // 取出水池值 & 水池修正相關資料
    if (useMinPool) {
        // 使用小水池
        poolValues = self.userPoolMin[pos].normal_values;
        poolArmsValues = self.userPoolMin[pos].arms_values;
        if (poolValues > 0) {
            // (大魚及小魚)(正向)
            normalBigContainer = self.theme.Normal_Fish_Pool.Positive.small.big_fish.container;
            normalSmallContainer = self.theme.Normal_Fish_Pool.Positive.small.small_fish.container;            
            
            // 依人數取得修正資料(分 3人同桌 4人同桌, 取不同的RatioFix )
            ... 省略
            
            // 如果是兩人取得 【正向】 普通子彈對應的RatioFix
            normalBigRatioFix = self.theme.Normal_Fish_Pool.Positive.small.big_fish.ratio_2;        // 兩人修正-大魚
            normalSmallRatioFix = self.theme.Normal_Fish_Pool.Positive.small.small_fish.ratio_2;    // 兩人修正-小魚
        }else{
            // (大魚及小魚)(負向)
            ... 省略
            
            // 如果是兩人取得 【負向】 普通子彈對應的RatioFix
            normalBigRatioFix = self.theme.Normal_Fish_Pool.Negative.small.big_fish.ratio_2;
            normalSmallRatioFix = self.theme.Normal_Fish_Pool.Negative.small.small_fish.ratio_2;
        }
        
        // 武器水池修正資料
        // 判斷水位
        spContainer = self.theme.Special_Fish_Pool.Positive.small.container;
        
        // 如果是兩人取得 【正向】 武器對應的RatioFix
        spRatioFix = self.theme.Special_Fish_Pool.Positive.small.ratio_2;
    }else{
        // 使用大水池
        poolValues = self.userPoolMax[pos].normal_values;
        poolArmsValues = self.userPoolMax[pos].arms_values;    
        
        // 一般水池修正資料  (正水池/負水池)
        if (poolValues > 0) {
            // 判斷水位 (大魚及小魚)(正向)
            normalBigContainer = self.theme.Normal_Fish_Pool.Positive.big.big_fish.container;
            normalSmallContainer = self.theme.Normal_Fish_Pool.Positive.big.small_fish.container;
            
            // 依人數取得修正資料(分 3人同桌 4人同桌, 取不同的RatioFix )
            ... 省略
            
            // 如果是兩人取得 【正向】 對應的RatioFix
            normalBigRatioFix = self.theme.Normal_Fish_Pool.Positive.big.big_fish.ratio_2;
            normalSmallRatioFix = self.theme.Normal_Fish_Pool.Positive.big.small_fish.ratio_2;
        }else{
            // 判斷水位 (大魚及小魚)(負向)
            normalBigContainer = self.theme.Normal_Fish_Pool.Negative.big.big_fish.container;
            normalSmallContainer = self.theme.Normal_Fish_Pool.Negative.big.small_fish.container;   
            
             ... 省略 同上
        }
        
        // 武器水池修正資料
        // 判斷水位  
        ... 省略   
        // 依人數取得修正資料
        spRatioFix = self.theme.Special_Fish_Pool.Positive.big.ratio_2;
    }
    
    // 計算擊中海盜船(待補)
    if (如果打中海盜船) {
        var pirateShipWeight = self.theme.Pirate_Ship.Random_Gap;    // ????
        for (var i = 0; i < pirateShipWeight.length; i++) {
            if (randWeight < pirateShipWeight[i]) {
                fishRatio[FISH_PIRATE_SHIP_DATA_ID] = self.theme.Pirate_Ship.Reward_Ratio[i];
                break;
            }    
        }    
    }

    // 確認大小水池取出MAX BET
    maxBet = self.userPoolXXX[pos].max_bet;

    // 整理所有魚的致死
    for (var i = 0; i < validFishDataId.length; i++) {
    
        // 取出魚的資料
        var fishData = self.theme.Fish_Data[validFishDataId[i]];    
        
        // 資料不存在,放入初始致死率
        if(!fishKillRate[validFishDataId[i]]){

            fishKillRate[validFishDataId[i]] = fishData.Kill_rate;  // 資料不存在,放入初始致死率
        }   
        
        // 不同魚使用不同水池
        if(fishData.Fish_group == FISH_SMALL || fishData.Fish_group == FISH_MIDDLE) {
            // 小型魚 & 中型魚使用小魚一般水池修正資料
            for (var j = 0; j < normalSmallContainer.length; j++) {
            
                if (isTrue) {
                    if (poolValues > 0)
                        fishKillRate[validFishDataId[i]] = parseInt(fishKillRate[validFishDataId[i]] * normalSmallRatioFix[j] / 10);
                    else
                        fishKillRate[validFishDataId[i]] = parseInt(fishKillRate[validFishDataId[i]] * 10 / normalSmallRatioFix[j]);    // 水庫下降, 代表玩家輸了, 開始調高致死率                
                }
            }
        } else if (fishData.Fish_group != FISH_PIRATE_SHIP && fishData.Fish_id != SP_DRILL && fishData.Fish_id != SP_CHAIN_BOMB && fishData.Fish_id != SP_FULL_SCREEN_BOMB && fishData.Fish_id != SP_PARTIAL_BOMB) {
        
            // 大型魚使用大於水池修正資料 (除去海盜船/鑽頭砲/連環/全屏/區域)
            
        } else {
        
        }
        
    }
    
    
    
    .... 待補....
}

致死率

一網捕多魚機率

for (var i = 0; i < validFishDataId.length; i++) {
    // 原本的致死率 / 魚網捕的魚隻數
    fishKillRate[validFishDataId[i]] = parseInt( fishKillRate[validFishDataId[i]] / validFishDataId.length );
}

假設有一隻魚 
odd = 5
RTP = 98%
魚原始死機率 = 19.6% 的機率會死

如果一次捕獲兩隻
所以第一隻魚的死亡率 => 19.6 /2 = 9.8%
所以第二隻魚的死亡率 => 19.6 /2 = 9.8%
期望值為 9.8 + 9.8 = 19.6

如果一次捕獲三隻
所以第一隻魚的死亡率 => 19.6 /3 = 6.5%
所以第二隻魚的死亡率 => 19.6 /3 = 6.5%
所以第二隻魚的死亡率 => 19.6 /3 = 6.5%
期望值為 6.5 + 6.5 + 6.5 = 19.6

如果捕獲不同賠率的魚 Odd=5  Odd=20
A魚原始死機率 = 19.6% B魚原始死的機率 = 4.9%

如果一次捕獲兩隻
所以第一隻魚的死亡率 => 19.6 /2 = 9.8%
所以第二隻魚的死亡率 => 4.9 /2 = 2.45%
期望值為 9.8 + 2.45 = 12.25

致死率會根據水庫水位動態調整

水庫:

水庫控制項

theme.json 內, Controll_Value 裡面的控制變數

名稱 數值 內容
All_pool_rate 96 進水量 = All_pool_rate = self.poolProb
Normal_pool_rate 85 普通子彈水池%數, 控制 normal_values
Weapon_pool_rate 15 特殊武器水池%數, 控制 arms_values

Normal_Fish_Pool( 根據個人水池的正跟負, 取得不同的修正值, 此值會影響修正魚的致死率 )

名稱 數值 內容

Normal_Fish_Pool( 根據個人水池的正跟負, 取得不同的修正值, 此值會影響修正魚的致死率 )

名稱 數值 內容
spRatioFix [10,10,12,15,20] 特殊武器致死率修正, 有分 2人3人4人的修正值

水庫和機率的規則

1.規則1 theme.json 裡面的 Fish_Data 的 Kill_rate(致死率) 我發現好像偏低 都是 已 rtp 0.75 去算的 ( 例如 綠魚 條紋魚 大眼魚.... 美人魚)
2.規則2  if 個人水池 > 0 {
            // 個人水池大於零, 代表玩家輸錢, 所以增加致死率, 方便玩家打死魚
            原本致死率 = 原本致死率 * (正向小水池修正因子 / 10)
        }else{
            // 個人水池小於零, 代表玩家贏錢, 所以縮小致死率, 讓玩家很難打死魚
            原本致死率 = 原本致死率 * (10 / 負向小水池修正因子)
        }
3.規則3  一網捕多魚修正 將   【原本魚的致死率 / 捕獲的魚數量 】 去做衰減
4.致死率超過80%一律以80%

水庫進水公式

// 一般水池
self.userPoolMin[pos].normal_values += (betPoint * self.poolProb / 100) * self.normalPoolRate / 100;
 
// 武器水池
self.userPoolMin[pos].arms_values += (betPoint * self.poolProb / 100) * self.armsPoolRate / 100; 
 
假設 子彈一發是1點 (Bet=1)
一般水池進水量 = 1 * 96/100 * 85/100 = 1 * 0.96 * 0.85 = 0.816
武器水池進水量 = 1 * 96/100 * 15/100 = 1 * 0.96 * 0.15 = 0.144

個人水庫修正公式

// 扣除個人一般水池 allWin是此魚的總贏分
if (useMinPool)
    self.userPoolMin[pos].normal_values -= allWin;
else
    self.userPoolMax[pos].normal_values -= allWin;

水庫流程

armsWin = 武器總贏分
allWin = 一般魚總贏分

  1. 在判斷是有效子彈後, 先根據 水庫進水公式, 把值灌進大小水池和武器水池內
    a.self.userPoolMin[pos].normal_values += (betPoint * self.poolProb / 100) * self.normalPoolRate / 100; // 個人水池
    b.self.userPoolMin[pos].arms_values += (betPoint * self.poolProb / 100) * self.armsPoolRate / 100; // 武器水池

  2. 呼叫 runResult, 進行中魚計算
    a. 根據目前水庫, 取得相對應的機率修正值 (normalBigRatioFix, normalSmallRatioFix)
    b. 整理所有魚的初步致死機率 fishKillRate[validFishDataId[i]] = fishData.Kill_rate
    c. 根據水庫水位調整魚的致死率, 水庫有水時 fishKillRate[validFishDataId[i]] = parseInt(fishKillRate[validFishDataId[i]] * normalSmallRatioFix[j] / 10);
    d. 根據水庫水位調整魚的致死率, 水庫沒水時 fishKillRate[validFishDataId[i]] = parseInt(fishKillRate[validFishDataId[i]] * 10 / normalSmallRatioFix[j]);
    e. 道具的致死率跟水庫修正 fishKillRate[validFishDataId[i]] = parseInt(fishKillRate[validFishDataId[i]] * spRatioFix[j] / 10);

e. 跑 cnt < 2 迴圈, for (var cnt = 0; cnt < 2; cnt++)
a. 一網捕多魚的致死率修正, 該魚致死率/捕獲數, 平均分散在每隻捕獲魚 fishKillRate[validFishDataId[i]] = parseInt( fishKillRate[validFishDataId[i]] / validFishDataId.length );

b1. 執行runResult時, 武器水池修正
c. 執行runResult時, 最後算出allWin時, 再從個人水池內 扣回去 self.userPoolMin[pos].normal_values -= allWin;
6. 離開runResult後, 判斷如果是鑽頭炮射擊, 進行武器大小水池修正 self.userPoolMin[pos].arms_values -= result.armsWin;
7. 離開runResult後, 判斷如果是連環炸彈, 進行武器大小水池修正 self.userPoolMin[pos].arms_values -= result.armsWin;
8. 離開runResult後, 檢查死魚陣列, 判斷如果打死海盜船, 進行武器大小水池修正 self.userPoolMin[pos].arms_values -= result.armsWin;
9. 離開runResult後, 檢查死魚陣列, 判斷如果打死魚雷, 進行武器大小水池修正 self.userPoolMin[pos].arms_values -= result.armsWin;

水庫的精華是

1.打魚沒死, 水庫水位上升
2.打死魚, 水庫慢慢下降

水庫水位影響 魚的致死率
根據水庫水位, 取得不同的參數來調整 致死率 (有分 大魚, 小魚, 遊戲桌內[2,3,4]人 )

當 (武器水位 - 武器的贏分) < 0, 變成負值時, 發不出錢時, 會 armOver = true
此時會重跑一次 致死率判斷魚死不死的迴圈, 然後將調整致死率=0, 就讓他再重跑一次流程
3.道具打中的每一隻魚, 致死率=0
4.炸彈類型/海盜船 都打不死, 每一隻魚, 致死率=0

基本上武器水池發不出獎就是直接不發武器水池的獎

資料庫結構:

gpk_center (管理會員)資料庫

gamelist 遊戲列表

欄位名稱 資料型態 說明
game_id VARCHAR(10) 主Key, 遊戲Id
cn_name VARCHAR(64) 簡中名稱
en_name VARCHAR(64) 英文名稱
enable TINYINT(1) 是否啟用
game_category_id TINYINT(3) 遊戲類型ID
chost VARCHAR(64) 前端服務器IP
cport INT(4) 前端服務器PORT
th_name VARCHAR(64) 泰文名稱
tw_name VARCHAR(64) 繁中名稱

item 玩家持有道具列表

欄位名稱 資料型態 說明
id VARCHAR(10) 主Key, Id, auto_increment 自動增長
count DECIMAL(18,2) 玩家遊戲點數(float)
item_id TINYINT(3) 道具ID
user_id VARCHAR(20) 玩家ID

play_log 玩遊戲下注紀錄列表

欄位名稱 資料型態 說明
id VARCHAR(255) 流水號
agent_id VARCHAR(255) 代理商AGENT ID
app_id VARCHAR(10) 遊戲ID
bet_amount DECIMAL(18,2) 注額
bet_num SMALLINT(5) 投注倍數
bonus DECIMAL(18,2) 附加遊戲彩金
cost DECIMAL(18,2) 總投注
contribution DECIMAL(18,6) 貢獻金
payoff DECIMAL(18,2) 總損益
free_count SMALLINT(5) 剩餘免費遊戲次數
game_category_id TINYINT(3) 遊戲類型
game_id BIGINT(20) 遊戲ID
game_table_idx TINYINT(3) 使用樣本庫ID
result_type TINYINT(3) 樣本結果
jackpot_score DECIMAL(18,2) JP彩金
linked_score DECIMAL(18,2) 連線彩金
payoff_time TIMESTAMP 派彩時間
status TINYINT(3) 注單狀態
total_score DECIMAL(18,2) 總贏分
user_id VARCHAR(20) 投注玩家ID
wagers_time TIMESTAMP 投注時間
date DATE 投注日期
coefficient SMALLINT(5) 得分倍數

transactionlog 玩家點數交易紀錄(平台)列表

欄位名稱 資料型態 說明
id VARCHAR(255) 交易序號
transfer_type TINYINT(3) 交易類型
user_id VARCHAR(20) 交易使用者ID
amount DECIMAL(18,2) 交易金額
bef_money DECIMAL(18,2) 交易前金額
aft_money DECIMAL(18,2) 交易後金額
create_date TIMESTAMP 交易時間
status TINYINT(3) 交易狀態

transactionlog 玩家點數交易紀錄(平台)列表

欄位名稱 資料型態 說明
id VARCHAR(20) 玩家ID
yahu_id INT(11) 亞虎玩家ID
agent VARCHAR(20) 代理商AGENT ID
enabled TINYINT(1) 帳號是否啟用
last_login DATETIME 最後登錄時間
last_quit DATETIME 最後離線時間
ip VARCHAR(15) 最後登錄IP
currency VARCHAR(5) 幣別
update_date_time TIMESTAMP 更新時間
create_date_time TIMESTAMP 帳號建立時間

fish_lord (魚王)資料庫

entergamelog 進入遊戲紀錄列表

欄位名稱 資料型態 說明
user_id VARCHAR(20) 玩家ID
lobby TINYINT(3) 進入的大廳Id
create_date_time TIMESTAMP 玩家ID

game_play_log 遊戲下注紀錄列表

欄位名稱 資料型態 說明
id VARCHAR(255) 流水號
agent_id VARCHAR(255) 代理商AGENT ID
app_id VARCHAR(10) 遊戲ID
bet_amount DECIMAL(18,2) 注額
bet_num SMALLINT(5) 投注倍數
bonus DECIMAL(18,2) 附加遊戲彩金
cost DECIMAL(18,2) 總投注
contribution DECIMAL(18,6) 貢獻金
payoff DECIMAL(18,2) 總損益
game_category_id TINYINT(3) 遊戲類型
fish_data_id TINYINT(3) 擊中魚ID
jackpot_score DECIMAL(18,2) JP彩金
linked_score DECIMAL(18,2) 連線彩金
payoff_time TIMESTAMP 派彩時間
status TINYINT(3) 注單狀態
total_score DECIMAL(18,2) 總贏分
user_id VARCHAR(20) 投注玩家ID
wagers_time TIMESTAMP 投注時間
grp_id VARCHAR(16) 局號ID
date DATE 投注日期
coefficient SMALLINT(5) 得分倍數

gamepool 遊戲水池數據列表

欄位名稱 資料型態 說明
user_id VARCHAR(20) 玩家ID
max_bet BIGINT(20) 水池 Max Bet
arms_values DECIMAL(18,2) 特殊武器水池值
normal_values DECIMAL(18,2) 一般水池值

gameuser 遊戲玩家數據列表

欄位名稱 資料型態 說明
id VARCHAR(20) 玩家ID
total_bet_amount VARCHAR(20) 總投注
total_payoff VARCHAR(20) 總收穫
enabled VARCHAR(20) 啟用
update_date_time TIMESTAMP
create_date_time TIMESTAMP