game_sync/gamesrv/chess/scene.go

784 lines
19 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package chesstitians
import (
"fmt"
"math"
"sort"
"strings"
"time"
"mongo.games.com/goserver/core/logger"
"mongo.games.com/game/common"
rule "mongo.games.com/game/gamerule/chess"
"mongo.games.com/game/gamesrv/base"
"mongo.games.com/game/model"
"mongo.games.com/game/proto"
"mongo.games.com/game/protocol/chesstitians"
"mongo.games.com/game/protocol/server"
"mongo.games.com/game/srvdata"
)
// SceneEx 房间上的额外数据
type SceneEx struct {
*base.Scene // 场景
players map[int32]*PlayerEx // 玩家信息
seats [rule.MaxNumOfPlayer]*PlayerEx // 玩家
opPos int // 当前等待出牌的玩家位置
winSnId int32 // 赢家id
masterSnId int32 // 房主
isAllRob bool // 是否是纯AI场
chess rule.Logic // 棋盘对象
lastMove []int32 // 上一步的行动
variant int // 棋类变种 对应fairy-stockfish
stepSnId int32 // 计步玩家SnId
stepNum int // 计步回合数
stepLimit int // 计步最大回合数
stepKing int // 孤王计步回合数
drawSnId int32 // 求和的玩家SnId
nextSnId int32 // 再来一局玩家SnId
}
func getChessVariant(gameId int) int {
var variant int
switch gameId {
case common.GameId_Chesstitians:
variant = rule.ChessVarChess
case common.GameId_ChesstitiansMakruk:
variant = rule.ChessVarMakruk
case common.GameId_ChesstitiansCambodian, common.GameId_ChesstitiansCambodianRobot:
variant = rule.ChessVarCambodian
}
return variant
}
func NewSceneEx(s *base.Scene) *SceneEx {
variant := getChessVariant(s.GameId)
chess := rule.NewChess(variant)
chess.Init()
sceneEx := &SceneEx{
Scene: s,
chess: chess,
players: make(map[int32]*PlayerEx),
variant: variant,
}
return sceneEx
}
func (e *SceneEx) init() bool {
// 初始配置
e.SetPlayerNum(rule.MaxNumOfPlayer)
return true
}
func (e *SceneEx) Clear() {
// 重置
e.chess.Init()
e.opPos = rule.InvalidPos
e.isAllRob = false
e.winSnId = 0
e.nextSnId = 0
for _, player := range e.players {
if player != nil {
player.UnmarkFlag(base.PlayerState_WaitNext)
}
}
e.lastMove = e.lastMove[:0]
e.stepSnId = 0
e.stepNum = 0
e.stepLimit = 0
e.drawSnId = 0
}
func (e *SceneEx) CanStart() bool {
if e.GetGaming() {
return false
}
if e.GetPlayerCnt() == rule.MaxNumOfPlayer {
return true
}
return false
}
func (e *SceneEx) GetPlayerCnt() int {
var cnt int
for i := 0; i < e.GetPlayerNum(); i++ {
playerEx := e.seats[i]
if playerEx == nil {
continue
}
cnt++
}
return cnt
}
func (e *SceneEx) GetGamingPlayerCnt() int {
var cnt int
for i := 0; i < e.GetPlayerNum(); i++ {
playerEx := e.seats[i]
if playerEx == nil {
continue
}
if playerEx.IsGameing() {
cnt++
}
}
return cnt
}
func (e *SceneEx) GetRobotCnt() int {
var cnt int
for i := 0; i < e.GetPlayerNum(); i++ {
playerEx := e.seats[i]
if playerEx == nil {
continue
}
if playerEx.IsRob {
cnt++
}
}
return cnt
}
func (e *SceneEx) GetGamingRobotCnt() int {
var cnt int
for i := 0; i < e.GetPlayerNum(); i++ {
playerEx := e.seats[i]
if playerEx == nil {
continue
}
if playerEx.IsRob && playerEx.IsGameing() {
cnt++
}
}
return cnt
}
func (e *SceneEx) GetSeatPlayerCnt() int {
var cnt int
for i := 0; i < e.GetPlayerNum(); i++ {
playerEx := e.seats[i]
if playerEx == nil {
continue
}
cnt++
}
return cnt
}
func (e *SceneEx) delPlayer(p *base.Player) {
if p, exist := e.players[p.SnId]; exist {
e.seats[p.GetPos()] = nil
delete(e.players, p.SnId)
}
}
func (e *SceneEx) OnPlayerLeave(p *base.Player, reason int) {
e.delPlayer(p)
e.BroadcastPlayerLeave(p, reason)
}
func (e *SceneEx) SceneDestroy(force bool) {
//销毁房间
e.Scene.Destroy(force)
}
func (e *SceneEx) BroadcastPlayerLeave(p *base.Player, reason int) {
scLeavePack := &chesstitians.SCChesstitiansPlayerLeave{
Pos: proto.Int(p.GetPos()),
}
proto.SetDefaults(scLeavePack)
logger.Logger.Trace("SCChesstitiansPlayerLeave: ", p.SnId, " scLeavePack: ", scLeavePack)
e.Broadcast(int(chesstitians.ChesstitiansPacketID_PACKET_SCChesstitiansPlayerLeave), scLeavePack, p.GetSid())
}
func (e *SceneEx) BroadcastAudienceNum(p *base.Player) {
pack := &chesstitians.SCChesstitiansUpdateAudienceNum{
AudienceNum: proto.Int(e.GetAudiencesNum()),
}
proto.SetDefaults(pack)
e.Broadcast(int(chesstitians.ChesstitiansPacketID_PACKET_SCChesstitiansUpdateAudienceNum), pack, p.GetSid())
}
// BroadcastUpdateMasterSnId 广播房主更换
func (e *SceneEx) BroadcastUpdateMasterSnId(changeState bool) {
pack := &chesstitians.SCChesstitiansUpdateMasterSnid{
MasterSnid: proto.Int32(e.masterSnId),
}
proto.SetDefaults(pack)
e.Broadcast(int(chesstitians.ChesstitiansPacketID_PACKET_SCChesstitiansUpdateMasterSnid), pack, 0)
//logger.Logger.Trace("BroadcastUpdateMasterSnId ", pack)
//重置开始倒计时
if changeState && e.CanStart() {
e.Scene.ChangeSceneState(rule.SceneStateWaitStart)
}
}
func (e *SceneEx) playerOpPack(snId int32, opcode int, opRetCode chesstitians.OpResultCode, params []int64) *chesstitians.SCChesstitiansPlayerOp {
pack := &chesstitians.SCChesstitiansPlayerOp{
OpCode: proto.Int(opcode),
OpParam: params,
SnId: proto.Int32(snId),
OpRetCode: opRetCode,
Check: e.chess.GetCheck(),
Checkmate: e.chess.GetCheckmate(),
StepSnId: e.stepSnId,
StepNum: int64(proto.Int(e.stepNum)),
StepLimit: int64(e.stepLimit),
}
p := e.GetPlayer(snId)
if p != nil {
if ex, ok := p.ExtraData.(*PlayerEx); ok {
pack.TotalTime = append(pack.TotalTime, ex.GetTotalTime())
for _, v := range e.seats {
if v != nil && v.Pos != ex.Pos {
pack.TotalTime = append(pack.TotalTime, v.GetTotalTime())
break
}
}
}
}
proto.SetDefaults(pack)
return pack
}
// OnPlayerSCOp 发送玩家操作情况
func (e *SceneEx) OnPlayerSCOp(p *base.Player, opcode int, opRetCode chesstitians.OpResultCode, params []int64) {
pack := e.playerOpPack(p.SnId, opcode, opRetCode, params)
p.SendToClient(int(chesstitians.ChesstitiansPacketID_PACKET_SCChesstitiansPlayerOp), pack)
logger.Logger.Tracef("OnPlayerSCOp %s", pack)
}
// BroadcastPlayerSCOp 广播发送玩家操作情况
func (e *SceneEx) BroadcastPlayerSCOp(snid int32, opcode int, opRetCode chesstitians.OpResultCode, params []int64) {
pack := e.playerOpPack(snid, opcode, opRetCode, params)
e.Broadcast(int(chesstitians.ChesstitiansPacketID_PACKET_SCChesstitiansPlayerOp), pack, 0)
logger.Logger.Tracef("BroadcastPlayerSCOp %s", pack)
}
// FindOnePos 逆时针找一个空位
func (e *SceneEx) FindOnePos() int {
for i := 0; i < e.GetPlayerNum(); i++ {
if e.seats[i] == nil {
return i
}
}
return rule.InvalidPos
}
func (e *SceneEx) SystemCoinOut() int64 {
systemGain := int64(0)
for i := 0; i < e.GetPlayerNum(); i++ {
playerData := e.seats[i]
if playerData != nil && playerData.IsGameing() && playerData.IsRob {
systemGain += playerData.winCoin
}
}
return systemGain
}
func (e *SceneEx) AllPlayerEnterGame() {
for i := 0; i < e.GetPlayerNum(); i++ {
if e.seats[i] == nil {
continue
}
if e.seats[i].IsMarkFlag(base.PlayerState_GameBreak) == false {
e.seats[i].UnmarkFlag(base.PlayerState_WaitNext)
e.seats[i].SyncFlag()
}
}
}
func (e *SceneEx) SetCurOpPos(pos int) {
e.opPos = pos
e.seats[pos].lastTime = time.Now()
}
func (e *SceneEx) GetCurOpPos() int {
return e.opPos
}
func (e *SceneEx) AddChessGrade(p *PlayerEx, num int64) {
if p == nil {
return
}
// 人机对战没有积分
if e.GetGameId() != common.GameId_ChesstitiansCambodianRobot {
p.AddChessGrade(num)
}
}
func (e *SceneEx) AddCoin(p *PlayerEx, num int64, gainWay int32, syncFlag int, oper, remark string) {
if p == nil {
return
}
// 人机对战没有金币结算
if e.GetGameId() != common.GameId_ChesstitiansCambodianRobot {
p.AddCoin(num, gainWay, syncFlag, oper, remark)
}
}
func (e *SceneEx) GetSeatByCKey(cKey string) *PlayerEx {
_cKey := strings.ToLower(cKey)
for _, seat := range e.seats {
if _cKey == "w" && !seat.isBlack || _cKey == "b" && seat.isBlack {
return seat
}
}
return nil
}
// ChessInit 棋盘初始化
func (e *SceneEx) ChessInit() {
e.chess.Init()
e.lastMove = []int32{}
// 空结构
// 随机
firstIdx := e.RandInt(2)
fmt.Println("@@firstIdx", firstIdx)
for idx, seat := range e.seats {
if seat != nil {
if idx == firstIdx {
// 默认seats中的第一个是白棋先走
seat.isBlack = false
} else {
seat.isBlack = true
}
seat.oldGrade = seat.ChessGrade
pack := &chesstitians.SCChesstitiansCard{}
// 通过cards[0] 传递isBlack
if seat.isBlack {
pack.Cards = append(pack.Cards, int32(1))
} else {
pack.Cards = append(pack.Cards, int32(0))
}
// 生成棋数据
pack.Chess = e.chess.GetChess()
pack.Act = e.chess.GetAct()
pack.Castling = e.chess.GetCastling()
pack.CambodianMove = e.chess.GetCambodianMove()
pack.Enpassant = int32(e.chess.GetEnPassant())
pack.ChessRound = int32(e.chess.GetRound())
pack.Check = e.chess.GetCheck()
pack.Checkmate = e.chess.GetCheckmate()
proto.SetDefaults(pack)
seat.SendToClient(int(chesstitians.ChesstitiansPacketID_PACKET_SCChesstitiansCard), pack)
}
}
}
// PlayChess 判断客户端发来的行棋操作,返回是否合法
func (e *SceneEx) PlayChess(fromTo []int64) bool {
fromIdx, toIdx := int(fromTo[0]), int(fromTo[1])
// 判断该谁走
// 检查棋子本身能不能走
if !e.chess.CanMove(fromIdx, toIdx) {
logger.Logger.Errorf("非法行棋(棋子本身的路线错误) %v %v %v", fromIdx, toIdx, e.chess.GetPiece(fromIdx))
return false
}
// 王被吃
toPiece := e.chess.GetPiece(toIdx)
if toPiece == rule.WK { // 白王被吃,标记黑方获胜
for _, seat := range e.seats {
if seat != nil {
if seat.isBlack {
e.winSnId = seat.GetSnId()
}
}
}
} else if toPiece == rule.BK { // 黑王被吃,标记白方获胜
for _, seat := range e.seats {
if !seat.isBlack {
e.winSnId = seat.GetSnId()
}
}
}
c := e.chess.GetPiece(fromIdx)
e.chess.Move(fromIdx, toIdx)
e.lastMove = []int32{int32(fromIdx), int32(toIdx)}
// 将死
if rule.IsWhite(c) && e.chess.IsCheckmate(true) { // 白方胜
e.winSnId = e.GetSeatByCKey("w").GetSnId()
} else if !rule.IsWhite(c) && e.chess.IsCheckmate(false) { // 黑方胜
e.winSnId = e.GetSeatByCKey("b").GetSnId()
}
// 无子可动平局
if rule.IsWhite(c) && e.chess.IsStop(false) {
e.ChangeSceneState(rule.SceneStateBilled)
} else if rule.IsBlack(c) && e.chess.IsStop(true) {
e.ChangeSceneState(rule.SceneStateBilled)
}
return true
}
// NextOpPos 轮换到下一个操作的玩家
func (e *SceneEx) NextOpPos() {
lastPlayer := e.seats[e.opPos]
if !lastPlayer.lastTime.IsZero() {
lastPlayer.totalTime += int64(time.Now().Sub(lastPlayer.lastTime).Seconds())
}
e.opPos = (e.opPos + 1) % rule.MaxNumOfPlayer
e.chess.NextAct()
e.chess.PrintChess()
s := e.chess.GenFen()
println(s)
// 计步
if e.IsStep() {
curPlayer := e.seats[e.opPos]
if curPlayer.SnId == e.stepSnId {
e.stepNum++
}
if e.IsKingStep() {
e.stepKing++
if e.stepKing == 2 {
e.stepNum++
e.stepKing = 0
}
}
if e.stepNum > e.stepLimit {
// 计步结束,平局
e.ChangeSceneState(rule.SceneStateBilled)
return
}
}
// 检查孤王计步
// 任何一方只剩一个王时,开始计步;取消残局计步;确定回合限制
if !e.IsKingStep() {
if e.chess.Has(rule.WK) && e.chess.GetWhiteNum() == 1 ||
e.chess.Has(rule.BK) && e.chess.GetBlackNum() == 1 {
if e.IsStep() {
e.cancelStep()
}
e.StartKingStep()
}
}
// 记录操作开始时间
e.seats[e.opPos].lastTime = time.Now()
}
// IsStep 是否计步中
func (e *SceneEx) IsStep() bool {
if e.stepLimit > 0 {
return true
}
return false
}
func (e *SceneEx) IsKingStep() bool {
if e.stepSnId == 0 && e.stepLimit > 0 {
return true
}
return false
}
// CanStep 是否可以计步
func (e *SceneEx) CanStep() bool {
if e.IsStep() {
return false
}
// 双方都没有兵
if e.chess.GetChessNum(rule.WP) == 0 && e.chess.GetChessNum(rule.BP) == 0 {
return true
}
return false
}
// StartStep 玩家开始计步
func (e *SceneEx) StartStep(p *PlayerEx) bool {
if !e.CanStep() {
return false
}
e.stepSnId = p.GetSnId()
e.stepNum = 1
e.stepLimit = rule.PlayerStepLimit
return true
}
// StartKingStep 开始孤王计步
func (e *SceneEx) StartKingStep() {
e.stepKing = 0
c := rule.White
if e.chess.Has(rule.WK) && e.chess.GetWhiteNum() == 1 {
c = rule.Black
}
//双车8回合
//单车16回合
//双象22回合
//双马32回合
//单象44回合
//单马64回合
//其他情况64回合
r := e.chess.GetChessNum(rule.ToPiece(c, rule.R))
if r == 2 {
e.stepLimit = 8
} else if r == 1 {
e.stepLimit = 16
}
if e.stepLimit == 0 {
s := e.chess.GetChessNum(rule.ToPiece(c, rule.S))
if s == 2 {
e.stepLimit = 22
} else {
n := e.chess.GetChessNum(rule.ToPiece(c, rule.N))
if n == 2 {
e.stepLimit = 32
} else if s == 1 {
e.stepLimit = 44
} else if n == 1 {
e.stepLimit = 64
}
}
}
if e.stepLimit == 0 {
e.stepLimit = 64
}
// 起始回合数
//e.stepNum = e.chess.GetWhiteNum() + e.chess.GetBlackNum() + 1
e.stepNum = 1
}
// cancelStep 取消计步
func (e *SceneEx) cancelStep() {
e.stepSnId = 0
e.stepNum = 0
e.stepLimit = 0
}
// CancelStep 玩家取消计步
func (e *SceneEx) CancelStep(p *PlayerEx) bool {
if !e.IsStep() {
return false
}
if e.stepSnId != p.GetSnId() {
return false
}
e.cancelStep()
return true
}
// RequestDraw 玩家求和
func (e *SceneEx) RequestDraw(p *PlayerEx) bool {
if e.drawSnId > 0 {
return false
}
e.drawSnId = p.GetSnId()
return true
}
// RefuseDraw 玩家拒绝求和
func (e *SceneEx) RefuseDraw(p *PlayerEx) bool {
if e.drawSnId == 0 {
return false
}
if e.drawSnId == p.GetSnId() {
return false
}
e.drawSnId = 0
return true
}
// AgreeDraw 玩家同意求和
func (e *SceneEx) AgreeDraw(p *PlayerEx) bool {
if e.drawSnId == 0 {
return false
}
if e.drawSnId == p.GetSnId() {
return false
}
e.drawSnId = 0
e.ChangeSceneState(rule.SceneStateBilled)
return true
}
// CancelDraw 撤销求和
func (e *SceneEx) CancelDraw(p *PlayerEx) bool {
if e.drawSnId == 0 {
return false
}
if e.drawSnId != p.GetSnId() {
return false
}
e.drawSnId = 0
return true
}
func (e *SceneEx) GetChessRank(p *PlayerEx) int32 {
i := sort.Search(len(e.ChessRank), func(i int) bool {
return int64(e.ChessRank[i]) > p.ChessGrade
})
if i == len(e.ChessRank) {
return e.ChessRank[i-1]
}
i = i - 1
if i < 0 {
i = 0
}
if i < len(e.ChessRank) {
return e.ChessRank[i]
}
return 0
}
func (e *SceneEx) GetChessRankNext(p *PlayerEx) int32 {
i := sort.Search(len(e.ChessRank), func(i int) bool {
return int64(e.ChessRank[i]) > p.ChessGrade
})
if i == len(e.ChessRank) {
return e.ChessRank[i-1]
}
return e.ChessRank[i]
}
func (e *SceneEx) ToBilled(p1, p2 *PlayerEx, isWin int) (*chesstitians.ChesstitiansPlayerGameBilled, *model.ChesstitiansPerson) {
// 获取结算规则
var typeId int32
var billedRule, billedRuleDraw *server.DB_ChessBilledRules
// 人机对战不结算
if e.GetGameId() != common.GameId_ChesstitiansCambodianRobot {
winRank := e.GetChessRank(p1)
loseRank := e.GetChessRank(p2)
if winRank > loseRank {
typeId = rule.BilledTypeGT
} else if winRank < loseRank {
typeId = rule.BilledTypeLT
} else {
typeId = rule.BilledTypeEQ
}
for _, v := range srvdata.PBDB_ChessBilledRulesMgr.Datas.Arr {
if v.GetTypeId() == typeId {
billedRule = v
}
if v.GetTypeId() == rule.BilledTypeEQ {
billedRuleDraw = v
}
}
}
p1.isWin = int32(isWin)
p1.winCoin = 0
p1.taxCoin = 0
p1.winTimes = 0
p1.winScore = 0
p1.otherScore = 0
p1.nextRank = 0
switch isWin {
case rule.Win:
// 金币和税收
gainScore := int64(float64(e.GetBaseScore()) * float64(10000-e.GetDBGameFree().GetTaxRate()) / 10000.0)
p1.winCoin = gainScore
p1.taxCoin = int64(e.GetBaseScore()) - gainScore
// 连赢次数
winTimes := p1.GetGameFreeIdData(e.KeyGamefreeId).Statics.WinGameTimesNum
e.AddCoin(p1, gainScore, common.GainWay_CoinSceneWin, 0, "system", e.GetSceneName())
p1.winTimes = int32(winTimes + 1)
var winScore int32
if billedRule != nil {
// 连赢积分
if p1.GetGameFreeIdData(e.KeyGamefreeId).Statics.GetInt(rule.StaticsChessTime)+1 >= int(billedRule.GetWinTimes()) {
p1.GetGameFreeIdData(e.KeyGamefreeId).Statics.SetInt(rule.StaticsChessTime, 0)
p1.AddChessGrade(int64(billedRule.OtherScore))
p1.otherScore = billedRule.OtherScore
winScore += billedRule.OtherScore
} else {
p1.GetGameFreeIdData(e.KeyGamefreeId).Statics.Incr(rule.StaticsChessTime)
}
// 总积分
e.AddChessGrade(p1, int64(billedRule.GetWinScore()))
// 本局积分
winScore += billedRule.GetWinScore()
p1.winScore = winScore
}
case rule.Lose:
e.AddCoin(p1, int64(-e.GetDBGameFree().GetBaseScore()), common.GainWay_CoinSceneLost, 0, "system", e.GetSceneName())
p1.GetGameFreeIdData(e.KeyGamefreeId).Statics.SetInt(rule.StaticsChessTime, 0)
p1.winCoin = int64(-e.GetDBGameFree().GetBaseScore())
if billedRule != nil {
// 总积分
e.AddChessGrade(p1, int64(billedRule.GetLoseScore()))
// 本局积分
p1.winScore = proto.Int32(billedRule.GetLoseScore())
}
case rule.Draw:
p1.GetGameFreeIdData(e.KeyGamefreeId).Statics.SetInt(rule.StaticsChessTime, 0)
if billedRule != nil {
// 总积分
e.AddChessGrade(p1, int64(billedRule.GetDrawScore()))
// 本局积分
p1.winScore = proto.Int32(billedRule.GetDrawScore())
}
}
if billedRuleDraw != nil {
// 还有几局晋级
n := math.Ceil(float64(int64(e.GetChessRankNext(p1))-p1.ChessGrade) / float64(billedRuleDraw.GetWinScore()))
p1.nextRank = int32(n)
}
// 人机对战,没有输赢分,没有积分变化
if e.GetGameId() == common.GameId_ChesstitiansCambodianRobot {
p1.winCoin = 0
p1.taxCoin = 0
p1.winScore = 0
p1.otherScore = 0
}
billData := &chesstitians.ChesstitiansPlayerGameBilled{
SnId: proto.Int32(p1.SnId),
WinCoin: proto.Int64(p1.winCoin),
GameCoin: proto.Int64(p1.GetCoin()),
IsWin: proto.Int32(p1.isWin),
WinTimes: proto.Int32(p1.winTimes),
OtherScore: proto.Int32(p1.otherScore),
NextRank: proto.Int32(p1.nextRank),
Score: proto.Int32(int32(p1.ChessGrade)),
WinScore: proto.Int32(p1.winScore),
OldChessGrade: proto.Int64(p1.oldGrade),
}
chessPerson := &model.ChesstitiansPerson{
UserId: p1.SnId,
UserIcon: p1.Head,
Platform: p1.Platform,
Channel: p1.Channel,
Promoter: p1.BeUnderAgentCode,
PackageTag: p1.PackageID,
InviterId: p1.InviterId,
WBLevel: p1.WBLevel,
IsRob: p1.IsRob,
IsFirst: e.IsPlayerFirst(e.GetPlayer(p1.SnId)),
IsLeave: false,
IsWin: p1.isWin,
Seat: p1.GetPos(),
GainCoin: p1.winCoin,
GainTaxCoin: p1.taxCoin,
}
return billData, chessPerson
}