package gamelogic import ( "encoding/json" "bet24.com/log" badge "bet24.com/servers/micros/badge/proto" ) const ( GAMEID = 38 GAME_NAME = "quickludo" 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 TIMER_QUIT_0 TIMER_QUIT_1 TIMER_QUIT_2 TIMER_QUIT_3 ) const SEC_READY = 30 * 1000 //准备计时 const SEC_MATCH = 4000 //匹配计时 4000 匹配动画时间 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 = 58 // 终点 const SAFE_START = 53 //安全通道起始坐标 const LOOP_START = 52 //转圈起始位 const LOOP_END = 51 //转圈截止位 const ( Action_Dice = iota // 投掷骰子 Action_Move // 移动飞机 Action_Drop // 2 弃牌 Action_Invalid ) // 飞机是否安全 func isSafe(pos int) bool { //还未出发 if pos == 0 { return true } if pos >= SAFE_START { return true } //第九格和第14格为安全区 return (pos-9)%13 == 0 || (pos-14)%13 == 0 } // 查找优先级高的棋子 func (gs *GameScene) getPriorityPlane(chairId int) (int, int) { unsafePlane := make(map[int]int) farthestPlane := -1 farthestPosition := -1 myPlanes := gs.Players[chairId].Planes for k, v := range myPlanes { if !v.CanMove { continue } if v.Position > farthestPosition { farthestPlane = v.Id farthestPosition = v.Position } if isSafe(v.Position) { continue } for k1, v1 := range myPlanes { if k == k1 || !v1.CanMove { continue } //叠棋 if v.Position == v1.Position && v.Id != v1.Id { continue } unsafePlane[v.Id] = v.Position } } unsafePlaneId := -1 if len(unsafePlane) > 0 { farthest := -1 //最远的 for unsafeId, unsafePosition := range unsafePlane { for i := 0; i < CHAIR_COUNT; i++ { //不跟自己比 if i == chairId || !gs.Players[i].IsValid || gs.Players[i].Dropped { continue } if gs.Players[i].checkChairPosition(chairId, unsafePosition) == 2 { //有危险 if unsafePosition > farthest { farthest = unsafePosition unsafePlaneId = unsafeId } } } } } //存在风险的棋子和最远端的棋子 return unsafePlaneId, farthestPlane } // 检查起飞区附近是否有敌人 func (gs *GameScene) checkTookOffArea(chairId int) bool { //检查全部玩家 for i := 0; i < CHAIR_COUNT; i++ { if i == chairId || !gs.Players[i].IsValid || gs.Players[i].Dropped { continue } //坐标转换后在1-10之间的都算有棋子 for _, v := range gs.Players[i].Planes { if v.Position == 0 { continue } if gs.Players[i].convertPlanePosition(v.Position) <= 10 { //有棋子 return true } } } return false } // 是否相撞 func isCrash(chair1, pos1, chair2, pos2 int) bool { if pos1 == 0 || pos2 == 0 || isSafe(pos1) || isSafe(pos2) { return false } return (chair1*13+pos1)%LOOP_START == (chair2*13+pos2)%LOOP_START } // 是否有效座位 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 } func (ua *userAction) dump() { log.Debug(" Chair[%d],Action[%d],PlaneId[%d],Number[%d]", ua.ChairId, ua.Action, ua.PlaneId, ua.Number) } // 弃权 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 { 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) checkFrontHasEnemy(chairId, position, planeId int) bool { //敌人的座位号 enemyChairId := (chairId + 2) % CHAIR_COUNT planeCount := gs.Players[chairId].checkSamePositionCount(position, planeId) if planeCount != 0 { return false } crashed := gs.Players[enemyChairId].checkCrashed(chairId, position) crashedCount := len(crashed) return crashedCount == 1 } // 得到不能攻击的点数 func (gs *GameScene) getUnAttackPoint(chairId int) []int { //临时标记 points := []int{1, 2, 3, 4, 5, 6} unAttackPoint := make([]int, 0) attackPoint := make([]int, 0) for _, v := range gs.Players[chairId].Planes { //没有出机场或者已经到终点和快到终点的过滤 if v.Position == 0 || v.Position == MAX_STEP || v.Position >= SAFE_START { continue } for _, p := range points { //可以攻击 则替换点数 isHasEnemy := gs.checkFrontHasEnemy(chairId, v.Position+p, v.Id) if isHasEnemy { attackPoint = append(attackPoint, p) } } } if len(attackPoint) == 0 { return unAttackPoint } //剔除可以攻击的点数 for _, v := range points { isHas := false for _, p := range attackPoint { if v == p { isHas = true break } } if !isHas { unAttackPoint = append(unAttackPoint, v) } } return unAttackPoint } // 检查棋子移动后结果 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() if myPositionCount < 10 { 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() if ret >= myPositionCount+10 { return true } } return false } // 计算机器人飞机移动权重 func (gs *GameScene) calcRobotPlaneWeight(chairId, number int) int { // 初始化变量 tempWeight := -1 // 权重 weightPlaneId := -1 // 权重最高的棋子 planeId := -1 farthest := -1 // 最远的 tookOffCount, leaveCount := gs.Players[chairId].checkTookOffCount() checkTookOffArea := false if number == 6 && tookOffCount < 4 && leaveCount == tookOffCount { //摇到6 并且还有没有起飞的 已经起飞的都离开起飞区域了 checkTookOffArea = gs.checkTookOffArea(chairId) } // 获取通道是否打开的信息 openChannel := gs.Players[chairId].OpenChannel // 获取危险的棋子的编号 unsafePlaneId, farthestPlane := gs.getPriorityPlane(chairId) for _, v := range gs.Players[chairId].Planes { if !v.CanMove { continue } id := v.Id position := v.Position movePosition := position + number if openChannel && movePosition == MAX_STEP { planeId = id break } weight := -1 // 权重 relativePos := (movePosition) % 13 //移动后的相对坐标 //计算距离 safeDistance := (relativePos - 9) % 13 if safeDistance <= (relativePos-14)%13 { safeDistance = (relativePos - 14) % 13 } if movePosition == 48 { //最后一个安全区 weight = 7 } else if id == unsafePlaneId || safeDistance == 0 { weight = 6 } if position == 0 && (checkTookOffArea || (tookOffCount == 1 && !openChannel)) { weight = 5 } if openChannel { if movePosition >= LOOP_START { weight = 9 } } else { if position == LOOP_END || movePosition > LOOP_END { weight = 0 } } //判断是否可以攻击 if position != 0 && (weight == -1 || weight == 6) { total, _, _ := gs.checkPositionIsCrashed(chairId, movePosition, id) if total == 1 { weight = 8 } } if weight == -1 { weight = 3 if position == 0 { weight = 2 } if weight != 2 { moveResult := gs.checkPlaneMoveResult(chairId, movePosition) //移动后的结果 if moveResult == 1 { //如果是可以追击则权重上升 weight++ } else if moveResult == 2 { if number != 6 { //超过去有危险 weight = 1 } else if gs.Players[chairId].ContinueSixPoint >= 1 { //摆脱机会不大 哪怕本次摇出6 接下来再次出6无法移动 weight = 2 if (position-9)%13 == 0 || (position-14)%13 == 0 { //已经在安全区 weight = 1 } } } else { //最远端的 if id == farthestPlane { weight = 6 } } } } if weight > tempWeight || (weight == tempWeight && position > farthest) { farthest = position weightPlaneId = id if weight > tempWeight { tempWeight = weight } } } //找到优先级更高的棋子时 根据权重选择 if !isValidPlane(planeId) && isValidPlane(weightPlaneId) { planeId = weightPlaneId } return planeId } 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 !isValidPlane(planeId) { planeId = gs.tryMovePlane(chairId, isRobot) } ok, movePlaneId, isReach, moveStepCount, oldPosition := gs.Players[chairId].canMovePlane(planeId) if !ok { return false, "wrong movement", isNextChair, moveStepCount } 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++ //打开通道 gs.Players[chairId].OpenChannel = true //获得徽章 badgeParam := badge.Scope{GameName: GAME_NAME} go badge.DoAction(gs.Players[chairId].userId, badge.Action_Game_Kill, 1, badgeParam) } //没有攻击玩家 并且 点数不是6 或者 6的次数没有超过限制 则到下一个玩家操作 if total != 1 && (gs.Players[chairId].Number != 6 || gs.Players[chairId].ContinueSixPoint > 2) { isNextChair = true } } gs.ActionResult.PlaneId = planeId gs.ActionResult.OldPosition = oldPosition 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() { isNextChair = true } else { gs.LastTurn = gs.WhoseTurn } //如果通道打开了则判断 if gs.Players[chairId].OpenChannel { //如果都不移动则判断是否快到终点 gs.Players[chairId].isWillWin() } } 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) tryMovePlane(chairId int, isRobot bool) int { var planeId int = -1 //判断是否被封禁 openChannel := gs.Players[chairId].OpenChannel number := gs.Players[chairId].Number if isRobot { //机器人计算权重 planeId = gs.calcRobotPlaneWeight(chairId, number) return planeId } //判断前端操作指令是否合理 托管 if gs.Players[chairId].AutoOut { farthest := -1 //最远的 tempPlaneId := -1 //临时标记 for _, v := range gs.Players[chairId].Planes { if !v.CanMove { continue } //先出机场中棋子 if v.Position == 0 { planeId = v.Id break } //其次走最后是距离 if v.Position > farthest { if !openChannel && (v.Position == LOOP_END || v.Position+number > LOOP_END) { tempPlaneId = v.Id } else { farthest = v.Position planeId = v.Id } } } if !isValidPlane(planeId) { planeId = tempPlaneId } } return planeId } func (gs *GameScene) initActionResult() { gs.ActionResult = ActionResult{Action: -1, Number: -1, PlaneId: -1, OldPosition: -1, Position: -1, CrashedChairId: -1, CrashedPlaneId: -1} } 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) 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人游戏第一名拿4份 第二名没有奖励 if totalPlayerCount == 4 { multiple = 4 } //大家都弃权了 第一名拿全部池子内金币 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 }