package gamelogic import ( "encoding/json" "math/rand" "bet24.com/log" ) const ( GAMEID = 82 GAME_NAME = "ludo" CHAIR_COUNT = 4 PLANE_COUNT = 4 ) const ( Phase_Free = iota Phase_Match //匹配结束 Phase_Dice //掷骰子中 Phase_Move //移动飞机中 Phase_End //结束 ) const ( ScoreType_Bet = iota ScoreType_End ScoreType_Return ) const ( TIMER_READY_0 = iota TIMER_READY_1 TIMER_READY_2 TIMER_READY_3 TIMER_MATCH TIMER_GAME TIMER_ROBOT TIMER_ADD_ROBOT TIMER_REMOVE_ROBOT ) const SEC_READY = 15 * 1000 //准备计时 const SEC_MATCH = 4000 //匹配计时 4000 匹配动画时间 // const SEC_DICE = 4000 //投掷骰子超时3s 后端多设置1秒延迟 // const SEC_MOVE = 10000 //移动飞机超时10s const SEC_DELAY = 2000 //动画等待时间2s const SEC_MOVE_TIME = 300 //移动一格时间 const SEC_NEXT_CHAIR = 500 //换人间隔 const SEC_AUTO = 2000 //托管超时 const SEC_ROBOT_CHAT = 5000 const MAX_STEP = 57 // 终点 const ( Action_Dice = iota // 投掷骰子 Action_Move // 移动飞机 Action_Drop // 2 弃牌 Action_Invalid ) // 飞机是否安全 func isSafe(pos int) bool { //还未出发 if pos == 0 { return true } if pos >= 52 { return true } //第九格和第14格为安全区 return (pos-9)%13 == 0 || (pos-14)%13 == 0 } // 查找位置存在危险的棋子 func (gs *GameScene) getNnsafePlane(chairId int) int { unsafePlane := make(map[int]int) myPlanes := gs.Players[chairId].Planes for k, v := range myPlanes { if isSafe(v.Position) || !v.CanMove { continue } for k1, v1 := range myPlanes { if k == k1 { continue } //叠棋 if v.Position == v1.Position && v.Id != v1.Id { continue } unsafePlane[v.Id] = v.Position } } planeId := -1 if len(unsafePlane) > 0 { farthest := -1 //最远的 for unsafeId, unsafePosition := range unsafePlane { for i := 0; i < CHAIR_COUNT; i++ { //不跟自己比 if i == chairId { continue } //无效的玩家过滤 if !gs.Players[i].IsValid { continue } if gs.Players[i].Dropped { continue } if gs.Players[i].checkChairPosition(chairId, unsafePosition) == 2 { //有危险 if unsafePosition > farthest { farthest = unsafePosition planeId = unsafeId } } } } } return planeId } // 是否相撞 func isCrash(chair1, pos1, chair2, pos2 int) bool { if isSafe(pos1) || isSafe(pos2) { return false } return (chair1*13+pos1)%52 == (chair2*13+pos2)%52 } // 是否有效座位 func isValidChair(chairId int) bool { return chairId >= 0 && chairId < CHAIR_COUNT } // 是否有效棋子 func isValidPlane(planeId int) bool { return planeId >= 0 && planeId < PLANE_COUNT } // 是否有效点数 func isValidPoint(point int) bool { return point >= 1 && point <= 6 } type userAction struct { ChairId int Action int Number int PlaneId int } // 操作结果 type ActionResult struct { Action int //动作信息 Number int //骰子数值 PlaneId int //操作棋子ID Position int //棋子移动位置 CrashedChairId int //被撞击座位 CrashedPlaneId int //被撞击棋子ID } func (ua *userAction) dump() { log.Debug(" Chair[%d],Action[%d],PlaneId[%d],Number[%d]", ua.ChairId, ua.Action, ua.PlaneId, ua.Number) } type GameScene struct { Index int Phase int WhoseTurn int LastTurn int ActionResult ActionResult // 操作信息 Players []PlayerInfo LeftSec int //本阶段剩余时间 userActions []userAction pool int base int } func newGameScene() *GameScene { gs := new(GameScene) gs.initData() return gs } // 弃权 func (p *PlayerInfo) drop() { p.Dropped = true } // 检测位置是否撞机 func (gs *GameScene) checkPositionIsCrashed(chairId, position, planeId int) (total int, tempCrashed *Plane, crashedPlayer *PlayerInfo) { total = 0 //自己的棋子数量 myPlaneCount := 0 for i := 0; i < CHAIR_COUNT; i++ { //不能忽略自己的棋子 有可能自己的棋子和对方的棋子形成了安全区 if i == chairId { planeCount := gs.Players[i].checkSamePositionCount(position, planeId) myPlaneCount += planeCount if planeCount != 0 { //log.Debug("checkPositionIsCrashed myPlaneCount[%d] ", myPlaneCount) break } continue } //无效的玩家过滤 if !gs.Players[i].IsValid { continue } if gs.Players[i].Dropped { continue } //检查加上步数后是否和该玩家的棋子是否相撞 crashed := gs.Players[i].checkCrashed(chairId, position) crashedCount := len(crashed) if crashedCount < 1 { continue } total += crashedCount //如果当前用户有一个被踩则暂存 if crashedCount == 1 { tempCrashed = crashed[0] crashedPlayer = &gs.Players[i] } } if total != 0 && myPlaneCount >= 1 { total += myPlaneCount } return total, tempCrashed, crashedPlayer } // 计算飞机移动权重 func (gs *GameScene) calculateWeight(pos, number int) int { weight := -1 relativePos := (pos + number) % 13 //移动后的相对坐标 //计算距离 safeDistance := (relativePos - 9) % 13 if safeDistance <= (relativePos-14)%13 { safeDistance = (relativePos - 14) % 13 } if pos < 52 && pos+number >= 52 { //点数可以进入52以后的 5 weight = 5 } else if safeDistance == 0 { //可以进入到安全区 4 weight = 4 } else if (pos-9)%13 == 0 || (pos-14)%13 == 0 { //已经在安全区优先动 1 weight = 1 } else { //其他情况 2 包括已经进入最后阶段 pos >= 52 weight = 2 } return weight } // 检查棋子移动后结果 func (gs *GameScene) checkPlaneMoveResult(chairId, position int) int { //临时标记 temp := -1 for i := 0; i < CHAIR_COUNT; i++ { //不跟自己比 if i == chairId { continue } //无效的玩家过滤 if !gs.Players[i].IsValid { continue } if gs.Players[i].Dropped { continue } //检查自己的棋子加上步数后 是否超过或者接近对手的棋子 ret := gs.Players[i].checkChairPosition(chairId, position) if ret == 2 { //有危险则不在判断 temp = ret break } if ret > temp { temp = ret } } return temp } // 检查自己是否落后对手超过10步以上了 func (gs *GameScene) checkIsBehind(chairId int) bool { //自己的棋子总和 myPositionCount := gs.Players[chairId].calculatePosition() //如果自己的步数还未大于20 则不触发 if myPositionCount < 20 { return false } for i := 0; i < CHAIR_COUNT; i++ { //不跟自己比 if i == chairId { continue } //无效的玩家过滤 if !gs.Players[i].IsValid { continue } if gs.Players[i].Dropped { continue } //检查自己的棋子加上步数后 是否超过或者接近对手的棋子 ret := gs.Players[i].calculatePosition() //检查自己是否落后对手超过10步以上了 if ret >= myPositionCount+10 { return true } } return false } func (gs *GameScene) addAction(chairId, action, number, planeId int, isRobot bool) (bool, string, bool, int) { gs.initActionResult() //是否下一个玩家 var isNextChair = false var stepCount = 0 //判断数据有效性 if chairId != gs.WhoseTurn { return false, "wrong turn", isNextChair, stepCount } if action == gs.Players[chairId].LastAction { return false, "wrong action", isNextChair, stepCount } //移动飞机 if action == Action_Move { var isReach = false //判断前端操作指令是否合理 托管 if gs.Players[chairId].AutoOut && !isValidPlane(planeId) { farthest := -1 //最远的 for _, v := range gs.Players[chairId].Planes { if !v.CanMove { continue } //先出机场中棋子 if v.Position == 0 { planeId = v.Id break } //其次走最后是距离 if v.Position > farthest { farthest = v.Position planeId = v.Id } } } else if isRobot && !isValidPlane(planeId) { //机器人 tempWeight := -1 //权重 weightGroup := []int{} unsafePlaneId := gs.getNnsafePlane(chairId) //危险的棋子 number := gs.Players[chairId].Number for _, v := range gs.Players[chairId].Planes { //排除不能移动的棋子 if !v.CanMove { continue } weight := -1 //可以攻击优先 if v.Position != 0 && v.Position < 52 && v.Position+number < 52 { total, _, _ := gs.checkPositionIsCrashed(chairId, v.Position+number, v.Id) if total == 1 { planeId = v.Id break } } else if v.Position == 0 { //其次出机场中棋子 weight = 7 } if weight == -1 && v.Id == unsafePlaneId { //再考虑走危险的棋子 weight = 6 } if weight == -1 { weight = gs.calculateWeight(v.Position, number) //机器人计算权重 if weight <= 2 { //2以下存在变数 moveResult := gs.checkPlaneMoveResult(chairId, v.Position+number) //移动后的结果 if moveResult == 1 { //如果是可以追击则权重上升 weight++ } else if moveResult == 2 { if number != 6 { //超过去有危险 weight = 0 } else if weight == 2 && number == 6 && gs.Players[chairId].ContinueSixPoint >= 1 { //摆脱机会不大 哪怕本次摇出6 接下来再次出6无法移动 weight = 1 } } } } if weight > tempWeight { weightGroup = []int{} weightGroup = append(weightGroup, v.Id) tempWeight = weight } else if weight == tempWeight { weightGroup = append(weightGroup, v.Id) } } //找到优先级更高的棋子时 根据权重选择 if !isValidPlane(planeId) && len(weightGroup) > 0 { count := len(weightGroup) if count > 1 { //随机选一个 planeId = weightGroup[rand.Intn(count)] } else { planeId = weightGroup[0] } } } ok, movePlaneId, isReach, moveStepCount := gs.Players[chairId].canMovePlane(planeId) if !ok { return false, "wrong movement", isNextChair, stepCount } stepCount = moveStepCount planeId = movePlaneId //获胜额外获得一次机会 if !isReach { // 检测撞机 total, tempCrashed, crashedPlayer := gs.checkPositionIsCrashed(chairId, gs.Players[chairId].Planes[planeId].Position, planeId) //一个区域如果出现2个棋子,无论是否同玩家 则为安全区 否则则攻击 if total == 1 { gs.ActionResult.CrashedChairId = crashedPlayer.chairId //被撞击座位 gs.ActionResult.CrashedPlaneId = tempCrashed.Id //被撞击棋子ID tempCrashed.Position = 0 tempCrashed.CanMove = false //玩家被踩加一 crashedPlayer.Death++ //踩别人的棋子额外获得一次机会 gs.Players[chairId].Kills++ } //没有攻击玩家 并且 点数不是6 或者 6的次数没有超过限制 则到下一个玩家操作 if total != 1 && (gs.Players[chairId].Number != 6 || gs.Players[chairId].ContinueSixPoint > 2) { isNextChair = true } } gs.ActionResult.PlaneId = planeId gs.ActionResult.Position = gs.Players[chairId].Planes[planeId].Position } else { if !isValidPoint(number) { return false, "wrong point", isNextChair, stepCount } //掷骰子 ok := gs.Players[chairId].setRollNumber(number) gs.ActionResult.Number = number if ok { if !gs.Players[chairId].isCanMove() { //如果都不移动则判断是否快到终点 gs.Players[chairId].isWillWin() isNextChair = true } else { gs.LastTurn = gs.WhoseTurn } } else { //如果点数设置失败(6的次数超过限制) 则直接跳过 isNextChair = true } } gs.Players[chairId].LastAction = action gs.ActionResult.Action = action if isNextChair { //重置6次数 要在此处重置 否则会导致到终点后重新计算 gs.Players[chairId].ContinueSixPoint = 0 } gs.userActions = append(gs.userActions, userAction{ChairId: chairId, Action: action, Number: number, PlaneId: planeId}) return true, "", isNextChair, stepCount } func (gs *GameScene) initActionResult() { gs.ActionResult = ActionResult{Action: -1, Number: -1, PlaneId: -1, Position: -1, CrashedChairId: -1, CrashedPlaneId: -1} return } func (gs *GameScene) initData() { gs.Index = 0 gs.pool = 0 gs.WhoseTurn = -1 gs.Phase = Phase_Free gs.WhoseTurn = 0 gs.LastTurn = 0 gs.initActionResult() gs.Players = make([]PlayerInfo, CHAIR_COUNT) gs.userActions = []userAction{} } func (gs *GameScene) getValidUserCount() int { ret := 0 for i := 0; i < CHAIR_COUNT; i++ { if gs.Players[i].IsValid { ret++ } } return ret } func (gs *GameScene) dump(tableId int) { log.Debug("====GameScene Table[%d]====", tableId) log.Debug(" Phase[%d],WhoseTurn[%d],LastTurn[%d]", gs.Phase, gs.WhoseTurn, gs.LastTurn) log.Debug(" Users:%d", gs.getValidUserCount()) for i := 0; i < CHAIR_COUNT; i++ { if !gs.Players[i].IsValid { continue } gs.Players[i].dump(i) } if len(gs.userActions) > 0 { log.Debug(" Actions:%d", len(gs.userActions)) for _, v := range gs.userActions { v.dump() } } } func (gs *GameScene) getPlayerCount() int { ret := 0 for i := 0; i < CHAIR_COUNT; i++ { if !gs.Players[i].IsValid { continue } ret++ } return ret } func (gs *GameScene) getScore(userId int) int { for i := 0; i < CHAIR_COUNT; i++ { if gs.Players[i].userId == userId { return gs.Players[i].Score } } return 0 } func (gs *GameScene) getScene(chairId int, player bool) string { d, _ := json.Marshal(gs) return string(d) } // 找到下一个操作者 顺时针操作 func (gs *GameScene) nextChair() { gs.LastTurn = gs.WhoseTurn for i := 1; i < CHAIR_COUNT; i++ { next := (gs.LastTurn + i) % CHAIR_COUNT if !gs.Players[next].IsValid { continue } if gs.Players[next].Dropped { continue } gs.Players[next].LastAction = Action_Move gs.WhoseTurn = next break } } func (gs *GameScene) addWinner(chairId int) (int, bool) { leftPlayerCount := 0 totalPlayerCount := 0 leftChair := CHAIR_COUNT for i := 0; i < CHAIR_COUNT; i++ { if !gs.Players[i].IsValid { continue } totalPlayerCount++ if chairId == i { continue } if gs.Players[i].Dropped { gs.Players[i].Place = -1 continue } leftPlayerCount++ leftChair = i } multiple := totalPlayerCount //4人游戏第一名拿3份 if totalPlayerCount == 4 { multiple = 3 } //大家都弃权了 第一名拿全部池子内金币 if leftPlayerCount == 0 { gs.Players[chairId].Score = gs.pool gs.Players[chairId].Place = 1 return gs.Players[chairId].Score, true } if gs.pool >= gs.base*multiple { gs.Players[chairId].Score = gs.base * multiple gs.Players[chairId].Place = 1 } else if gs.pool <= gs.base && totalPlayerCount > 2 { //池子还有钱并且是4人游戏 则第二名拿剩下的一份 gs.Players[chairId].Score = gs.pool gs.Players[chairId].Place = 2 } gs.pool -= gs.Players[chairId].Score if gs.pool > 0 && leftPlayerCount == 1 { gs.Players[leftChair].Score = gs.pool gs.Players[leftChair].Place = 2 gs.pool = 0 } return gs.Players[chairId].Score, gs.pool == 0 } // commands const ( CMD_ROOMINFO = "CMD_ROOMINFO" CMD_ACTION = "CMD_ACTION" CMD_TABLECHAT = "CMD_TABLECHAT" CMD_CANCLE_AUTO = "CMD_CANCLE_AUTO" CMD_DROP = "CMD_DROP" ) type CmdAction struct { Action int PlaneId int } // 广播Drop type CmdDrop struct { ChairId int }