package tienlen

import (
	"fmt"
	"math"
	"math/rand"
	"sort"
	"time"

	"mongo.games.com/goserver/core"
	"mongo.games.com/goserver/core/logger"

	"mongo.games.com/game/common"
	rule "mongo.games.com/game/gamerule/tienlen"
	"mongo.games.com/game/gamesrv/base"
	"mongo.games.com/game/model"
	"mongo.games.com/game/proto"
	"mongo.games.com/game/protocol/tienlen"
	"mongo.games.com/game/srvdata"
)

var ScenePolicyTienLenSingleton = &ScenePolicyTienLen{}

type ScenePolicyTienLen struct {
	base.BaseScenePolicy
	states [rule.TienLenSceneStateMax]base.SceneState
}

// 创建场景扩展数据
func (this *ScenePolicyTienLen) CreateSceneExData(s *base.Scene) interface{} {
	sceneEx := NewTienLenSceneData(s)
	if sceneEx != nil {
		sceneEx.Clear()
		if sceneEx.init() {
			s.SetExtraData(sceneEx)
		}
	}
	return sceneEx
}

// 创建玩家扩展数据
func (this *ScenePolicyTienLen) CreatePlayerExData(s *base.Scene, p *base.Player) interface{} {
	playerEx := &TienLenPlayerData{Player: p}
	if playerEx != nil {
		playerEx.init()
		p.SetExtraData(playerEx)
	}
	return playerEx
}

// 场景开启事件
func (this *ScenePolicyTienLen) OnStart(s *base.Scene) {
	logger.Logger.Trace("(this *ScenePolicyTienLen) OnStart, GetSceneId()=", s.GetSceneId())

	sceneEx := NewTienLenSceneData(s)
	if sceneEx != nil {
		sceneEx.Clear()
		if sceneEx.init() {
			s.SetExtraData(sceneEx)
			s.ChangeSceneState(rule.TienLenSceneStateWaitPlayer)
		}
	}
}

// 场景心跳事件
func (this *ScenePolicyTienLen) OnTick(s *base.Scene) {
	if s == nil {
		return
	}
	if s.GetSceneState() != nil {
		s.GetSceneState().OnTick(s)
	}
}

// 玩家进入事件
func (this *ScenePolicyTienLen) OnPlayerEnter(s *base.Scene, p *base.Player) {
	if s == nil || p == nil {
		return
	}
	logger.Logger.Trace("(this *ScenePolicyTienLen) OnPlayerEnter, GetSceneId()=", s.GetSceneId(), " player=", p.GetName())
	if sceneEx, ok := s.GetExtraData().(*TienLenSceneData); ok {
		// pos
		pos := sceneEx.FindOnePos()
		if p.Pos != -1 && sceneEx.seats[p.Pos] == nil {
			pos = p.Pos
		}
		if pos < 0 || pos > 3 || sceneEx.seats[pos] != nil {
			p.MarkFlag(base.PlayerState_EnterSceneFailed)
			cnt := len(sceneEx.players)
			logger.Logger.Warnf("ScenePolicyTienLen.OnPlayerEnter(scene:%v, player:%v) no found fit GetPos(), current player count:%v NumOfGames:%v", s.GetSceneId(), p.SnId, cnt, sceneEx.NumOfGames)
			return
		}

		// playerEx
		playerEx := &TienLenPlayerData{Player: p}
		playerEx.init()
		playerEx.SetPos(pos)
		if sceneEx.GetGaming() {
			playerEx.MarkFlag(base.PlayerState_WaitNext)
			playerEx.SyncFlag()
		}
		p.SetExtraData(playerEx)

		// sceneEx
		sceneEx.seats[pos] = playerEx
		sceneEx.players[p.SnId] = playerEx

		// 广播个人信息
		playerData := TienLenCreatePlayerData(p, p.GetRankScore(rule.RankType))
		if sceneEx.IsMatchScene() && p.IsRob {
			if len(p.MatchParams) > 2 {
				if p.MatchParams[2] != 0 {
					playerData.CopySnid = p.MatchParams[2]
				}
			}
			if len(p.MatchParams) > 3 {
				if p.MatchParams[3] != 0 {
					playerData.CopyRoleId = p.MatchParams[3]
				}
			}
		}
		pack := &tienlen.SCTienLenPlayerEnter{
			Data: playerData,
		}
		proto.SetDefaults(pack)
		s.Broadcast(int(tienlen.TienLenPacketID_PACKET_SCTienLenPlayerEnter), pack, p.GetSid())

		// 给自己发送房间信息
		TienLenSendRoomInfo(s, p, sceneEx)
		s.FirePlayerEvent(p, base.PlayerEventEnter, nil)
	}
}

// 玩家离开事件
func (this *ScenePolicyTienLen) OnPlayerLeave(s *base.Scene, p *base.Player, reason int) {
	if s == nil || p == nil {
		return
	}
	logger.Logger.Trace("(this *ScenePolicyTienLen) OnPlayerLeave, GetSceneId()=", s.GetSceneId(), " player=", p.SnId)
	sceneEx, ok := s.GetExtraData().(*TienLenSceneData)
	if !ok {
		return
	}
	sceneEx.OnPlayerLeave(p, reason)
	s.FirePlayerEvent(p, base.PlayerEventLeave, []int64{int64(reason)})
}

// 玩家掉线
func (this *ScenePolicyTienLen) OnPlayerDropLine(s *base.Scene, p *base.Player) {
	if s == nil || p == nil {
		return
	}
	logger.Logger.Trace("(this *ScenePolicyTienLen) OnPlayerDropLine, GetSceneId()=", s.GetSceneId(), " player=", p.Name)
	sceneEx, ok := s.GetExtraData().(*TienLenSceneData)
	if !ok {
		return
	}

	if sceneEx.GetGaming() {
		p.MarkFlag(base.PlayerState_Auto)
		p.SyncFlag()
	} else {
		s.PlayerLeave(p, common.PlayerLeaveReason_DropLine, true)
	}

	s.FirePlayerEvent(p, base.PlayerEventDropLine, nil)
}

// 玩家重连
func (this *ScenePolicyTienLen) OnPlayerRehold(s *base.Scene, p *base.Player) {
	if s == nil || p == nil {
		return
	}
	logger.Logger.Trace("(this *ScenePolicyTienLen) OnPlayerRehold, GetSceneId()=", s.GetSceneId(), " player=", p.Name)
	if sceneEx, ok := s.GetExtraData().(*TienLenSceneData); ok {
		if _, ok := p.GetExtraData().(*TienLenPlayerData); ok {
			if p.IsMarkFlag(base.PlayerState_Auto) {
				p.UnmarkFlag(base.PlayerState_Auto)
				p.SyncFlag()
			}

			//发送房间信息给自己
			TienLenSendRoomInfo(s, p, sceneEx)
			s.FirePlayerEvent(p, base.PlayerEventRehold, nil)
		}
	}
}

// 返回房间
func (this *ScenePolicyTienLen) OnPlayerReturn(s *base.Scene, p *base.Player) {
	if s == nil || p == nil {
		return
	}
	logger.Logger.Trace("(this *ScenePolicyTienLen) OnPlayerReturn, GetSceneId()=", s.GetSceneId(), " player=", p.Name)
	if sceneEx, ok := s.GetExtraData().(*TienLenSceneData); ok {
		if _, ok := p.GetExtraData().(*TienLenPlayerData); ok {
			if p.IsMarkFlag(base.PlayerState_Auto) {
				p.UnmarkFlag(base.PlayerState_Auto)
				p.SyncFlag()
			}

			//发送房间信息给自己
			TienLenSendRoomInfo(s, p, sceneEx)
			s.FirePlayerEvent(p, base.PlayerEventReturn, nil)
		}
	}
}

func (this *ScenePolicyTienLen) OnPlayerOp(s *base.Scene, p *base.Player, opcode int, params []int64) bool {
	if s == nil || p == nil {
		return false
	}
	logger.Logger.Trace("(this *ScenePolicyTienLen) OnPlayerOp, GetSceneId()=", s.GetSceneId(), " player=", p.GetName(), " opcode=", opcode, " params=", params)
	if s.GetSceneState() != nil {
		if s.GetSceneState().OnPlayerOp(s, p, opcode, params) {
			p.LastOPTimer = time.Now()
			return true
		}
		return false
	}
	return true
}

func (this *ScenePolicyTienLen) OnPlayerEvent(s *base.Scene, p *base.Player, evtcode int, params []int64) {
	if s == nil || p == nil {
		return
	}
	logger.Logger.Trace("(this *ScenePolicyTienLen) OnPlayerEvent, GetSceneId()=", s.GetSceneId(), " player=", p.GetName(), " eventcode=", evtcode, " params=", params)
	if s.GetSceneState() != nil {
		s.GetSceneState().OnPlayerEvent(s, p, evtcode, params)
	}
}

func (this *ScenePolicyTienLen) OnAudienceEnter(s *base.Scene, p *base.Player) {
	if s == nil || p == nil {
		return
	}
	logger.Logger.Trace("ScenePolicyTienLen OnAudienceEnter, sceneId=", s.SceneId, " player=", p.SnId)
	this.BaseScenePolicy.OnAudienceEnter(s, p)
	if sceneEx, ok := s.ExtraData.(*TienLenSceneData); ok {
		//给自己发送房间信息
		p.UnmarkFlag(base.PlayerState_Leave)
		TienLenSendRoomInfo(s, p, sceneEx)
		sceneEx.BroadcastAudienceNum(p)
	}
}

func (this *ScenePolicyTienLen) OnAudienceLeave(s *base.Scene, p *base.Player, reason int) {
	if s == nil || p == nil {
		return
	}
	logger.Logger.Trace("ScenePolicyTienLen OnAudienceLeave, sceneId=", s.SceneId, " player=", p.SnId, " reason=", reason)
	if sceneEx, ok := s.ExtraData.(*TienLenSceneData); ok {
		sceneEx.BroadcastAudienceNum(p)
	}
}

func (this *ScenePolicyTienLen) OnAudienceSit(s *base.Scene, p *base.Player) {
	if s == nil || p == nil {
		return
	}
	logger.Logger.Trace("ScenePolicyTienLen OnAudienceSit, sceneId=", s.SceneId, " player=", p.SnId)
	sceneEx, ok := s.GetExtraData().(*TienLenSceneData)
	if !ok {
		return
	}
	if s.Gaming && !s.GetBEnterAfterStart() {
		return
	}

	//自动带入金币
	pos := sceneEx.FindOnePos()
	if pos < 0 || pos > 3 || sceneEx.seats[pos] != nil {
		p.MarkFlag(base.PlayerState_EnterSceneFailed)
		cnt := len(sceneEx.players)
		logger.Logger.Warnf("ScenePolicyTienLen.OnAudienceSit(scene:%v, player:%v) no found fit GetPos(), current player count:%v NumOfGames:%v", s.GetSceneId(), p.SnId, cnt, sceneEx.NumOfGames)
		return
	}

	playerEx := &TienLenPlayerData{Player: p}
	playerEx.init()
	playerEx.SetPos(pos)
	p.UnmarkFlag(base.PlayerState_Audience)
	p.UnmarkFlag(base.PlayerState_Leave)
	if sceneEx.GetGaming() {
		playerEx.MarkFlag(base.PlayerState_WaitNext)
		p.SyncFlag()
	}

	p.SetExtraData(playerEx)
	sceneEx.seats[pos] = playerEx
	sceneEx.players[p.SnId] = playerEx

	//广播个人信息
	playerData := TienLenCreatePlayerData(p, p.GetRankScore(rule.RankType))
	pack := &tienlen.SCTienLenPlayerEnter{
		Data: playerData,
	}
	proto.SetDefaults(pack)
	s.Broadcast(int(tienlen.TienLenPacketID_PACKET_SCTienLenPlayerEnter), pack, p.GetSid())

	//给自己发送房间信息
	TienLenSendRoomInfo(s, p, sceneEx)
	sceneEx.BroadcastAudienceNum(p)

	s.FirePlayerEvent(p, base.PlayerEventEnter, nil)

}

func (this *ScenePolicyTienLen) OnAudienceDropLine(s *base.Scene, p *base.Player) {
	if s == nil || p == nil {
		return
	}
	logger.Logger.Trace("ScenePolicyTienLen OnAudienceDropLine, sceneId=", s.SceneId, " player=", p.SnId)
	s.AudienceLeave(p, common.PlayerLeaveReason_DropLine)
	if sceneEx, ok := s.ExtraData.(*TienLenSceneData); ok {
		sceneEx.BroadcastAudienceNum(p)
	}
}

// 是否完成了整个牌局
func (this *ScenePolicyTienLen) IsCompleted(s *base.Scene) bool {
	if s == nil {
		return false
	}
	return false
}

// 是否可以强制开始
func (this *ScenePolicyTienLen) IsCanForceStart(s *base.Scene) bool {
	return false
}

// 当前状态能否换桌
func (this *ScenePolicyTienLen) CanChangeCoinScene(s *base.Scene, p *base.Player) bool {
	if s == nil || p == nil {
		return false
	}
	if s.GetSceneState() != nil {
		return s.GetSceneState().CanChangeCoinScene(s, p)
	}
	return true
}

func (this *ScenePolicyTienLen) ForceStart(s *base.Scene) {
	s.ChangeSceneState(rule.TienLenSceneStateWaitStart)
}

func TienLenCreatePlayerData(p *base.Player, rankScore int64) *tienlen.TienLenPlayerData {
	playerEx, _ := p.GetExtraData().(*TienLenPlayerData)
	pd := &tienlen.TienLenPlayerData{
		Name:        proto.String(p.Name),
		SnId:        proto.Int32(p.SnId),
		Head:        proto.Int32(p.Head),
		Sex:         proto.Int32(p.Sex),
		Coin:        proto.Int64(p.GetCoin()),
		Flag:        proto.Int(p.GetFlag()),
		Longitude:   proto.Int32(p.Longitude),
		Latitude:    proto.Int32(p.Latitude),
		City:        proto.String(p.City),
		VIP:         proto.Int32(p.VIP),
		HeadOutLine: proto.Int32(p.HeadOutLine),
		NiceId:      proto.Int32(p.NiceId),
		Pos:         proto.Int(p.GetPos()),
		RankScore:   rankScore,
	}
	if playerEx != nil {
		pd.ThinkLongCnt = playerEx.ThinkLongCnt
	}
	if p.Roles != nil {
		pd.RoleId = proto.Int32(p.Roles.ModId)
	}
	if p.Items != nil {
		pd.Items = make(map[int32]int32)
		for id, num := range p.Items {
			pd.Items[id] = proto.Int32(num)
		}
	}
	if len(p.MatchParams) > 0 {
		pd.MatchRankId = p.MatchParams[0]
	}
	if len(p.MatchParams) > 1 {
		pd.Lv = p.MatchParams[1]
	}
	logger.Logger.Trace("TienLenCreatePlayerData pd : ", pd)

	return pd
}

func TienLenCreateRoomInfoPacket(s *base.Scene, p *base.Player, sceneEx *TienLenSceneData) *tienlen.SCTienLenRoomInfo {
	pack := &tienlen.SCTienLenRoomInfo{
		RoomId:       proto.Int(s.GetSceneId()),
		Creator:      proto.Int32(s.GetCreator()),
		GameId:       proto.Int(s.GetGameId()),
		RoomMode:     proto.Int(s.GetSceneMode()),
		Params:       s.GetParams(),
		State:        proto.Int32(int32(s.GetSceneState().GetState())),
		TimeOut:      proto.Int(s.GetSceneState().GetTimeout(s)),
		NumOfGames:   proto.Int(sceneEx.NumOfGames),
		TotalOfGames: proto.Int(sceneEx.TotalOfGames),
		CurOpIdx:     proto.Int(-1),
		MasterSnid:   proto.Int32(sceneEx.masterSnid),
		AudienceNum:  proto.Int(s.GetAudiencesNum()),
		BaseScore:    proto.Int32(s.GetBaseScore()),
		MaxPlayerNum: proto.Int(s.GetPlayerNum()),
		RankType:     s.GetDBGameFree().GetRankType(),
		SceneAdd:     s.GetDBGameFree().GetSceneAdd(),
		// 比赛场相关
		Round:        proto.Int32(s.MatchRound),
		CurPlayerNum: proto.Int32(s.MatchCurPlayerNum),
		NextNeed:     proto.Int32(s.MatchNextNeed),
		RecordId:     sceneEx.recordId,
	}
	pack.IsMatch = int32(0)
	// 0.普通场 1.锦标赛 2.冠军赛 3.vip专属
	if s.IsMatchScene() {
		pack.IsMatch = s.MatchType
	}
	pack.MatchFinals = 0
	if s.MatchFinals {
		pack.MatchFinals = 1
		if s.NumOfGames >= 2 {
			pack.MatchFinals = 2
		}
	}
	for _, snid := range sceneEx.winSnids {
		pack.WinSnids = append(pack.WinSnids, snid)
	}
	if s.GetSceneState().GetState() == rule.TienLenSceneStateBilled {
		sceneEx.currOpPos = -1
		pack.CurOpIdx = proto.Int32(-1)
	}
	if s.GetSceneState().GetState() >= rule.TienLenSceneStatePlayerOp {
		pack.CurOpIdx = proto.Int32(sceneEx.currOpPos)
	}
	playerEx, _ := p.GetExtraData().(*TienLenPlayerData)
	//玩家信息.第一个必然是自己
	pd := TienLenCreatePlayerData(p, p.GetRankScore(rule.RankType))
	if playerEx != nil {
		//手牌
		for i := int32(0); i < rule.HandCardNum; i++ {
			if playerEx.cards[i] != rule.InvalideCard {
				pd.Cards = append(pd.Cards, playerEx.cards[i])
			}
		}
		if sceneEx.IsMatchScene() && p.IsRob {
			if len(p.MatchParams) > 2 {
				if p.MatchParams[2] != 0 {
					pd.CopySnid = p.MatchParams[2]
				}
			}
			if len(p.MatchParams) > 3 {
				if p.MatchParams[3] != 0 {
					pd.CopyRoleId = p.MatchParams[3]
				}
			}
		}

	}
	pack.Players = append(pack.Players, pd)

	//剩下的按座位排序
	for i := 0; i < sceneEx.GetPlayerNum(); i++ {
		nowPlayer := sceneEx.seats[i]
		if nowPlayer != nil {
			if nowPlayer.SnId != p.SnId {
				pd1 := TienLenCreatePlayerData(nowPlayer.Player, nowPlayer.Player.GetRankScore(rule.RankType))
				if sceneEx.IsMatchScene() && nowPlayer.IsRob {
					if len(nowPlayer.MatchParams) > 2 {
						if nowPlayer.MatchParams[2] != 0 {
							pd1.CopySnid = nowPlayer.MatchParams[2]
						}
					}
					if len(nowPlayer.MatchParams) > 3 {
						if nowPlayer.MatchParams[3] != 0 {
							pd1.CopyRoleId = nowPlayer.MatchParams[3]
						}
					}
				}
				//手牌
				for j := int32(0); j < rule.HandCardNum; j++ {
					if nowPlayer.cards[j] != rule.InvalideCard {
						if s.GetSceneState().GetState() == rule.TienLenSceneStateBilled { //结算状态显示用
							pd1.Cards = append(pd1.Cards, nowPlayer.cards[j])
						} else {
							pd1.Cards = append(pd1.Cards, rule.InvalideCard)
						}
					}
				}
				pack.Players = append(pack.Players, pd1)
			}
		}
	}
	//上两手玩家出的牌（有序）
	insertNum := 0
	for i := len(sceneEx.delOrders) - 1; i >= 0 && insertNum < 2; i-- {
		lastDelCard := &tienlen.LastDelCard{}
		delCards := sceneEx.delCards[i]
		for j := 0; j < len(delCards); j++ {
			lastDelCard.Cards = append(lastDelCard.Cards, delCards[j])
		}
		pack.LastDelCards = append(pack.LastDelCards, lastDelCard)
		insertNum++
	}
	proto.SetDefaults(pack)
	return pack
}

func TienLenSendRoomInfo(s *base.Scene, p *base.Player, sceneEx *TienLenSceneData) {
	pack := TienLenCreateRoomInfoPacket(s, p, sceneEx)
	ok := p.SendToClient(int(tienlen.TienLenPacketID_PACKET_SCTienLenRoomInfo), pack)
	logger.Logger.Trace("SCTienLenSendRoomInfo isok : ", ok, ",pack", pack)
}

// /////////////////////////////////////////////////////////////////////////////////////////////////////////////
// BaseState
// /////////////////////////////////////////////////////////////////////////////////////////////////////////////
type SceneBaseStateTienLen struct {
}

func (this *SceneBaseStateTienLen) GetTimeout(s *base.Scene) int {
	if sceneEx, ok := s.GetExtraData().(*TienLenSceneData); ok {
		return int(time.Now().Sub(sceneEx.StateStartTime) / time.Second)
	}
	return 0
}

func (this *SceneBaseStateTienLen) CanChangeTo(s base.SceneState) bool {
	return true
}

// 当前状态能否换桌
func (this *SceneBaseStateTienLen) CanChangeCoinScene(s *base.Scene, p *base.Player) bool {
	return !p.IsGameing() || s.GetDestroyed()
}

func (this *SceneBaseStateTienLen) OnEnter(s *base.Scene) {
	if sceneEx, ok := s.GetExtraData().(*TienLenSceneData); ok {
		sceneEx.StateStartTime = time.Now()
	}
}

func (this *SceneBaseStateTienLen) OnLeave(s *base.Scene) {}

func (this *SceneBaseStateTienLen) OnTick(s *base.Scene) {
	//场景状态是所有房间公用，房间的私有属性不能放到场景状态上
	if time.Now().Unix() > s.GetTimerRandomRobot() {
		s.RandRobotCnt()
		s.SetTimerRandomRobot(s.GetRobotTime())
	}
}

// 发送玩家操作情况
func (this *SceneBaseStateTienLen) OnPlayerSToCOp(s *base.Scene, p *base.Player, pos int, opcode int, opRetCode tienlen.OpResultCode, params []int64) {
	pack := &tienlen.SCTienLenPlayerOp{
		OpCode:    proto.Int(opcode),
		SnId:      proto.Int32(p.SnId),
		OpRetCode: opRetCode,
		OpParam:   params,
	}
	proto.SetDefaults(pack)
	p.SendToClient(int(tienlen.TienLenPacketID_PACKET_SCTienLenPlayerOp), pack)
	//logger.Logger.Trace("发送玩家操作情况 ", pack)
}

// 广播发送玩家操作情况
func (this *SceneBaseStateTienLen) BroadcastPlayerSToCOp(s *base.Scene, snid int32, pos int, opcode int, opRetCode tienlen.OpResultCode, params []int64) {
	pack := &tienlen.SCTienLenPlayerOp{
		OpCode:    proto.Int(opcode),
		SnId:      proto.Int32(snid),
		OpRetCode: opRetCode,
		OpParam:   params,
	}
	proto.SetDefaults(pack)
	s.Broadcast(int(tienlen.TienLenPacketID_PACKET_SCTienLenPlayerOp), pack, 0)
	if opRetCode == tienlen.OpResultCode_OPRC_Sucess {
		cards := []int{}
		for _, param := range params {
			cards = append(cards, rule.ValueStr(int32(param)))
		}
		//logger.Logger.Trace("广播发送玩家操作情况 ", pack, "  snid:", snid, "  cards:", cards)
	}
}

func (this *SceneBaseStateTienLen) OnPlayerOp(s *base.Scene, p *base.Player, opcode int, params []int64) bool {
	logger.Logger.Trace("SceneBaseStateTienLen.", " s.GetSceneId() : ", s.GetSceneId(), " p.SnId : ", p.SnId, " opcode : ", opcode, " params ", params)

	sceneEx, _ := s.GetExtraData().(*TienLenSceneData)
	if sceneEx == nil {
		return true
	}
	playerEx, _ := p.GetExtraData().(*TienLenPlayerData)
	if playerEx == nil {
		return true
	}

	switch opcode {
	case int(rule.TienLenPlayerOpTest):
		if !common.Config.IsDevMode {
			return false
		}
		sceneEx.testPokers = make([]int32, len(params)+1)
		sceneEx.testPokers[0] = p.SnId
		copy(sceneEx.testPokers[1:], common.Int64ToInt32(params))
		return true
	case int(rule.TienLenPlayerOpCanelAuto): //取消托管
		playerEx.LeaveAutoState(s)
		this.OnPlayerSToCOp(s, p, playerEx.GetPos(), opcode, tienlen.OpResultCode_OPRC_Sucess, params)
		return true
	}

	return false
}

func (this *SceneBaseStateTienLen) OnPlayerEvent(s *base.Scene, p *base.Player, evtcode int, params []int64) {
	sceneEx, ok := s.GetExtraData().(*TienLenSceneData)
	if ok {
		// 没有防住，选房主
		if sceneEx.GetPlayer(sceneEx.masterSnid) == nil {
			findOne := false
			for i := 0; i < sceneEx.GetPlayerNum(); i++ {
				playerExSeat := sceneEx.seats[i]
				if playerExSeat != nil && playerExSeat.SnId != sceneEx.masterSnid {
					sceneEx.masterSnid = playerExSeat.SnId
					sceneEx.BroadcastUpdateMasterSnid()
					findOne = true
					break
				}
			}
			if !findOne {
				sceneEx.masterSnid = 0
				sceneEx.BroadcastUpdateMasterSnid()
			}
		}
	}
}

func (this *SceneBaseStateTienLen) BroadcastRoomState(s *base.Scene, state int, params ...int64) {
	pack := &tienlen.SCTienLenRoomState{
		State:  proto.Int(state),
		Params: params,
	}
	proto.SetDefaults(pack)
	s.Broadcast(int(tienlen.TienLenPacketID_PACKET_SCTienLenRoomState), pack, 0)
}

// /////////////////////////////////////////////////////////////////////////////////////////////////////////////
// TienLenSceneStateWaitPlayer
// /////////////////////////////////////////////////////////////////////////////////////////////////////////////
type SceneWaitPlayerStateTienLen struct {
	SceneBaseStateTienLen
}

func (this *SceneWaitPlayerStateTienLen) GetState() int {
	return rule.TienLenSceneStateWaitPlayer
}

func (this *SceneWaitPlayerStateTienLen) CanChangeTo(s base.SceneState) bool {
	if s.GetState() == rule.TienLenSceneStateWaitStart || s.GetState() == rule.TienLenSceneStateHandCard {
		return true
	}
	return false
}

// 当前状态能否换桌
func (this *SceneWaitPlayerStateTienLen) CanChangeCoinScene(s *base.Scene, p *base.Player) bool {
	if s.IsMatchScene() {
		return false
	}
	return true
}

func (this *SceneWaitPlayerStateTienLen) OnEnter(s *base.Scene) {
	this.SceneBaseStateTienLen.OnEnter(s)

	if sceneEx, ok := s.GetExtraData().(*TienLenSceneData); ok {
		sceneEx.Clear()
		sceneEx.SetGaming(false)
		this.BroadcastRoomState(s, this.GetState())
	}
}

// 状态离开时
func (this *SceneWaitPlayerStateTienLen) OnLeave(s *base.Scene) {
	this.SceneBaseStateTienLen.OnLeave(s)
	logger.Logger.Tracef("(this *SceneWaitPlayerStateTienLen) OnLeave, sceneid=%v", s.GetSceneId())
}

// 玩家操作
func (this *SceneWaitPlayerStateTienLen) OnPlayerOp(s *base.Scene, p *base.Player, opcode int, params []int64) bool {
	if this.SceneBaseStateTienLen.OnPlayerOp(s, p, opcode, params) {
		return true
	}
	return true
}

// 玩家事件
func (this *SceneWaitPlayerStateTienLen) OnPlayerEvent(s *base.Scene, p *base.Player, evtcode int, params []int64) {
	logger.Logger.Trace("(this *SceneWaitPlayerStateTienLen) OnPlayerEvent, GetSceneId()=", s.GetSceneId(), " player=", p.Name, " evtcode=", evtcode)
	this.SceneBaseStateTienLen.OnPlayerEvent(s, p, evtcode, params)

	if _, ok := p.GetExtraData().(*TienLenPlayerData); ok {
		if sceneEx, ok := s.GetExtraData().(*TienLenSceneData); ok {
			switch evtcode {
			case base.PlayerEventEnter:
				//如果有人进入, 检查在线人是否能够开启游戏,够的话，切换到延迟开启状态
				if sceneEx.CanStart() {
					logger.Logger.Tracef("(this *SceneWaitPlayerStateTienLen) OnPlayerEvent s.ChangeSceneState(TienLenSceneStateWaitStart) %v", s.GetSceneId())
					s.ChangeSceneState(rule.TienLenSceneStateWaitStart)
				}
			}
		}
	}
}

func (this *SceneWaitPlayerStateTienLen) OnTick(s *base.Scene) {
	this.SceneBaseStateTienLen.OnTick(s)
	if sceneEx, ok := s.GetExtraData().(*TienLenSceneData); ok {
		if s.CheckNeedDestroy() {
			sceneEx.SceneDestroy(true)
			return
		}
		if sceneEx.CanStart() {
			logger.Logger.Tracef("(this *SceneWaitPlayerStateTienLen) OnTick s.ChangeSceneState(TienLenSceneStateWaitStart) %v", s.GetSceneId())
			s.ChangeSceneState(rule.TienLenSceneStateWaitStart)
		} else if !s.GetGaming() && s.GetRobotNum() == 1 {
			tNow := time.Now()
			for _, p := range s.Players {
				if p.IsRob && tNow.Sub(p.GetLastOPTimer()) > time.Second*time.Duration(30+rand.Int63n(60)) {
					//p.LastOPTimer = tNow.Add(time.Minute * 30)
					s.PlayerLeave(p, common.PlayerLeaveReason_Normal, true)
				}
			}
		}
	}
}

// /////////////////////////////////////////////////////////////////////////////////////////////////////////////
// TienLenSceneStateWaitStart
// /////////////////////////////////////////////////////////////////////////////////////////////////////////////
type SceneWaitStartStateTienLen struct {
	SceneBaseStateTienLen
}

func (this *SceneWaitStartStateTienLen) GetState() int {
	return rule.TienLenSceneStateWaitStart
}

func (this *SceneWaitStartStateTienLen) CanChangeTo(s base.SceneState) bool {
	if s.GetState() == rule.TienLenSceneStateWaitPlayer || s.GetState() == rule.TienLenSceneStateWaitStart || s.GetState() == rule.TienLenSceneStateHandCard {
		return true
	}
	return false
}

// 当前状态能否换桌
func (this *SceneWaitStartStateTienLen) CanChangeCoinScene(s *base.Scene, p *base.Player) bool {
	if s.IsMatchScene() {
		return false
	}
	return true
}

func (this *SceneWaitStartStateTienLen) OnEnter(s *base.Scene) {
	this.SceneBaseStateTienLen.OnEnter(s)

	if sceneEx, ok := s.GetExtraData().(*TienLenSceneData); ok {
		sceneEx.Clear()
		sceneEx.SetGaming(false)
		this.BroadcastRoomState(s, this.GetState())
		logger.Logger.Trace("(this *SceneWaitStartStateTienLen) OnEnter", this.GetState())
	}
}

// 状态离开时
func (this *SceneWaitStartStateTienLen) OnLeave(s *base.Scene) {
	this.SceneBaseStateTienLen.OnLeave(s)
	logger.Logger.Tracef("(this *SceneWaitStartStateTienLen) OnLeave", this.GetState())
}

// 玩家操作
func (this *SceneWaitStartStateTienLen) OnPlayerOp(s *base.Scene, p *base.Player, opcode int, params []int64) bool {
	if this.SceneBaseStateTienLen.OnPlayerOp(s, p, opcode, params) {
		return true
	}
	sceneEx, _ := s.GetExtraData().(*TienLenSceneData)
	if sceneEx != nil {
		playerEx, _ := p.GetExtraData().(*TienLenPlayerData)
		if playerEx != nil {
			opRetCode := tienlen.OpResultCode_OPRC_Error
			if playerEx.SnId == sceneEx.masterSnid && this.GetState() == rule.TienLenSceneStateWaitStart {
				if sceneEx.IsMatchScene() {
					return false
				}
				switch int32(opcode) {
				case rule.TienLenPlayerOpStart: //房主开始游戏
					if sceneEx.CanStart() == false {
						s.ChangeSceneState(rule.TienLenSceneStateWaitPlayer)
					} else {
						opRetCode = tienlen.OpResultCode_OPRC_Sucess
						s.ChangeSceneState(rule.TienLenSceneStateHandCard)
					}
				}
			}
			if opRetCode == tienlen.OpResultCode_OPRC_Sucess {
				this.OnPlayerSToCOp(s, p, playerEx.GetPos(), opcode, opRetCode, params)
			} else {
				logger.Logger.Tracef("SceneWaitStartStateTienLen OnPlayerOp snid:%v, masterSnid:%v", playerEx.SnId, sceneEx.masterSnid)
			}
		}
	}
	return true
}

// 玩家事件
func (this *SceneWaitStartStateTienLen) OnPlayerEvent(s *base.Scene, p *base.Player, evtcode int, params []int64) {
	logger.Logger.Trace("(this *SceneWaitStartStateTienLen) OnPlayerEvent, GetSceneId()=", s.GetSceneId(), " player=", p.Name, " evtcode=", evtcode)
	this.SceneBaseStateTienLen.OnPlayerEvent(s, p, evtcode, params)

	if sceneEx, ok := s.GetExtraData().(*TienLenSceneData); ok {
		switch evtcode {
		case base.PlayerEventLeave:
			//如果有人退出, 检查在线人是否能够开启游戏,不够的话，切换到等待状态
			if sceneEx.CanStart() == false {
				s.ChangeSceneState(rule.TienLenSceneStateWaitPlayer)
			}
		}
	}
}

func (this *SceneWaitStartStateTienLen) OnTick(s *base.Scene) {
	this.SceneBaseStateTienLen.OnTick(s)
	if sceneEx, ok := s.GetExtraData().(*TienLenSceneData); ok {
		if sceneEx.IsMatchScene() {
			delayT := time.Second * 2
			if sceneEx.MatchRound != 1 { //第一轮延迟2s，其他延迟3s 配合客户端播放动画
				delayT = time.Second * 4
			}
			if time.Now().Sub(sceneEx.StateStartTime) > delayT {
				s.ChangeSceneState(rule.TienLenSceneStateHandCard) // 比赛场直接发牌
				return
			}
		}
		if sceneEx.SceneMode == common.SceneMode_Public {
			if time.Now().Sub(sceneEx.StateStartTime) > rule.TienLenWaitStartTimeout {
				if sceneEx.Creator != 0 && sceneEx.GetRealPlayerNum() == 0 {
					sceneEx.Destroy(true)
					return
				}
				//开始前再次检查开始条件
				if sceneEx.CanStart() == true {
					s.ChangeSceneState(rule.TienLenSceneStateHandCard)
				} else {
					s.ChangeSceneState(rule.TienLenSceneStateWaitPlayer)
				}
			}
		}
	}
}

// /////////////////////////////////////////////////////////////////////////////////////////////////////////////
// TienLenSceneStateHandCard 发牌
// /////////////////////////////////////////////////////////////////////////////////////////////////////////////
type SceneHandCardStateTienLen struct {
	SceneBaseStateTienLen
}

func (this *SceneHandCardStateTienLen) GetState() int {
	return rule.TienLenSceneStateHandCard
}

func (this *SceneHandCardStateTienLen) CanChangeTo(s base.SceneState) bool {
	if s.GetState() == rule.TienLenSceneStatePlayerOp || s.GetState() == rule.TienLenSceneStateBilled {
		return true
	}
	return false
}

func (this *SceneHandCardStateTienLen) OnEnter(s *base.Scene) {
	this.SceneBaseStateTienLen.OnEnter(s)

	if sceneEx, ok := s.GetExtraData().(*TienLenSceneData); ok {
		sceneEx.Clear()
		sceneEx.SetGaming(true)
		sceneEx.GameNowTime = time.Now()
		sceneEx.AllPlayerEnterGame()

		//参与游戏次数
		for i := 0; i < sceneEx.GetPlayerNum(); i++ {
			playerEx := sceneEx.seats[i]
			if playerEx != nil {
				if playerEx.IsGameing() {
					playerEx.GameTimes++
					sceneEx.curGamingPlayerNum++
					if playerEx.IsRob {
						playerEx.robotGameTimes--
						sceneEx.robotGamingNum++
					}
				}
			}
		}

		s.NumOfGames++
		s.NotifySceneRoundStart(s.NumOfGames)
		this.BroadcastRoomState(s, this.GetState(), int64(s.NumOfGames))

		//同步防伙牌数据
		sceneEx.SyncScenePlayer()
		//发牌
		if rule.TestOpen {
			sceneEx.SendHandCardTest()
		} else {
			if sceneEx.IsMatchScene() {
				sceneEx.SendHandCard_Match()
			} else {
				sceneEx.SendHandCardOdds()
			}
		}

		for _, seat := range sceneEx.seats {
			if seat != nil {
				tmpCards := seat.cards[:]
				sceneEx.allPlayerCards = append(sceneEx.allPlayerCards, tmpCards)
				logger.Logger.Info("--------------------------------------allplayerCards = ", sceneEx.allPlayerCards)
				seat.tianHu = 0
				switch {
				case rule.Have2FourBomb(tmpCards):
					seat.tianHu = rule.TianHu2Four
				case rule.Have6StraightTwin(tmpCards):
					seat.tianHu = rule.TianHu6StraightTwin
				case rule.Have12Straight(tmpCards):
					seat.tianHu = rule.TianHu12Straight
				}
				if seat.tianHu > 0 {
					keyNovice := common.GetKeyNoviceGameId(sceneEx.GameId)
					data, ok := seat.GDatas[keyNovice]
					if !ok {
						data = &model.PlayerGameInfo{FirstTime: time.Now()}
						seat.GDatas[keyNovice] = data
					}
					data.Statics.Incr(rule.StaticsTianHuTimes)
					sceneEx.tianHuSnids = append(sceneEx.tianHuSnids, seat.SnId) // 天胡玩家
					if !common.InSliceInt32(sceneEx.winSnids, seat.SnId) {
						sceneEx.winSnids = append(sceneEx.winSnids, seat.SnId) // 赢家snid
					}
				}
				// 手牌分数
				cardsTypeMap := rule.GetCardsType(tmpCards, sceneEx.IsTienLenYule())
				score, _ := rule.GetCardTypeScore(cardsTypeMap, sceneEx.IsTienLenYule(), tmpCards)
				seat.cardScore = score
			}
		}
		if len(sceneEx.tianHuSnids) == 0 { //没有天胡玩家
			//有赢家，赢家先出；无赢家手持最小牌先
			pos := int32(sceneEx.FindWinPos())
			if pos == -1 {
				pos = sceneEx.startOpPos
			}
			pack := &tienlen.SCTienLenFirstOpPos{
				Pos: proto.Int32(pos),
			}
			proto.SetDefaults(pack)
			sceneEx.Broadcast(int(tienlen.TienLenPacketID_PACKET_SCTienLenFirstOpPos), pack, 0)
			logger.Logger.Trace("SCTienLenFirstOpPos: ", pack)
		}
	}
}

// 状态离开时
func (this *SceneHandCardStateTienLen) OnLeave(s *base.Scene) {
	this.SceneBaseStateTienLen.OnLeave(s)

	logger.Logger.Tracef("(this *SceneHandCardStateTienLen) OnLeave, sceneid=%v", s.GetSceneId())
}

// 玩家操作
func (this *SceneHandCardStateTienLen) OnPlayerOp(s *base.Scene, p *base.Player, opcode int, params []int64) bool {
	if this.SceneBaseStateTienLen.OnPlayerOp(s, p, opcode, params) {
		return true
	}

	return true
}

// 玩家事件
func (this *SceneHandCardStateTienLen) OnPlayerEvent(s *base.Scene, p *base.Player, evtcode int, params []int64) {
	logger.Logger.Trace("(this *SceneHandCardStateTienLen) OnPlayerEvent, GetSceneId()=", s.GetSceneId(), " player=", p.Name, " evtcode=", evtcode)
	this.SceneBaseStateTienLen.OnPlayerEvent(s, p, evtcode, params)
}

func (this *SceneHandCardStateTienLen) OnTick(s *base.Scene) {
	this.SceneBaseStateTienLen.OnTick(s)
	if sceneEx, ok := s.GetExtraData().(*TienLenSceneData); ok {
		newTime := rule.TienLenHandCardTimeout
		if sceneEx.isAllRob {
			newTime = time.Second * 1
		}
		if time.Now().Sub(sceneEx.StateStartTime) > newTime {
			if len(sceneEx.tianHuSnids) != 0 {
				//天胡牌型直接结算
				logger.Logger.Trace("tianHu to Billed: ", sceneEx.tianHuSnids)
				s.ChangeSceneState(rule.TienLenSceneStateBilled)
			} else {
				//正常出牌:有赢家，赢家先出；无赢家手持最小牌先出（最小牌必出）
				winPos := sceneEx.FindWinPos()
				if winPos != -1 { //有赢家
					sceneEx.SetCurOpPos(int32(winPos))
				} else {
					sceneEx.SetCurOpPos(sceneEx.startOpPos)
				}
				s.ChangeSceneState(rule.TienLenSceneStatePlayerOp)
			}
		}
	}
}

// /////////////////////////////////////////////////////////////////////////////////////////////////////////////
// TienLenSceneStatePlayerOp 出牌（玩家操作阶段）
// /////////////////////////////////////////////////////////////////////////////////////////////////////////////
type ScenePlayerOpStateTienLen struct {
	SceneBaseStateTienLen
}

func (this *ScenePlayerOpStateTienLen) GetState() int {
	return rule.TienLenSceneStatePlayerOp
}

func (this *ScenePlayerOpStateTienLen) CanChangeTo(s base.SceneState) bool {
	if s.GetState() == rule.TienLenSceneStateBilled {
		return true
	}
	return false
}

func (this *ScenePlayerOpStateTienLen) OnEnter(s *base.Scene) {
	this.SceneBaseStateTienLen.OnEnter(s)
	this.BroadcastRoomState(s, this.GetState())

	sceneEx, _ := s.GetExtraData().(*TienLenSceneData)
	if sceneEx != nil {
		sceneEx.BroadcastOpPos()
	}
}

// 状态离开时
func (this *ScenePlayerOpStateTienLen) OnLeave(s *base.Scene) {
	this.SceneBaseStateTienLen.OnLeave(s)

	logger.Logger.Tracef("(this *SceneHandCardStateTienLen) OnLeave, sceneid=%v", s.GetSceneId())
}

// 玩家操作
func (this *ScenePlayerOpStateTienLen) OnPlayerOp(s *base.Scene, p *base.Player, opcode int, params []int64) bool {
	if this.SceneBaseStateTienLen.OnPlayerOp(s, p, opcode, params) {
		return true
	}
	sceneEx, _ := s.GetExtraData().(*TienLenSceneData)
	if sceneEx != nil {
		playerEx, _ := p.GetExtraData().(*TienLenPlayerData)
		if playerEx != nil {
			if sceneEx.GetCurOpPos() != int32(playerEx.GetPos()) {
				return false
			}

			opRetCode := tienlen.OpResultCode_OPRC_Error
			switch int32(opcode) {
			case rule.TienLenPlayerOpPlay: //出牌

				delCards := []int32{}
				for _, card := range params {
					isHave := false
					for _, hcard := range playerEx.cards { //去手牌里找找看有没有
						if int32(card) == hcard && hcard != rule.InvalideCard {
							isHave = true
						}
					}
					if isHave {
						delCards = append(delCards, int32(card))
					} else {
						opRetCode = tienlen.OpResultCode_OPRC_Error
						break
					}
				}
				if len(delCards) == len(params) && len(delCards) > 0 {
					isRule, _ := rule.RulePopEnable(delCards)
					ruleType := rule.Tienlen_Pass
					if sceneEx.IsTienLenYule() {
						isRule, ruleType = rule.RulePopEnable_yl(delCards)
					}
					//logger.Logger.Trace("ScenePlayerOpStateTienLen,2params:", params, " isRule:", isRule)
					if isRule { //符合出牌规则
						if int32(playerEx.GetPos()) == sceneEx.lastOpPos || sceneEx.lastOpPos == rule.InvalidePos { //当前操作者和上一个操作者是同一个人，必出牌
							if ruleType == rule.Plane_Single { //飞机带单只能最后一手出
								haveCardNum := 0
								for _, hcard := range playerEx.cards {
									if hcard != rule.InvalideCard {
										haveCardNum++
									}
								}
								if len(delCards) != haveCardNum {
									isRule = false
									opRetCode = tienlen.OpResultCode_OPRC_Error
									logger.Logger.Trace("Plane_Single, delCards:", delCards, " haveCardNum:", haveCardNum)
								}
							}
							if isRule {
								if sceneEx.lastOpPos == rule.InvalidePos { //首出玩家
									//有赢家，赢家先出，出牌不受限制
									//无赢家，手持最小牌先出，最小牌必先出
									winPos := sceneEx.FindWinPos()
									//logger.Logger.Trace("ScenePlayerOpStateTienLen,8params:", params, " winPos:", winPos)
									if winPos == -1 { //无赢家
										haveMinCard := false
										for _, card := range delCards {
											if card == sceneEx.curMinCard { //最小牌必先出
												haveMinCard = true
											}
										}
										//logger.Logger.Trace("ScenePlayerOpStateTienLen,9params:", params, " curMinCard:", sceneEx.curMinCard, " haveMinCard", haveMinCard)
										if haveMinCard {
											isDel := sceneEx.DelCards(playerEx, delCards)
											//logger.Logger.Trace("ScenePlayerOpStateTienLen,3params:", params, " isDel:", isDel)
											if isDel {

												playerEx.RefreshThinkLongCnt(time.Now().Sub(sceneEx.StateStartTime), false)

												sceneEx.DoNext(int32(playerEx.GetPos()))
												opRetCode = tienlen.OpResultCode_OPRC_Sucess
												sceneEx.SetLastOpPos(int32(playerEx.GetPos()))
												sceneEx.RefreshPlayerHandLimitTimeOut()
											}
										}
									} else {
										isDel := sceneEx.DelCards(playerEx, delCards)
										//logger.Logger.Trace("ScenePlayerOpStateTienLen,10params:", params, " isDel:", isDel)
										if isDel {

											playerEx.RefreshThinkLongCnt(time.Now().Sub(sceneEx.StateStartTime), false)

											sceneEx.DoNext(int32(playerEx.GetPos()))
											opRetCode = tienlen.OpResultCode_OPRC_Sucess
											sceneEx.SetLastOpPos(int32(playerEx.GetPos()))
											sceneEx.RefreshPlayerHandLimitTimeOut()
										}
									}
								} else {
									isDel := sceneEx.DelCards(playerEx, delCards)
									//logger.Logger.Trace("ScenePlayerOpStateTienLen,4params:", params, " isDel:", isDel)
									if isDel {

										playerEx.RefreshThinkLongCnt(time.Now().Sub(sceneEx.StateStartTime), false)

										nextPos := sceneEx.DoNext(int32(playerEx.GetPos()))
										//logger.Logger.Trace("ScenePlayerOpStateTienLen,4paramssss:", params, " nextPos:", nextPos)
										if sceneEx.IsTienLenToEnd() && nextPos == rule.InvalidePos {
											sceneEx.UnmarkPass()
											nextPos = sceneEx.DoNext(int32(playerEx.GetPos()))
											//logger.Logger.Trace("ScenePlayerOpStateTienLen,4paramssss:", params, " nextPos:", nextPos)
										}
										opRetCode = tienlen.OpResultCode_OPRC_Sucess
										sceneEx.SetLastOpPos(int32(playerEx.GetPos()))
										sceneEx.RefreshPlayerHandLimitTimeOut()
									}
								}
								if opRetCode == tienlen.OpResultCode_OPRC_Sucess {
									isBomb := rule.IsFourBomb(delCards)
									if isBomb {
										sceneEx.isKongBomb = true
									}
								}
								sceneEx.UnmarkPass()
							}
						} else { //当前操作者和上一个操作者不是同一个人，必压制
							if !playerEx.isPass {
								lastOpPlayer := sceneEx.GetLastOpPlayer()
								//logger.Logger.Trace("ScenePlayerOpStateTienLen,5params:", params, " lastOpPlayer:", lastOpPlayer)
								if lastOpPlayer != nil && len(lastOpPlayer.delCards) != 0 {
									lastDelCards := lastOpPlayer.delCards[len(lastOpPlayer.delCards)-1]
									canDel, isBomb, bombScore := rule.CanDel(lastDelCards, delCards, sceneEx.IsTienLenToEnd())
									if sceneEx.IsTienLenYule() {
										canDel, isBomb, bombScore = rule.CanDel_yl(lastDelCards, delCards, sceneEx.IsTienLenToEnd())
									}
									//logger.Logger.Trace("ScenePlayerOpStateTienLen,6params:", params, " canDel:", canDel, " lastDelCards:", lastDelCards)
									if canDel {
										if isBomb {
											sceneEx.curBombPos = int32(playerEx.GetPos())
											sceneEx.lastBombPos = sceneEx.lastOpPos
											sceneEx.roundScore += bombScore
										} else {
											sceneEx.curBombPos = rule.InvalidePos
											sceneEx.lastBombPos = rule.InvalidePos
											sceneEx.roundScore = 0
										}
										isDel := sceneEx.DelCards(playerEx, delCards)
										//logger.Logger.Trace("ScenePlayerOpStateTienLen,7params:", params, " isDel:", isDel)
										if isDel {

											playerEx.RefreshThinkLongCnt(time.Now().Sub(sceneEx.StateStartTime), false)

											nextPos := sceneEx.DoNext(int32(playerEx.GetPos()))
											if sceneEx.IsTienLenToEnd() && nextPos == rule.InvalidePos {
												sceneEx.UnmarkPass()
												sceneEx.DoNext(int32(playerEx.GetPos()))
											}
											sceneEx.SetLastOpPos(int32(playerEx.GetPos()))
											sceneEx.RefreshPlayerHandLimitTimeOut()
											opRetCode = tienlen.OpResultCode_OPRC_Sucess
											sceneEx.isKongBomb = false
										}
									}
								}
							}
						}
					}
				}
			case rule.TienLenPlayerOpPass: //过牌
				if int32(playerEx.GetPos()) == sceneEx.lastOpPos { //当前操作者和上一个操作者是同一个人，必出牌，不能过牌
					opRetCode = tienlen.OpResultCode_OPRC_Error
				} else {
					if sceneEx.lastOpPos != rule.InvalidePos {

						playerEx.RefreshThinkLongCnt(time.Now().Sub(sceneEx.StateStartTime), false)

						sceneEx.card_play_action_seq = append(sceneEx.card_play_action_seq, fmt.Sprintf("%v-过", playerEx.GetPos()))
						sceneEx.card_play_action_seq_int32 = append(sceneEx.card_play_action_seq_int32, []int32{-1})
						nextPos := sceneEx.DoNext(int32(playerEx.GetPos()))

						if sceneEx.IsTienLenToEnd() && nextPos == rule.InvalidePos {
							sceneEx.UnmarkPass()
							nextPos = sceneEx.DoNext(int32(playerEx.GetPos()))
							sceneEx.SetLastOpPos(int32(nextPos))
						}

						sceneEx.RefreshPlayerHandLimitTimeOut()
						opRetCode = tienlen.OpResultCode_OPRC_Sucess
						playerEx.isPass = true
						if nextPos == sceneEx.lastOpPos { //一轮都不出牌
							sceneEx.TrySmallGameBilled()
						}
					}
				}
			case rule.TienLenPlayerClientHintCards:
				if int32(playerEx.GetPos()) == sceneEx.lastOpPos || sceneEx.lastOpPos == rule.InvalidePos {
					logger.Logger.Trace("ScenePlayerOpStateTienLen,107 OPRC_Error:", params, " opcode:", opcode, " snid:", p.SnId, " pos:", playerEx.GetPos())
				} else {
					if len(params) > 0 {
						sceneEx.cHintCards = make([]int32, len(params)+1)
						sceneEx.cHintCards[0] = p.SnId
						copy(sceneEx.cHintCards[1:], common.Int64ToInt32(params))
						opRetCode = tienlen.OpResultCode_OPRC_Hint
					}
				}
			default:
				opRetCode = tienlen.OpResultCode_OPRC_Error
			}

			//next
			if opRetCode == tienlen.OpResultCode_OPRC_Sucess {
				this.BroadcastPlayerSToCOp(s, playerEx.SnId, playerEx.GetPos(), opcode, opRetCode, params)

				delCardNum := 0
				for _, hcard := range playerEx.cards {
					if hcard == rule.InvalideCard {
						delCardNum++
					}
				}
				if delCardNum == rule.Hand_CardNum { //牌出完了
					sceneEx.TrySmallGameBilled()
					if !common.InSliceInt32(sceneEx.winSnids, playerEx.SnId) {
						sceneEx.winSnids = append(sceneEx.winSnids, playerEx.SnId)
					}
					if sceneEx.IsTienLenToEnd() { //打到底
						if sceneEx.GetGameingPlayerCnt()-1 == len(sceneEx.winSnids) {
							sceneEx.ChangeSceneState(rule.TienLenSceneStateBilled)
						} else {
							playerEx.isDelAll = true
							sceneEx.BroadcastOpPos()
						}
					} else {
						sceneEx.ChangeSceneState(rule.TienLenSceneStateBilled)
					}
				} else {
					sceneEx.BroadcastOpPos()
				}
			} else if opRetCode == tienlen.OpResultCode_OPRC_Error {
				this.OnPlayerSToCOp(s, p, playerEx.GetPos(), opcode, opRetCode, params)

				logger.Logger.Trace("ScenePlayerOpStateTienLen,100 OPRC_Error:", params, " opcode:", opcode, " snid:", p.SnId, " pos:", playerEx.GetPos())
			}
		}
	}

	return true
}

// 玩家事件
func (this *ScenePlayerOpStateTienLen) OnPlayerEvent(s *base.Scene, p *base.Player, evtcode int, params []int64) {
	logger.Logger.Trace("(this *SceneHandCardStateTienLen) OnPlayerEvent, GetSceneId()=", s.GetSceneId(), " player=", p.Name, " evtcode=", evtcode)
	this.SceneBaseStateTienLen.OnPlayerEvent(s, p, evtcode, params)
}

func (this *ScenePlayerOpStateTienLen) OnTick(s *base.Scene) {
	this.SceneBaseStateTienLen.OnTick(s)

	sceneEx, ok := s.GetExtraData().(*TienLenSceneData)
	if !ok {
		return
	}

	curOpPos := sceneEx.GetCurOpPos()

	if curOpPos == rule.InvalidePos {
		return
	}

	playerEx := sceneEx.seats[curOpPos]
	if playerEx == nil {
		return
	}

	if time.Now().Sub(sceneEx.StateStartTime) < playerEx.GetCurHandLimitTimeOut() {
		return
	}

	if !playerEx.IsRob {
		logger.Logger.Trace("(this *ScenePlayerOpStateTienLen) OnTick snid: ",
			playerEx.SnId, " HandLTime:", playerEx.GetCurHandLimitTimeOut(), " thinkCnt:", playerEx.ThinkLongCnt,
			" flag:", playerEx.GetFlag())
	}

	playerEx.EnterAutoState()

	//超时后单轮首出玩家出最小的牌，非单轮首出玩家不出牌
	if sceneEx.lastOpPos == curOpPos || sceneEx.lastOpPos == rule.InvalidePos { //单轮首出玩家
		cpCards := playerEx.cards[:]
		rule.MinSortCards(cpCards)

		var delCards []int32
		for _, card := range cpCards {
			if card != rule.InvalideCard {
				delCards = append(delCards, card)
				break
			}
		}

		isDel := sceneEx.DelCards(playerEx, delCards)
		if isDel {
			sceneEx.DoNext(int32(playerEx.GetPos()))
			sceneEx.UnmarkPass()
			this.BroadcastPlayerSToCOp(s, playerEx.SnId, playerEx.GetPos(), int(rule.TienLenPlayerOpPlay),
				tienlen.OpResultCode_OPRC_Sucess, common.CopySliceInt32ToInt64(delCards))
			sceneEx.SetLastOpPos(int32(playerEx.GetPos()))
			sceneEx.RefreshPlayerHandLimitTimeOut()
			sceneEx.BroadcastOpPos()
		} else {
			sceneEx.DoNext(int32(playerEx.GetPos()))
			playerEx.isPass = true
			this.BroadcastPlayerSToCOp(s, playerEx.SnId, playerEx.GetPos(), int(rule.TienLenPlayerOpPass), tienlen.OpResultCode_OPRC_Sucess, []int64{})
			sceneEx.RefreshPlayerHandLimitTimeOut()
			sceneEx.BroadcastOpPos()
		}
		sceneEx.isKongBomb = false
		delAll := 0
		for _, card := range playerEx.cards {
			if card == rule.InvalideCard {
				delAll++
			}
		}
		if delAll == rule.Hand_CardNum { // 出完
			sceneEx.TrySmallGameBilled()
			if !common.InSliceInt32(sceneEx.winSnids, playerEx.SnId) {
				sceneEx.winSnids = append(sceneEx.winSnids, playerEx.SnId)
			}
			if sceneEx.IsTienLenToEnd() { //打到底
				if sceneEx.GetGameingPlayerCnt()-1 == len(sceneEx.winSnids) {
					sceneEx.ChangeSceneState(rule.TienLenSceneStateBilled)
				} else {
					playerEx.isDelAll = true
					sceneEx.BroadcastOpPos()
				}
			} else {
				sceneEx.ChangeSceneState(rule.TienLenSceneStateBilled)
			}
		}
	} else {

		bHandCard := false
		if len(sceneEx.cHintCards) >= 2 && playerEx.SnId == sceneEx.cHintCards[0] {
			lastOpPlayer := sceneEx.GetLastOpPlayer()
			if lastOpPlayer != nil && len(lastOpPlayer.delCards) != 0 {
				lastDelCards := lastOpPlayer.delCards[len(lastOpPlayer.delCards)-1]

				hintCards := sceneEx.cHintCards[1:]
				recmCards := make([]int32, len(hintCards))
				copy(recmCards, hintCards)

				bExistHandCards := true
				for _, card := range recmCards {
					isHave := false
					for _, hcard := range playerEx.cards { //去手牌里找找看有没有
						if int32(card) == hcard && hcard != rule.InvalideCard {
							isHave = true
							break
						}
					}

					if !isHave {
						bExistHandCards = false
						break
					}
				}

				isRule, _ := rule.RulePopEnable(recmCards)
				if sceneEx.IsTienLenYule() {
					isRule, _ = rule.RulePopEnable_yl(recmCards)
				}

				if bExistHandCards && isRule {
					// 检查能否压得住
					canDel, isBomb, bombScore := rule.CanDel(lastDelCards, recmCards, sceneEx.IsTienLenToEnd())
					if sceneEx.IsTienLenYule() {
						canDel, isBomb, bombScore = rule.CanDel_yl(lastDelCards, recmCards, sceneEx.IsTienLenToEnd())
					}

					if canDel {
						if isBomb {
							sceneEx.curBombPos = int32(playerEx.GetPos())
							sceneEx.lastBombPos = sceneEx.lastOpPos
							sceneEx.roundScore += bombScore
						} else {
							sceneEx.curBombPos = rule.InvalidePos
							sceneEx.lastBombPos = rule.InvalidePos
							sceneEx.roundScore = 0
						}

						isDel := sceneEx.DelCards(playerEx, recmCards)
						if isDel {
							nextPos := sceneEx.DoNext(int32(playerEx.GetPos()))
							if sceneEx.IsTienLenToEnd() && nextPos == rule.InvalidePos {
								sceneEx.UnmarkPass()
								sceneEx.DoNext(int32(playerEx.GetPos()))
							}

							sceneEx.SetLastOpPos(int32(playerEx.GetPos()))
							sceneEx.isKongBomb = false
							sceneEx.RefreshPlayerHandLimitTimeOut()
							retParams := []int64{}
							for _, card := range recmCards {
								retParams = append(retParams, int64(card))
							}

							this.BroadcastPlayerSToCOp(s, playerEx.SnId, playerEx.GetPos(), int(rule.TienLenPlayerOpAutoHandCard), tienlen.OpResultCode_OPRC_Sucess, retParams)

							delCardNum := 0
							for _, hcard := range playerEx.cards {
								if hcard == rule.InvalideCard {
									delCardNum++
								}
							}

							if delCardNum == rule.Hand_CardNum { //牌出完了
								sceneEx.TrySmallGameBilled()
								if !common.InSliceInt32(sceneEx.winSnids, playerEx.SnId) {
									sceneEx.winSnids = append(sceneEx.winSnids, playerEx.SnId)
								}
								if sceneEx.IsTienLenToEnd() { //打到底
									if sceneEx.GetGameingPlayerCnt()-1 == len(sceneEx.winSnids) {
										sceneEx.ChangeSceneState(rule.TienLenSceneStateBilled)
									} else {
										playerEx.isDelAll = true
										sceneEx.BroadcastOpPos()
									}
								} else {
									sceneEx.ChangeSceneState(rule.TienLenSceneStateBilled)
								}
							} else {
								sceneEx.BroadcastOpPos()
							}

							bHandCard = true
						}
					}
				}
			}

			// 用完后置空
			sceneEx.cHintCards = []int32{}
		}

		if !bHandCard {
			sceneEx.card_play_action_seq = append(sceneEx.card_play_action_seq, fmt.Sprintf("%v-过", playerEx.GetPos()))
			sceneEx.card_play_action_seq_int32 = append(sceneEx.card_play_action_seq_int32, []int32{-1})
			nextPos := sceneEx.DoNext(int32(playerEx.GetPos()))
			if sceneEx.IsTienLenToEnd() && nextPos == rule.InvalidePos {
				sceneEx.UnmarkPass()
				nextPos = sceneEx.DoNext(int32(playerEx.GetPos()))
				sceneEx.SetLastOpPos(int32(nextPos))
			}
			sceneEx.RefreshPlayerHandLimitTimeOut()
			playerEx.isPass = true
			if nextPos == sceneEx.lastOpPos { //一轮都不出牌
				sceneEx.TrySmallGameBilled()
			}

			this.BroadcastPlayerSToCOp(s, playerEx.SnId, playerEx.GetPos(), int(rule.TienLenPlayerOpPass), tienlen.OpResultCode_OPRC_Sucess, []int64{})
			sceneEx.BroadcastOpPos()
		}
	}

	sceneEx.StateStartTime = time.Now()
}

// /////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Billed
// /////////////////////////////////////////////////////////////////////////////////////////////////////////////
type SceneBilledStateTienLen struct {
	SceneBaseStateTienLen
}

func (this *SceneBilledStateTienLen) GetState() int {
	return rule.TienLenSceneStateBilled
}

func (this *SceneBilledStateTienLen) CanChangeTo(s base.SceneState) bool {
	if s.GetState() == rule.TienLenSceneStateWaitPlayer || s.GetState() == rule.TienLenSceneStateWaitStart {
		return true
	}
	return false
}

func (this *SceneBilledStateTienLen) OnEnter(s *base.Scene) {
	this.SceneBaseStateTienLen.OnEnter(s)
	this.BroadcastRoomState(s, this.GetState())

	//在这里执行结算
	if sceneEx, ok := s.GetExtraData().(*TienLenSceneData); ok {
		winScore := int64(0)
		winRankScore := int64(0)
		pack := &tienlen.SCTienLenGameBilled{}
		tienlenType := model.TienLenType{
			GameId:      sceneEx.GameId,
			RoomId:      int32(sceneEx.GetSceneId()),
			RoomType:    sceneEx.GetFreeGameSceneType(),
			NumOfGames:  int32(sceneEx.Scene.NumOfGames),
			BankId:      sceneEx.masterSnid,
			PlayerCount: sceneEx.curGamingPlayerNum,
			BaseScore:   s.GetBaseScore(),
			TaxRate:     s.DbGameFree.GetTaxRate(),
			RoomMode:    s.GetSceneMode(),
		}

		nGamingPlayerCount := sceneEx.GetGameingPlayerCnt()
		if len(sceneEx.tianHuSnids) == nGamingPlayerCount { //和
			for i := 0; i < sceneEx.GetPlayerNum(); i++ {
				playerEx := sceneEx.seats[i]
				if playerEx == nil {
					continue
				}
				if !playerEx.IsGameing() {
					continue
				}
				billData := &tienlen.TienLenPlayerGameBilled{
					SnId:      proto.Int32(playerEx.SnId),
					IsWin:     proto.Int32(0),
					WinCoin:   proto.Int64(0),
					GameCoin:  proto.Int64(playerEx.GetCoin()),
					RankScore: playerEx.GetRankScore(sceneEx.GetDBGameFree().GetRankType()),
					TianHu:    playerEx.tianHu,
				}
				playerEx.CurIsWin = int64(0)
				tienlenPerson := model.TienLenPerson{
					UserId:      playerEx.SnId,
					UserIcon:    playerEx.Head,
					Platform:    playerEx.Platform,
					Channel:     playerEx.Channel,
					Promoter:    playerEx.BeUnderAgentCode,
					PackageTag:  playerEx.PackageID,
					InviterId:   playerEx.InviterId,
					WBLevel:     playerEx.WBLevel,
					IsRob:       playerEx.IsRob,
					IsFirst:     sceneEx.IsPlayerFirst(sceneEx.GetPlayer(playerEx.SnId)),
					IsLeave:     false,
					IsWin:       0,
					GainCoin:    0,
					BombCoin:    0,
					BillCoin:    0,
					GainTaxCoin: 0,
					BombTaxCoin: 0,
					BillTaxCoin: 0,
					Seat:        playerEx.GetPos(),
					IsTianHu:    sceneEx.IsTianhuPlayer(playerEx.SnId),
					TestLog:     playerEx.TestLog,
					CardsScore:  playerEx.cardScore,
				}
				//排下序，正常应该客户端排序
				cards := make([]int32, rule.HandCardNum)
				copy(cards, playerEx.cards[:])
				sort.Slice(cards, func(i, j int) bool {
					v_i := rule.Value(int32(cards[i]))
					v_j := rule.Value(int32(cards[j]))
					c_i := rule.Color(int32(cards[i]))
					c_j := rule.Color(int32(cards[j]))
					if v_i > v_j {
						return false
					} else if v_i == v_j {
						return c_i < c_j
					}
					return true
				})
				for _, card := range cards {
					if card != rule.InvalideCard {
						billData.Cards = append(billData.Cards, card)
						tienlenPerson.CardInfoEnd = append(tienlenPerson.CardInfoEnd, card)
					}
				}
				pack.Datas = append(pack.Datas, billData)
				sceneEx.TryBillExGameDrop(playerEx.Player) //和
				tienlenType.PlayerData = append(tienlenType.PlayerData, tienlenPerson)
			}
		} else { //输赢局
			if sceneEx.IsTienLenToEnd() && len(sceneEx.tianHuSnids) == 0 {
				lastSnid := int32(0)
				playerNum := 0
				for _, p := range sceneEx.players {
					if p != nil && p.IsGameing() {
						playerNum++
						if !common.InSliceInt32(sceneEx.winSnids, p.SnId) {
							lastSnid = p.SnId
						}
					}
				}
				if lastSnid == 0 {
					logger.Logger.Error("TienLenToEndGameBilled Error: lastSnid == 0")
					return
				}
				if playerNum-1 != len(sceneEx.winSnids) {
					logger.Logger.Error("TienLenToEndGameBilled Error: playerNum: ", playerNum, " this.winSnids： ", sceneEx.winSnids)
					return
				}
				// 没出完牌的输家
				losePlayerScore := int64(0)
				loseRankScore := int64(0)
				losePlayer := sceneEx.players[lastSnid]
				if losePlayer != nil {
					var rankScore int64      // 排位积分
					var sceneTypeScore int64 // 场次额外积分
					var vipScore int64       // vip加成
					var roleScore int64      // 角色加成
					var roleAdd int32
					var otherScore int64 // 额外总加分
					oldRankScore := losePlayer.GetRankScore(sceneEx.GetDBGameFree().GetRankType())
					playerLoseScore := rule.GetLoseScore(losePlayer.cards, true)
					rankScore = int64(playerLoseScore) + 100
					score := int64(float64(s.GetBaseScore()) * (float64(playerLoseScore) + 100.0) / 100.0) //手牌输分和基础
					gainScore := score
					if sceneEx.IsTienLenYule() && sceneEx.bombToEnd > 0 { //娱乐版空放炸弹底分翻倍
						logger.Logger.Trace("TienLenYule,bombToEnd: ", sceneEx.bombToEnd, " SnId: ", losePlayer.SnId)
						for bomb := 0; bomb < sceneEx.bombToEnd; bomb++ {
							gainScore *= 2
							rankScore *= 2
						}
					}
					losePlayerCoin := losePlayer.GetCoin()
					if !sceneEx.IsMatchScene() && losePlayerCoin < gainScore {
						gainScore = losePlayerCoin
					}
					losePlayerScore = gainScore
					if sceneEx.IsMatchScene() { //比赛场是积分，不应该增加账变
						losePlayer.AddCoinNoLog(int64(-gainScore), 0)
					} else {
						losePlayer.AddCoin(int64(-gainScore), common.GainWay_CoinSceneLost, 0, "system", s.GetSceneName())
					}
					if sceneEx.IsRankMatch() {
						rankScore = int64(float64(rankScore) / 100.0 * float64(-rule.RankBaseScoreToEnd))
						loseRankScore = -rankScore
						rankScore += losePlayer.bombRankScore
						if rankScore > 0 {
							// 场次加成分
							sceneTypeScore = int64(math.Ceil(float64(rankScore) * float64(sceneEx.GetDBGameFree().GetSceneAdd()) / 100.0)) // 场次加成
							// vip加成分
							vipScore = int64(math.Ceil(float64(rankScore) * float64(losePlayer.VipExtra) / 100.0))
							// 角色加成分
							_, roleAdd = srvdata.RolePetMgrSington.GetRoleAdd(&losePlayer.PlayerData, common.RoleAddRankScore)
							roleScore = int64(math.Ceil(float64(rankScore) * float64(roleAdd) / 100.0))
						}
						otherScore = sceneTypeScore + vipScore + roleScore
						losePlayer.AddRankScore(sceneEx.GetDBGameFree().GetRankType(), rankScore+otherScore) // 炸弹分一起算
					}
					losePlayer.winCoin -= gainScore
					billData := &tienlen.TienLenPlayerGameBilled{
						SnId:      proto.Int32(losePlayer.SnId),
						IsWin:     proto.Int32(2),
						WinCoin:   proto.Int64(gainScore),
						GameCoin:  proto.Int64(losePlayer.GetCoin()),
						RankScore: losePlayer.GetRankScore(sceneEx.GetDBGameFree().GetRankType()),
						AddScore:  otherScore,
						TianHu:    losePlayer.tianHu,
					}
					billData.WinRankScore = billData.RankScore - oldRankScore
					if otherScore > 0 {
						// 场次加成配置
						billData.AddItems = append(billData.AddItems, &tienlen.AddItem{
							ItemType: 1,
							ItemId:   0,
							Addition: sceneEx.GetDBGameFree().GetSceneAdd(),
							Score:    sceneTypeScore,
						})
						// vip加成
						billData.AddItems = append(billData.AddItems, &tienlen.AddItem{
							ItemType: 5,
							ItemId:   0,
							Addition: losePlayer.VipExtra,
							Score:    vipScore,
						})
						// 角色加成
						billData.AddItems = append(billData.AddItems, &tienlen.AddItem{
							ItemType: 4,
							ItemId:   0,
							Addition: roleAdd,
							Score:    roleScore,
						})
					}
					isWin := int32(0)
					billCoin := losePlayer.bombScore - gainScore
					if billCoin > 0 {
						isWin = 1
					} else if billCoin < 0 {
						isWin = -1
					}
					losePlayer.CurIsWin = int64(isWin)
					tienlenPerson := model.TienLenPerson{
						UserId:        losePlayer.SnId,
						UserIcon:      losePlayer.Head,
						Platform:      losePlayer.Platform,
						Channel:       losePlayer.Channel,
						Promoter:      losePlayer.BeUnderAgentCode,
						PackageTag:    losePlayer.PackageID,
						InviterId:     losePlayer.InviterId,
						WBLevel:       losePlayer.WBLevel,
						IsRob:         losePlayer.IsRob,
						IsFirst:       sceneEx.IsPlayerFirst(sceneEx.GetPlayer(losePlayer.SnId)),
						IsLeave:       false,
						IsWin:         isWin,
						GainCoin:      -gainScore,
						BombCoin:      losePlayer.bombScore,
						BillCoin:      billCoin,
						GainTaxCoin:   0,
						BombTaxCoin:   losePlayer.bombTaxScore,
						BillTaxCoin:   losePlayer.bombTaxScore,
						Seat:          losePlayer.GetPos(),
						IsTianHu:      false,
						TestLog:       losePlayer.TestLog,
						WinRankScore:  rankScore + otherScore,
						RankScore:     losePlayer.GetRankScore(sceneEx.GetDBGameFree().GetRankType()),
						AddScore:      otherScore,
						BombRankScore: losePlayer.bombRankScore,
						CardsScore:    losePlayer.cardScore,
					}
					tienlenPerson.DelOrderCards = make(map[int][]int32, len(sceneEx.delOrders))
					for i2, orderSnid := range sceneEx.delOrders {
						if orderSnid == losePlayer.SnId {
							tienlenPerson.DelOrderCards[i2] = sceneEx.delCards[i2]
						}
					}
					//排下序，正常应该客户端排序
					cards := make([]int32, rule.HandCardNum)
					copy(cards, losePlayer.cards[:])
					sort.Slice(cards, func(i, j int) bool {
						v_i := rule.Value(int32(cards[i]))
						v_j := rule.Value(int32(cards[j]))
						c_i := rule.Color(int32(cards[i]))
						c_j := rule.Color(int32(cards[j]))
						if v_i > v_j {
							return false
						} else if v_i == v_j {
							return c_i < c_j
						}
						return true
					})
					for _, card := range cards {
						if card != rule.InvalideCard {
							billData.Cards = append(billData.Cards, card)
							tienlenPerson.CardInfoEnd = append(tienlenPerson.CardInfoEnd, card)
						}
					}
					pack.Datas = append(pack.Datas, billData)
					sceneEx.TryBillExGameDrop(losePlayer.Player) //输家
					tienlenType.PlayerData = append(tienlenType.PlayerData, tienlenPerson)

					lastWinPlayerScore := int64(0)
					lastWinPlayerRankScore := int64(0)
					if playerNum == 3 || playerNum == 4 {
						// 最后一位出完牌的输家
						lastWinPlayer := sceneEx.players[sceneEx.winSnids[len(sceneEx.winSnids)-1]]
						if lastWinPlayer != nil {
							var rankScore int64      // 排位积分
							var sceneTypeScore int64 // 场次额外积分
							var vipScore int64       // vip加成
							var otherScore int64     // 额外总加分
							oldRankScore := lastWinPlayer.GetRankScore(sceneEx.GetDBGameFree().GetRankType())
							lastWinScore := rule.GetLoseScore(lastWinPlayer.cards, true)
							rankScore = int64(lastWinScore) + 50
							lastWinscore := int64(float64(s.GetBaseScore()) * float64(int64(lastWinScore)+50) / 100.0) //手牌输分和基础
							astWinGainScore := lastWinscore
							if sceneEx.IsTienLenYule() && sceneEx.bombToEnd > 0 { //娱乐版空放炸弹底分翻倍
								logger.Logger.Trace("TienLenYule,bombToEnd: ", sceneEx.bombToEnd, " SnId: ", lastWinPlayer.SnId)
								for bomb := 0; bomb < sceneEx.bombToEnd; bomb++ {
									astWinGainScore *= 2
									rankScore *= 2
								}
							}
							lastWinPlayerCoin := lastWinPlayer.GetCoin()
							if !sceneEx.IsMatchScene() && lastWinPlayerCoin < astWinGainScore {
								astWinGainScore = lastWinPlayerCoin
							}
							lastWinPlayerScore = astWinGainScore
							if sceneEx.IsMatchScene() {
								lastWinPlayer.AddCoinNoLog(int64(-astWinGainScore), 0)
							} else {
								lastWinPlayer.AddCoin(int64(-astWinGainScore), common.GainWay_CoinSceneLost, 0, "system", s.GetSceneName())
							}
							if sceneEx.IsRankMatch() {
								rankScore = int64(float64(rankScore) / 100.0 * float64(-rule.RankBaseScoreToEnd))
								lastWinPlayerRankScore = -rankScore
								rankScore += lastWinPlayer.bombRankScore
								if rankScore > 0 {
									// 场次加成分
									sceneTypeScore = int64(math.Ceil(float64(rankScore) * float64(sceneEx.GetDBGameFree().GetSceneAdd()) / 100.0)) // 场次加成
									// vip加成分
									vipScore = int64(math.Ceil(float64(rankScore) * float64(lastWinPlayer.VipExtra) / 100.0))
									// 角色加成分
									_, roleAdd = srvdata.RolePetMgrSington.GetRoleAdd(&lastWinPlayer.PlayerData, common.RoleAddRankScore)
									roleScore = int64(math.Ceil(float64(rankScore) * float64(roleAdd) / 100.0))
								}
								otherScore = sceneTypeScore + vipScore + roleScore
								lastWinPlayer.AddRankScore(sceneEx.GetDBGameFree().GetRankType(), rankScore+otherScore) // 炸弹分一起算
							}
							lastWinPlayer.winCoin -= astWinGainScore
							billData := &tienlen.TienLenPlayerGameBilled{
								SnId:      proto.Int32(lastWinPlayer.SnId),
								IsWin:     proto.Int32(2),
								WinCoin:   proto.Int64(astWinGainScore),
								GameCoin:  proto.Int64(lastWinPlayer.GetCoin()),
								RankScore: lastWinPlayer.GetRankScore(sceneEx.GetDBGameFree().GetRankType()),
								AddScore:  otherScore,
								TianHu:    lastWinPlayer.tianHu,
							}
							billData.WinRankScore = billData.RankScore - oldRankScore
							if otherScore > 0 {
								// 场次加成配置
								billData.AddItems = append(billData.AddItems, &tienlen.AddItem{
									ItemType: 1,
									ItemId:   0,
									Addition: sceneEx.GetDBGameFree().GetSceneAdd(),
									Score:    sceneTypeScore,
								})
								// vip加成
								billData.AddItems = append(billData.AddItems, &tienlen.AddItem{
									ItemType: 5,
									ItemId:   0,
									Addition: lastWinPlayer.VipExtra,
									Score:    vipScore,
								})
								// 角色加成
								billData.AddItems = append(billData.AddItems, &tienlen.AddItem{
									ItemType: 4,
									ItemId:   0,
									Addition: roleAdd,
									Score:    roleScore,
								})
							}
							isWin := int32(0)
							billCoin := lastWinPlayer.bombScore - astWinGainScore
							if billCoin > 0 {
								isWin = 1
							} else if billCoin < 0 {
								isWin = -1
							}
							lastWinPlayer.CurIsWin = int64(isWin)
							tienlenPerson := model.TienLenPerson{
								UserId:        lastWinPlayer.SnId,
								UserIcon:      lastWinPlayer.Head,
								Platform:      lastWinPlayer.Platform,
								Channel:       lastWinPlayer.Channel,
								Promoter:      lastWinPlayer.BeUnderAgentCode,
								PackageTag:    lastWinPlayer.PackageID,
								InviterId:     lastWinPlayer.InviterId,
								WBLevel:       lastWinPlayer.WBLevel,
								IsRob:         lastWinPlayer.IsRob,
								IsFirst:       sceneEx.IsPlayerFirst(sceneEx.GetPlayer(lastWinPlayer.SnId)),
								IsLeave:       false,
								IsWin:         isWin,
								GainCoin:      -astWinGainScore,
								BombCoin:      lastWinPlayer.bombScore,
								BillCoin:      billCoin,
								GainTaxCoin:   0,
								BombTaxCoin:   lastWinPlayer.bombTaxScore,
								BillTaxCoin:   lastWinPlayer.bombTaxScore,
								Seat:          lastWinPlayer.GetPos(),
								IsTianHu:      false,
								TestLog:       lastWinPlayer.TestLog,
								WinRankScore:  rankScore + otherScore,
								RankScore:     lastWinPlayer.GetRankScore(sceneEx.GetDBGameFree().GetRankType()),
								AddScore:      otherScore,
								BombRankScore: lastWinPlayer.bombRankScore,
								CardsScore:    lastWinPlayer.cardScore,
							}
							tienlenPerson.DelOrderCards = make(map[int][]int32, len(sceneEx.delOrders))
							for i2, orderSnid := range sceneEx.delOrders {
								if orderSnid == lastWinPlayer.SnId {
									tienlenPerson.DelOrderCards[i2] = sceneEx.delCards[i2]
								}
							}
							pack.Datas = append(pack.Datas, billData)
							sceneEx.TryBillExGameDrop(lastWinPlayer.Player) //输家
							tienlenType.PlayerData = append(tienlenType.PlayerData, tienlenPerson)
						}
					}

					//first
					playerEx := sceneEx.players[sceneEx.winSnids[0]]
					if playerEx != nil {
						var rankScore int64      // 排位积分
						var sceneTypeScore int64 // 场次额外积分
						var vipScore int64       // vip加成
						var otherScore int64     // 额外总加分
						oldRankScore := playerEx.GetRankScore(sceneEx.GetDBGameFree().GetRankType())
						rankScore = loseRankScore
						taxRate := sceneEx.DbGameFree.GetTaxRate()                                      //万分比
						gainScore := int64(float64(losePlayerScore) * float64(10000-taxRate) / 10000.0) //税后
						gainTaxScore := losePlayerScore - gainScore                                     // 税收
						if playerNum == 3 {
							gainScore = int64(float64(losePlayerScore+lastWinPlayerScore) * float64(10000-taxRate) / 10000.0) //税后
							gainTaxScore = losePlayerScore + lastWinPlayerScore - gainScore
							rankScore = loseRankScore + lastWinPlayerRankScore
						}
						if sceneEx.IsMatchScene() {
							playerEx.AddCoinNoLog(int64(gainScore), 0)
						} else {
							playerEx.AddCoin(gainScore, common.GainWay_CoinSceneWin, 0, "system", s.GetSceneName())
						}
						if sceneEx.IsRankMatch() {
							rankScore += playerEx.bombRankScore
							if rankScore > 0 {
								// 场次加成分
								sceneTypeScore = int64(math.Ceil(float64(rankScore) * float64(sceneEx.GetDBGameFree().GetSceneAdd()) / 100.0)) // 场次加成
								// vip加成分
								vipScore = int64(math.Ceil(float64(rankScore) * float64(playerEx.VipExtra) / 100.0))
								// 角色加成分
								_, roleAdd = srvdata.RolePetMgrSington.GetRoleAdd(&playerEx.PlayerData, common.RoleAddRankScore)
								roleScore = int64(math.Ceil(float64(rankScore) * float64(roleAdd) / 100.0))
							}
							otherScore = sceneTypeScore + vipScore + roleScore
							playerEx.AddRankScore(sceneEx.GetDBGameFree().GetRankType(), rankScore+otherScore) // 炸弹分一起算
						}
						playerEx.winCoin += gainScore
						billData := &tienlen.TienLenPlayerGameBilled{
							SnId:      proto.Int32(playerEx.SnId),
							IsWin:     proto.Int32(1),
							WinCoin:   proto.Int64(gainScore),
							GameCoin:  proto.Int64(playerEx.GetCoin()),
							RankScore: playerEx.GetRankScore(sceneEx.GetDBGameFree().GetRankType()),
							AddScore:  otherScore,
							TianHu:    playerEx.tianHu,
						}
						billData.WinRankScore = billData.RankScore - oldRankScore
						if otherScore > 0 {
							// 场次加成配置
							billData.AddItems = append(billData.AddItems, &tienlen.AddItem{
								ItemType: 1,
								ItemId:   0,
								Addition: sceneEx.GetDBGameFree().GetSceneAdd(),
								Score:    sceneTypeScore,
							})
							// vip加成
							billData.AddItems = append(billData.AddItems, &tienlen.AddItem{
								ItemType: 5,
								ItemId:   0,
								Addition: playerEx.VipExtra,
								Score:    vipScore,
							})
							// 角色加成
							billData.AddItems = append(billData.AddItems, &tienlen.AddItem{
								ItemType: 4,
								ItemId:   0,
								Addition: roleAdd,
								Score:    roleScore,
							})
						}
						isWin := int32(0)
						billCoin := playerEx.bombScore + gainScore
						if billCoin > 0 {
							isWin = 1
						} else if billCoin < 0 {
							isWin = -1
						}
						playerEx.CurIsWin = int64(isWin)
						tienlenPerson := model.TienLenPerson{
							UserId:        playerEx.SnId,
							UserIcon:      playerEx.Head,
							Platform:      playerEx.Platform,
							Channel:       playerEx.Channel,
							Promoter:      playerEx.BeUnderAgentCode,
							PackageTag:    playerEx.PackageID,
							InviterId:     playerEx.InviterId,
							WBLevel:       playerEx.WBLevel,
							IsRob:         playerEx.IsRob,
							IsFirst:       sceneEx.IsPlayerFirst(sceneEx.GetPlayer(playerEx.SnId)),
							IsLeave:       false,
							IsWin:         isWin,
							GainCoin:      gainScore,
							BombCoin:      playerEx.bombScore,
							BillCoin:      billCoin,
							GainTaxCoin:   gainTaxScore,
							BombTaxCoin:   playerEx.bombTaxScore,
							BillTaxCoin:   playerEx.bombTaxScore + gainTaxScore,
							Seat:          playerEx.GetPos(),
							IsTianHu:      sceneEx.IsTianhuPlayer(playerEx.SnId),
							TestLog:       playerEx.TestLog,
							WinRankScore:  rankScore + otherScore,
							RankScore:     playerEx.GetRankScore(sceneEx.GetDBGameFree().GetRankType()),
							AddScore:      otherScore,
							BombRankScore: playerEx.bombRankScore,
							CardsScore:    playerEx.cardScore,
						}
						tienlenPerson.DelOrderCards = make(map[int][]int32, len(sceneEx.delOrders))
						for i2, orderSnid := range sceneEx.delOrders {
							if orderSnid == playerEx.SnId {
								tienlenPerson.DelOrderCards[i2] = sceneEx.delCards[i2]
							}
						}
						pack.Datas = append(pack.Datas, billData)
						sceneEx.TryBillExGameDrop(playerEx.Player) //赢家
						tienlenType.PlayerData = append(tienlenType.PlayerData, tienlenPerson)
					}
					//second
					if playerNum == 4 {
						playerEx = sceneEx.players[sceneEx.winSnids[1]]
						if playerEx != nil {
							var rankScore int64      // 排位积分
							var sceneTypeScore int64 // 场次额外积分
							var vipScore int64       // vip加成
							var otherScore int64     // 额外总加分
							oldRankScore := playerEx.GetRankScore(sceneEx.GetDBGameFree().GetRankType())
							rankScore = lastWinPlayerRankScore
							taxRate := sceneEx.DbGameFree.GetTaxRate()                                         //万分比
							gainScore := int64(float64(lastWinPlayerScore) * float64(10000-taxRate) / 10000.0) //税后
							gainTaxScore := lastWinPlayerScore - gainScore
							if sceneEx.IsMatchScene() {
								playerEx.AddCoinNoLog(int64(gainScore), 0)
							} else {
								playerEx.AddCoin(gainScore, common.GainWay_CoinSceneWin, 0, "system", s.GetSceneName())
							}
							if sceneEx.IsRankMatch() {
								rankScore += playerEx.bombRankScore
								if rankScore > 0 {
									// 场次加成分
									sceneTypeScore = int64(math.Ceil(float64(rankScore) * float64(sceneEx.GetDBGameFree().GetSceneAdd()) / 100.0)) // 场次加成
									// vip加成分
									vipScore = int64(math.Ceil(float64(rankScore) * float64(playerEx.VipExtra) / 100.0))
									// 角色加成分
									_, roleAdd = srvdata.RolePetMgrSington.GetRoleAdd(&playerEx.PlayerData, common.RoleAddRankScore)
									roleScore = int64(math.Ceil(float64(rankScore) * float64(roleAdd) / 100.0))
								}
								otherScore = sceneTypeScore + vipScore + roleScore
								playerEx.AddRankScore(sceneEx.GetDBGameFree().GetRankType(), rankScore+otherScore) // 炸弹分一起算
							}
							playerEx.winCoin += gainScore
							billData := &tienlen.TienLenPlayerGameBilled{
								SnId:      proto.Int32(playerEx.SnId),
								IsWin:     proto.Int32(1),
								WinCoin:   proto.Int64(gainScore),
								GameCoin:  proto.Int64(playerEx.GetCoin()),
								RankScore: playerEx.GetRankScore(sceneEx.GetDBGameFree().GetRankType()),
								AddScore:  otherScore,
								TianHu:    playerEx.tianHu,
							}
							billData.WinRankScore = billData.RankScore - oldRankScore
							if otherScore > 0 {
								// 场次加成配置
								billData.AddItems = append(billData.AddItems, &tienlen.AddItem{
									ItemType: 1,
									ItemId:   0,
									Addition: sceneEx.GetDBGameFree().GetSceneAdd(),
									Score:    sceneTypeScore,
								})
								// vip加成
								billData.AddItems = append(billData.AddItems, &tienlen.AddItem{
									ItemType: 5,
									ItemId:   0,
									Addition: playerEx.VipExtra,
									Score:    vipScore,
								})
								// 角色加成
								billData.AddItems = append(billData.AddItems, &tienlen.AddItem{
									ItemType: 4,
									ItemId:   0,
									Addition: roleAdd,
									Score:    roleScore,
								})
							}
							isWin := int32(0)
							billCoin := playerEx.bombScore + gainScore
							if billCoin > 0 {
								isWin = 1
							} else if billCoin < 0 {
								isWin = -1
							}
							playerEx.CurIsWin = int64(isWin)
							tienlenPerson := model.TienLenPerson{
								UserId:        playerEx.SnId,
								UserIcon:      playerEx.Head,
								Platform:      playerEx.Platform,
								Channel:       playerEx.Channel,
								Promoter:      playerEx.BeUnderAgentCode,
								PackageTag:    playerEx.PackageID,
								InviterId:     playerEx.InviterId,
								WBLevel:       playerEx.WBLevel,
								IsRob:         playerEx.IsRob,
								IsFirst:       sceneEx.IsPlayerFirst(sceneEx.GetPlayer(playerEx.SnId)),
								IsLeave:       false,
								IsWin:         isWin,
								GainCoin:      gainScore,
								BombCoin:      playerEx.bombScore,
								BillCoin:      billCoin,
								GainTaxCoin:   gainTaxScore,
								BombTaxCoin:   playerEx.bombTaxScore,
								BillTaxCoin:   playerEx.bombTaxScore + gainTaxScore,
								Seat:          playerEx.GetPos(),
								IsTianHu:      sceneEx.IsTianhuPlayer(playerEx.SnId),
								TestLog:       playerEx.TestLog,
								WinRankScore:  rankScore + otherScore,
								RankScore:     playerEx.GetRankScore(sceneEx.GetDBGameFree().GetRankType()),
								AddScore:      otherScore,
								BombRankScore: playerEx.bombRankScore,
								CardsScore:    playerEx.cardScore,
							}
							tienlenPerson.DelOrderCards = make(map[int][]int32, len(sceneEx.delOrders))
							for i2, orderSnid := range sceneEx.delOrders {
								if orderSnid == playerEx.SnId {
									tienlenPerson.DelOrderCards[i2] = sceneEx.delCards[i2]
								}
							}
							pack.Datas = append(pack.Datas, billData)
							sceneEx.TryBillExGameDrop(playerEx.Player) //赢家
							tienlenType.PlayerData = append(tienlenType.PlayerData, tienlenPerson)
						}
					}
				}
			} else {
				// 输家
				for i := 0; i < sceneEx.GetPlayerNum(); i++ {
					var rankScore int64      // 排位积分
					var sceneTypeScore int64 // 场次额外积分
					var vipScore int64       // vip加成
					var roleScore int64      // 角色加成
					var roleAdd int32
					var otherScore int64 // 额外总加分
					playerEx := sceneEx.seats[i]
					if playerEx == nil {
						continue
					}
					oldRankScore := playerEx.GetRankScore(sceneEx.GetDBGameFree().GetRankType())
					if !playerEx.IsGameing() {
						continue
					}
					if sceneEx.IsTianhuPlayer(playerEx.SnId) {
						continue
					}
					if sceneEx.IsWinPlayer(playerEx.SnId) && !sceneEx.IsTienLenToEnd() {
						continue
					}
					logger.Logger.Trace("SceneBilledStateTienLe,losePos: ", i, " SnId: ", playerEx.SnId, " BaseScore: ", s.GetBaseScore())
					playerLoseScore := rule.GetLoseScore(playerEx.cards, sceneEx.IsTienLenToEnd())
					score := int64(s.GetBaseScore()) * int64(playerLoseScore)
					rankScore = int64(playerLoseScore)
					gainScore := int64(0)
					if len(sceneEx.tianHuSnids) != 0 { //天胡结算翻倍
						gainScore = int64(score) * int64(len(sceneEx.tianHuSnids)) * 2
						rankScore = int64(playerLoseScore) * int64(len(sceneEx.tianHuSnids)) * 2
						if sceneEx.IsTienLenToEnd() { //打到底天胡结算加上2倍底注（不是翻倍）
							gainScore = int64(float64(score)/100.0 + float64(s.GetBaseScore()*2))
							rankScore = int64(playerLoseScore) + 200
						}
					} else { //正常结算
						gainScore = int64(score)
						if sceneEx.IsTienLenYule() && sceneEx.bombToEnd > 0 { //娱乐版空放炸弹底分翻倍
							logger.Logger.Trace("TienLenYule,bombToEnd: ", sceneEx.bombToEnd, " SnId: ", playerEx.SnId)
							for bomb := 0; bomb < sceneEx.bombToEnd; bomb++ {
								gainScore *= 2
								rankScore *= 2
							}
						}
					}
					losePlayerCoin := playerEx.GetCoin()
					if !sceneEx.IsMatchScene() && losePlayerCoin < gainScore {
						gainScore = losePlayerCoin
					}
					winScore += gainScore
					if sceneEx.IsMatchScene() {
						playerEx.AddCoinNoLog(int64(-gainScore), 0)
					} else {
						playerEx.AddCoin(int64(-gainScore), common.GainWay_CoinSceneLost, 0, "system", s.GetSceneName())
					}
					if sceneEx.IsRankMatch() {
						// 排位积分
						if sceneEx.IsTienLenToEnd() {
							rankScore = int64(float64(rankScore) / 100.0 * float64(-rule.RankBaseScoreToEnd))
						} else {
							rankScore *= -rule.RankBaseScore
						}
						winRankScore += -rankScore // 不含炸弹分
						rankScore += playerEx.bombRankScore
						if rankScore > 0 {
							// 场次加成分
							sceneTypeScore = int64(math.Ceil(float64(rankScore) * float64(sceneEx.GetDBGameFree().GetSceneAdd()) / 100)) // 场次加成
							// vip加成分
							vipScore = int64(math.Ceil(float64(rankScore) * float64(playerEx.VipExtra) / 100.0))
							// 角色加成分
							_, roleAdd = srvdata.RolePetMgrSington.GetRoleAdd(&playerEx.PlayerData, common.RoleAddRankScore)
							roleScore = int64(math.Ceil(float64(rankScore) * float64(roleAdd) / 100.0))
						}
						otherScore = sceneTypeScore + vipScore + roleScore
						playerEx.AddRankScore(sceneEx.GetDBGameFree().GetRankType(), rankScore+otherScore) // 炸弹分一起算
					}
					playerEx.winCoin -= gainScore
					billData := &tienlen.TienLenPlayerGameBilled{
						SnId:     proto.Int32(playerEx.SnId),
						IsWin:    proto.Int32(2),
						WinCoin:  proto.Int64(gainScore),
						GameCoin: proto.Int64(playerEx.GetCoin()),
						//WinRankScore: -rankScore,
						RankScore: playerEx.GetRankScore(sceneEx.GetDBGameFree().GetRankType()),
						AddScore:  otherScore,
						TianHu:    playerEx.tianHu,
					}
					billData.WinRankScore = billData.RankScore - oldRankScore
					if otherScore > 0 {
						// 场次加成配置
						billData.AddItems = append(billData.AddItems, &tienlen.AddItem{
							ItemType: 1,
							ItemId:   0,
							Addition: sceneEx.GetDBGameFree().GetSceneAdd(),
							Score:    sceneTypeScore,
						})
						// vip加成
						billData.AddItems = append(billData.AddItems, &tienlen.AddItem{
							ItemType: 5,
							ItemId:   0,
							Addition: playerEx.VipExtra,
							Score:    vipScore,
						})
						// 角色加成
						billData.AddItems = append(billData.AddItems, &tienlen.AddItem{
							ItemType: 4,
							ItemId:   0,
							Addition: roleAdd,
							Score:    roleScore,
						})
					}

					isWin := int32(0)
					billCoin := playerEx.bombScore - gainScore
					if billCoin > 0 {
						isWin = 1
					} else if billCoin < 0 {
						isWin = -1
					}
					playerEx.CurIsWin = int64(isWin)
					tienlenPerson := model.TienLenPerson{
						UserId:        playerEx.SnId,
						UserIcon:      playerEx.Head,
						Platform:      playerEx.Platform,
						Channel:       playerEx.Channel,
						Promoter:      playerEx.BeUnderAgentCode,
						PackageTag:    playerEx.PackageID,
						InviterId:     playerEx.InviterId,
						WBLevel:       playerEx.WBLevel,
						IsRob:         playerEx.IsRob,
						IsFirst:       sceneEx.IsPlayerFirst(sceneEx.GetPlayer(playerEx.SnId)),
						IsLeave:       false,
						IsWin:         isWin,
						GainCoin:      -gainScore,
						BombCoin:      playerEx.bombScore,
						BillCoin:      billCoin,
						GainTaxCoin:   0,
						BombTaxCoin:   playerEx.bombTaxScore,
						BillTaxCoin:   playerEx.bombTaxScore,
						Seat:          playerEx.GetPos(),
						IsTianHu:      false,
						TestLog:       playerEx.TestLog,
						WinRankScore:  rankScore + otherScore,
						RankScore:     playerEx.GetRankScore(sceneEx.GetDBGameFree().GetRankType()),
						AddScore:      otherScore,
						BombRankScore: playerEx.bombRankScore,
						CardsScore:    playerEx.cardScore,
					}
					tienlenPerson.DelOrderCards = make(map[int][]int32, len(sceneEx.delOrders))
					for i2, orderSnid := range sceneEx.delOrders {
						if orderSnid == playerEx.SnId {
							tienlenPerson.DelOrderCards[i2] = sceneEx.delCards[i2]
						}
					}
					//排下序，正常应该客户端排序
					cards := make([]int32, rule.HandCardNum)
					copy(cards, playerEx.cards[:])
					sort.Slice(cards, func(i, j int) bool {
						v_i := rule.Value(int32(cards[i]))
						v_j := rule.Value(int32(cards[j]))
						c_i := rule.Color(int32(cards[i]))
						c_j := rule.Color(int32(cards[j]))
						if v_i > v_j {
							return false
						} else if v_i == v_j {
							return c_i < c_j
						}
						return true
					})
					for _, card := range cards {
						if card != rule.InvalideCard {
							billData.Cards = append(billData.Cards, card)
							tienlenPerson.CardInfoEnd = append(tienlenPerson.CardInfoEnd, card)
						}
					}
					pack.Datas = append(pack.Datas, billData)
					sceneEx.TryBillExGameDrop(playerEx.Player) //输家
					tienlenType.PlayerData = append(tienlenType.PlayerData, tienlenPerson)
				}
				logger.Logger.Trace("SceneBilledStateTienLe, winSnids: ", sceneEx.winSnids, " winScore: ", winScore)

				//赢家
				if len(sceneEx.tianHuSnids) != 0 {
					winScore = winScore / int64(len(sceneEx.tianHuSnids))
					winRankScore = winRankScore / int64(len(sceneEx.tianHuSnids))
				}
				for _, winSnid := range sceneEx.winSnids {
					var rankScore = winRankScore // 排位积分
					var sceneTypeScore int64     // 场次额外积分
					var vipScore int64           // vip加成
					var roleScore int64          // 角色加成
					var roleAdd int32
					var otherScore int64 // 额外总加分
					playerEx := sceneEx.players[winSnid]
					if playerEx != nil {
						taxRate := sceneEx.DbGameFree.GetTaxRate()                               //万分比
						gainScore := int64(float64(winScore) * float64(10000-taxRate) / 10000.0) //税后
						gainTaxScore := winScore - gainScore
						if sceneEx.IsMatchScene() {
							playerEx.AddCoinNoLog(int64(gainScore), 0)
						} else {
							playerEx.AddCoin(gainScore, common.GainWay_CoinSceneWin, 0, "system", s.GetSceneName())
						}
						if sceneEx.IsRankMatch() {
							// 排位积分
							rankScore += playerEx.bombRankScore
							// 场次加成分
							sceneTypeScore = int64(math.Ceil(float64(rankScore) * float64(sceneEx.GetDBGameFree().GetSceneAdd()) / 100.0)) // 场次加成
							// vip加成分
							vipScore = int64(math.Ceil(float64(rankScore) * float64(playerEx.VipExtra) / 100.0))
							// 角色加成分
							_, roleAdd = srvdata.RolePetMgrSington.GetRoleAdd(&playerEx.PlayerData, common.RoleAddRankScore)
							roleScore = int64(math.Ceil(float64(rankScore) * float64(roleAdd) / 100.0))
							otherScore = sceneTypeScore + vipScore + roleScore
							playerEx.AddRankScore(sceneEx.GetDBGameFree().GetRankType(), rankScore+otherScore)
						}
						playerEx.winCoin += gainScore
						billData := &tienlen.TienLenPlayerGameBilled{
							SnId:         proto.Int32(playerEx.SnId),
							IsWin:        proto.Int32(1),
							WinCoin:      proto.Int64(gainScore),
							GameCoin:     proto.Int64(playerEx.GetCoin()),
							WinRankScore: rankScore + otherScore,
							RankScore:    playerEx.GetRankScore(sceneEx.GetDBGameFree().GetRankType()),
							AddScore:     otherScore,
							TianHu:       playerEx.tianHu,
						}
						// 场次加成配置
						billData.AddItems = append(billData.AddItems, &tienlen.AddItem{
							ItemType: 1,
							ItemId:   0,
							Addition: sceneEx.GetDBGameFree().GetSceneAdd(),
							Score:    sceneTypeScore,
						})
						// vip加成
						billData.AddItems = append(billData.AddItems, &tienlen.AddItem{
							ItemType: 5,
							ItemId:   0,
							Addition: playerEx.VipExtra,
							Score:    vipScore,
						})
						// 角色加成
						billData.AddItems = append(billData.AddItems, &tienlen.AddItem{
							ItemType: 4,
							ItemId:   0,
							Addition: roleAdd,
							Score:    roleScore,
						})

						isWin := int32(0)
						billCoin := playerEx.bombScore + gainScore
						if billCoin > 0 {
							isWin = 1
						} else if billCoin < 0 {
							isWin = -1
						}
						playerEx.CurIsWin = int64(isWin)
						tienlenPerson := model.TienLenPerson{
							UserId:        playerEx.SnId,
							UserIcon:      playerEx.Head,
							Platform:      playerEx.Platform,
							Channel:       playerEx.Channel,
							Promoter:      playerEx.BeUnderAgentCode,
							PackageTag:    playerEx.PackageID,
							InviterId:     playerEx.InviterId,
							WBLevel:       playerEx.WBLevel,
							IsRob:         playerEx.IsRob,
							IsFirst:       sceneEx.IsPlayerFirst(sceneEx.GetPlayer(playerEx.SnId)),
							IsLeave:       false,
							IsWin:         isWin,
							GainCoin:      gainScore,
							BombCoin:      playerEx.bombScore,
							BillCoin:      billCoin,
							GainTaxCoin:   gainTaxScore,
							BombTaxCoin:   playerEx.bombTaxScore,
							BillTaxCoin:   playerEx.bombTaxScore + gainTaxScore,
							Seat:          playerEx.GetPos(),
							IsTianHu:      sceneEx.IsTianhuPlayer(playerEx.SnId),
							TestLog:       playerEx.TestLog,
							WinRankScore:  rankScore + otherScore,
							RankScore:     playerEx.GetRankScore(sceneEx.GetDBGameFree().GetRankType()),
							AddScore:      otherScore,
							BombRankScore: playerEx.bombRankScore,
							CardsScore:    playerEx.cardScore,
						}
						tienlenPerson.DelOrderCards = make(map[int][]int32, len(sceneEx.delOrders))
						for i2, orderSnid := range sceneEx.delOrders {
							if orderSnid == playerEx.SnId {
								tienlenPerson.DelOrderCards[i2] = sceneEx.delCards[i2]
							}
						}
						//排下序，正常应该客户端排序
						cards := make([]int32, rule.HandCardNum)
						copy(cards, playerEx.cards[:])
						sort.Slice(cards, func(i, j int) bool {
							v_i := rule.Value(int32(cards[i]))
							v_j := rule.Value(int32(cards[j]))
							c_i := rule.Color(int32(cards[i]))
							c_j := rule.Color(int32(cards[j]))
							if v_i > v_j {
								return false
							} else if v_i == v_j {
								return c_i < c_j
							}
							return true
						})
						for _, card := range cards {
							if card != rule.InvalideCard {
								billData.Cards = append(billData.Cards, card)
								tienlenPerson.CardInfoEnd = append(tienlenPerson.CardInfoEnd, card)
							}
						}
						pack.Datas = append(pack.Datas, billData)
						sceneEx.TryBillExGameDrop(playerEx.Player) //赢家
						tienlenType.PlayerData = append(tienlenType.PlayerData, tienlenPerson)
					}
				}
			}
		}

		proto.SetDefaults(pack)
		s.Broadcast(int(tienlen.TienLenPacketID_PACKET_SCTienLenGameBilled), pack, 0)
		logger.Logger.Trace("TienLenPacketID_PACKET_SCTienLenGameBilled gameFreeId:", sceneEx.GetGameFreeId(), ";pack:", pack)

		// 牌局记录
		info, err := model.MarshalGameNoteByFIGHT(&tienlenType)
		if err == nil {
			isSave := false
			for _, o_player := range tienlenType.PlayerData {
				if !sceneEx.Testing && !o_player.IsRob {
					isSave = true
					var totalin, totalout int64
					if o_player.GainCoin < 0 {
						totalin -= (o_player.GainCoin + o_player.GainTaxCoin)
					} else {
						totalout += (o_player.GainCoin + o_player.GainTaxCoin)
					}
					if o_player.BombCoin < 0 {
						totalin -= (o_player.BombCoin + o_player.BombTaxCoin)
					} else {
						totalout += (o_player.BombCoin + o_player.BombTaxCoin)
					}

					validFlow := totalin + totalout
					validBet := common.AbsI64(totalin - totalout)
					sceneEx.SaveFriendRecord(o_player.UserId, o_player.IsWin, o_player.BillCoin, sceneEx.GetBaseScore())

					// 玩家数据统计
					winState := int32(0)
					if o_player.IsWin == 1 {
						winState = 1
					} else if o_player.IsWin == 2 {
						winState = 2
					} else {
						winState = 3
					}
					sceneEx.Statistics(&base.StaticParam{
						SnId:           o_player.UserId,
						Gain:           o_player.GainCoin + o_player.BombCoin,
						GainTax:        o_player.GainTaxCoin + o_player.BombTaxCoin,
						IsAddTimes:     true,
						HasRobotGaming: sceneEx.robotGamingNum > 0,
						WinState:       winState,
					})

					// 保存玩家游戏记录
					sceneEx.SaveGamePlayerListLog(o_player.UserId,
						base.GetSaveGamePlayerListLogParam(o_player.Platform, o_player.Channel, o_player.Promoter,
							o_player.PackageTag, sceneEx.recordId, o_player.InviterId, totalin, totalout, o_player.BillTaxCoin,
							0, 0, o_player.GainCoin+o_player.BombCoin, validBet, validFlow, o_player.IsFirst, o_player.IsLeave))
				}
			}
			if isSave {
				// 牌局记录
				sceneEx.SaveGameDetailedLog(sceneEx.recordId, info, &base.GameDetailedParam{})
			}
		}

		if s.IsMatchScene() { //记录比赛数据
			var matchRobotGrades []base.MatchRobotGrade
			var realPlayers []*base.Player
			for _, p := range sceneEx.players {
				if p != nil && p.IsGameing() {
					if p.IsRob {
						CopySnid := p.SnId
						if len(p.MatchParams) > 2 && p.MatchParams[2] != 0 {
							CopySnid = p.MatchParams[2]
						}
						mrg := &base.MatchRobotGrade{
							CopySnid: CopySnid,
							Grade:    int32(p.Coin),
						}
						matchRobotGrades = append(matchRobotGrades, *mrg)
					} else {
						p.MatchRobotGrades = nil
						realPlayers = append(realPlayers, p.Player)
					}
				}

			}
			if realPlayers != nil && len(realPlayers) == 1 && matchRobotGrades != nil && len(matchRobotGrades) > 0 {
				//真人把机器人记录带出去
				realPlayers[0].MatchRobotGrades = matchRobotGrades
			}
		}
		if sceneEx.isCardsKu {
			playerEx := sceneEx.players[sceneEx.winSnids[0]]
			logger.Logger.Infof("telnet使用牌库，模式：是否娱乐 %t,随机到的牌库id = %d,赢家是否是机器人：%t,赢家snid = %d\n", sceneEx.IsTienLenYule(), sceneEx.cardsKuId, playerEx.IsRob, playerEx.SnId)
		}
		sceneEx.isCardsKu = false
		sceneEx.cardsKuId = -1
		if !sceneEx.IsRankMatch() {
			sceneEx.NotifySceneRoundPause()
		}
	}
}

// 状态离开时
func (this *SceneBilledStateTienLen) OnLeave(s *base.Scene) {
	this.SceneBaseStateTienLen.OnLeave(s)
	if sceneEx, ok := s.GetExtraData().(*TienLenSceneData); ok {
		sceneEx.lastGamingPlayerNum = sceneEx.curGamingPlayerNum
		sceneEx.curGamingPlayerNum = 0
		if len(sceneEx.tianHuSnids) != sceneEx.GetGameingPlayerCnt() { //非和局
			sceneEx.lastWinSnid = sceneEx.winSnids[0]
		}
		sceneEx.SetGaming(false)

		hasLeave := false
		//剔除下线玩家
		for i := 0; i < sceneEx.GetPlayerNum(); i++ {
			player_data := sceneEx.seats[i]
			if player_data == nil {
				continue
			}
			player_data.Clear()
			if sceneEx.IsMatchScene() {
				continue
			}
			if !player_data.IsOnLine() {
				sceneEx.PlayerLeave(player_data.Player, common.PlayerLeaveReason_DropLine, true)
				hasLeave = true
				continue
			}
			if player_data.IsRob {
				if player_data.robotGameTimes <= 0 {
					sceneEx.PlayerLeave(player_data.Player, common.PlayerLeaveReason_Normal, true)
					hasLeave = true
					continue
				}
				if s.CoinOverMaxLimit(player_data.GetCoin(), player_data.Player) {
					sceneEx.PlayerLeave(player_data.Player, common.PlayerLeaveReason_Normal, true)
					hasLeave = true
					continue
				}
			}
			if !s.CoinInLimit(player_data.GetCoin()) {
				sceneEx.PlayerLeave(player_data.Player, s.NotCoinInLimitType(player_data.GetCoin()), true)
				hasLeave = true
				continue
			}

			if player_data.IsAuto() && !player_data.IsRob {
				player_data.LeaveAutoState(s)

				if !(s.IsMatchScene() || s.IsRankMatch()) {
					sceneEx.PlayerLeave(player_data.Player, common.PlayerLeaveReason_AutoState, true)
					hasLeave = true
				}
				continue
			}
		}

		if !hasLeave && !sceneEx.IsRobFightGame() && !sceneEx.IsMatchScene() {
			s.TryDismissRob()
		}

		if s.CheckNeedDestroy() || (s.IsMatchScene() && (!s.MatchFinals || (s.MatchFinals && s.NumOfGames >= 2))) { // 非决赛打一场 决赛打两场
			sceneEx.SceneDestroy(true)
		}
		s.RankMatchDestroy()
		s.TryRelease()
	}
}

// 玩家操作
func (this *SceneBilledStateTienLen) OnPlayerOp(s *base.Scene, p *base.Player, opcode int, params []int64) bool {
	if this.SceneBaseStateTienLen.OnPlayerOp(s, p, opcode, params) {
		return true
	}
	return true
}

// 玩家事件
func (this *SceneBilledStateTienLen) OnPlayerEvent(s *base.Scene, p *base.Player, evtcode int, params []int64) {
	this.SceneBaseStateTienLen.OnPlayerEvent(s, p, evtcode, params)
}

func (this *SceneBilledStateTienLen) OnTick(s *base.Scene) {
	this.SceneBaseStateTienLen.OnTick(s)

	if sceneEx, ok := s.GetExtraData().(*TienLenSceneData); ok {
		newTime := rule.TienLenBilledTimeout
		if sceneEx.isAllRob {
			newTime = time.Second * 1
		}
		n := len(sceneEx.tianHuSnids)
		newTime += time.Duration(n) * rule.TIenLenTianhuTimeout
		if time.Now().Sub(sceneEx.StateStartTime) > newTime {
			//开始前再次检查开始条件
			if sceneEx.CanStart() == true {
				s.ChangeSceneState(rule.TienLenSceneStateWaitStart)
			} else {
				s.ChangeSceneState(rule.TienLenSceneStateWaitPlayer)
			}
		}
	}
}

// //////////////////////////////////////////////////////////////////////////////
func (this *ScenePolicyTienLen) RegisteSceneState(state base.SceneState) {
	if state == nil {
		return
	}
	stateid := state.GetState()
	if stateid < 0 || stateid >= rule.TienLenSceneStateMax {
		return
	}
	this.states[stateid] = state
}

func (this *ScenePolicyTienLen) GetSceneState(s *base.Scene, stateid int) base.SceneState {
	if stateid >= 0 && stateid < rule.TienLenSceneStateMax {
		return this.states[stateid]
	}
	return nil
}

func init() {
	ScenePolicyTienLenSingleton.RegisteSceneState(&SceneWaitPlayerStateTienLen{})
	ScenePolicyTienLenSingleton.RegisteSceneState(&SceneWaitStartStateTienLen{})
	ScenePolicyTienLenSingleton.RegisteSceneState(&SceneHandCardStateTienLen{})
	ScenePolicyTienLenSingleton.RegisteSceneState(&ScenePlayerOpStateTienLen{})
	ScenePolicyTienLenSingleton.RegisteSceneState(&SceneBilledStateTienLen{})

	core.RegisteHook(core.HOOK_BEFORE_START, func() error {
		// 自由桌
		base.RegisteScenePolicy(common.GameId_TienLen, 0, ScenePolicyTienLenSingleton)
		base.RegisteScenePolicy(common.GameId_TienLen_yl, 0, ScenePolicyTienLenSingleton)
		base.RegisteScenePolicy(common.GameId_TienLen_toend, 0, ScenePolicyTienLenSingleton)
		base.RegisteScenePolicy(common.GameId_TienLen_yl_toend, 0, ScenePolicyTienLenSingleton)
		// 比赛
		base.RegisteScenePolicy(common.GameId_TienLen_m, 0, ScenePolicyTienLenSingleton)
		base.RegisteScenePolicy(common.GameId_TienLen_m_toend, 0, ScenePolicyTienLenSingleton)
		// 手动选场
		base.RegisteScenePolicy(common.GameId_TienLenSelect, 0, ScenePolicyTienLenSingleton)
		base.RegisteScenePolicy(common.GameId_TienLenSelect_toend, 0, ScenePolicyTienLenSingleton)
		base.RegisteScenePolicy(common.GameId_TienLenSelect_yl, 0, ScenePolicyTienLenSingleton)
		base.RegisteScenePolicy(common.GameId_TienLenSelect_yl_toend, 0, ScenePolicyTienLenSingleton)
		// 排位赛
		base.RegisteScenePolicy(common.GameId_TienLenRank, 0, ScenePolicyTienLenSingleton)
		base.RegisteScenePolicy(common.GameId_TienLenRank_toend, 0, ScenePolicyTienLenSingleton)
		base.RegisteScenePolicy(common.GameId_TienLenRank_yl, 0, ScenePolicyTienLenSingleton)
		base.RegisteScenePolicy(common.GameId_TienLenRank_yl_toend, 0, ScenePolicyTienLenSingleton)
		return nil
	})
}
