Gogs 4 hónapja
szülő
commit
815d637f2b

+ 2 - 1
bin/client_msg/common.proto

@@ -113,6 +113,7 @@ message PlayerOpt {
     PlayerOptType opt_type = 1;  // 操作类型
     int32 bet_amount = 2;        // 下注金额
     int64 timestamp = 3;         // 操作时间戳
+    repeated ReqCard seenCards = 4;  // 看牌的牌
 }
 
 message ReqRoom {
@@ -131,7 +132,7 @@ message ReqCard {
 message  ReqPlayer {
   string Id = 1;
   repeated ReqCard HandCards =2;
-  int32 SitPos = 3;
+  optional int32 SitPos = 3;
   bool IsDealer = 4;
 }
 message ReqGameRound {

+ 0 - 2
src/server/game/internal/handler.go

@@ -6,7 +6,6 @@ import (
 	"server/msg"
 
 	"github.com/name5566/leaf/gate"
-	"github.com/name5566/leaf/log"
 )
 
 func init() {
@@ -20,7 +19,6 @@ func Handler(m interface{}, h interface{}) {
 func handlePlayerOptAction(args []interface{}) {
 	m := args[0].(*msg.ResPlayerOptAction)
 	a := args[1].(gate.Agent)
-	log.Debug("player opt action %v", m.PlayerOpt)
 	switch m.GameId {
 	case "teen_patti":
 		events.EventChan <- events.Event{

+ 62 - 27
src/server/game/room/room.go

@@ -1,35 +1,17 @@
 package room
 
 import (
+	"server/msg"
+	"sort"
+
 	"github.com/name5566/leaf/gate"
 	"github.com/name5566/leaf/log"
 )
 
-type Round struct {
-	// 回合数
-	Round int
-	// 回合座位
-	RoundSitPos int
-	// 回合下注
-	RoundBet int
-	// 思考时间
-	RoundThinkTime int
-	// 回合结束时间
-	RoundEndTime int64
-	// 操作看牌
-	OpLookCard bool
-	// 操作下注
-	OpBet bool
-	// 操作弃牌
-	OpPack bool
-	// 操作开牌
-	OpOpenCard bool
-}
-
 // 定一个类为每局游戏
 type GameRound struct {
 	//回合列表
-	Rounds []Round
+	Rounds []msg.ReqRound
 }
 
 // RoomStatus 房间状态
@@ -73,17 +55,19 @@ type Player struct {
 	// 等待发牌
 	WaitCard bool
 	// 是否已看牌
-	IsLookCard bool
+	IsSeen bool
 	// 是否已弃牌
 	IsPacked bool
-	// 是否开牌
-	IsOpenCard bool
+	// 是否开牌
+	IsShow bool
 	// 是否已结算
 	IsSettle bool
 	// 是否已准备
 	IsReady bool
 	// 是否是庄家
 	IsDealer bool
+	// 是否获胜
+	IsWin bool
 }
 
 // 获取玩家ID
@@ -192,6 +176,57 @@ func (r *Room) GetPlayerBySitPos(sitPos int32) *Player {
 }
 
 // 增加玩家操作到当前回合
-func (r *Room) AddPlayerOpt(round Round) {
-	r.GameRound.Rounds = append(r.GameRound.Rounds, round)
+func (r *Room) AddPlayerOpt(round *msg.ReqRound) {
+	r.Round++
+	r.GameRound.Rounds = append(r.GameRound.Rounds, *round)
+}
+
+// 设置当前回合为下个没有弃牌的玩家(逆时针)
+func (r *Room) SetNextRound() {
+	curSitPos := int32(r.RoundSitPos)
+
+	// 按照座位号从小到大排序
+	sort.Slice(r.Players, func(i, j int) bool {
+		return r.Players[i].SitPos < r.Players[j].SitPos
+	})
+
+	// 获取当前座位号的索引
+	curSitPosIndex := -1
+	for i, player := range r.Players {
+		if player.SitPos == curSitPos {
+			curSitPosIndex = i
+			break
+		}
+	}
+
+	// 逆时针:当前索引减1
+	// 如果减1后小于0,则回到最后一个位置
+	nextIndex := curSitPosIndex
+	for i := 0; i < len(r.Players); i++ {
+		nextIndex = (curSitPosIndex - 1 - i + len(r.Players)) % len(r.Players)
+		if !r.Players[nextIndex].IsPacked {
+			break
+		}
+	}
+	r.RoundSitPos = int(r.Players[nextIndex].SitPos)
+	log.Debug("下一个操作的玩家座位:%d", r.RoundSitPos)
+}
+
+// 获取没有弃牌的玩家是否只有一个,并返回没有弃牌的座位号
+func (r *Room) IsOnlyOneNotPacked() (bool, int32) {
+	count := 0
+	sitPos := int32(0)
+	for _, player := range r.Players {
+		if !player.IsPacked {
+			count++
+			sitPos = player.SitPos
+		}
+	}
+	return count == 1, sitPos
+}
+
+// 当前座位号玩家获胜
+func (r *Room) PlayerWin(sitPos int32) {
+	player := r.GetPlayerBySitPos(sitPos)
+	player.IsWin = true
 }

+ 4 - 8
src/server/game/teen/buildRoom.go

@@ -23,7 +23,7 @@ func convertToMsgPlayerList(players []*room.Player) []*msg.ReqPlayer {
 		msgPlayers[i] = &msg.ReqPlayer{
 			Id:        player.Id,
 			HandCards: convertToMsgCardList(player.HandCards),
-			SitPos:    int32(player.SitPos),
+			SitPos:    &player.SitPos,
 			IsDealer:  player.IsDealer,
 		}
 	}
@@ -47,14 +47,10 @@ func convertToMsgGameRound(gameRound *room.GameRound) *msg.ReqGameRound {
 		Rounds: convertToMsgRoundList(gameRound.Rounds),
 	}
 }
-
-func convertToMsgRoundList(rounds []room.Round) []*msg.ReqRound {
+func convertToMsgRoundList(rounds []msg.ReqRound) []*msg.ReqRound {
 	msgRounds := make([]*msg.ReqRound, len(rounds))
-	for i, round := range rounds {
-		msgRounds[i] = &msg.ReqRound{
-			Round:       int32(round.Round),
-			RoundSitPos: int32(round.RoundSitPos),
-		}
+	for i := range rounds {
+		msgRounds[i] = &rounds[i]
 	}
 	return msgRounds
 }

+ 6 - 6
src/server/game/teen/event.go

@@ -35,13 +35,18 @@ func handleEvents() {
 				IsRobot:  false,
 				UserData: userData,
 				SitPos:   SelfSitPos,
+				IsPacked: false,
+				IsSeen:   false,
+				IsShow:   false,
+				IsDealer: false,
 			})
 			userData.Teen_Patti_Room.GameRound = &room.GameRound{
-				Rounds: make([]room.Round, 0),
+				Rounds: make([]msg.ReqRound, 0),
 			}
 			go startGame(m.UserId, m.RoomId, event.Agent, userData.Teen_Patti_Room)
 		case events.EventTeenPattiPlayerOptAction:
 			// m := event.Data.(*msg.ResPlayerOptAction)
+			log.Debug("EventTeenPattiPlayerOptAction")
 			m := event.Data.(*msg.ResPlayerOptAction)
 			userData := event.Agent.UserData().(*user.UserData)
 			if userData.Teen_Patti_Room != nil {
@@ -52,8 +57,3 @@ func handleEvents() {
 		}
 	}
 }
-
-// 通知所有玩家当前回合操作
-func notifyAllPlayerRoundOpt(room *room.Room) {
-
-}

+ 38 - 3
src/server/game/teen/opt.go

@@ -2,12 +2,17 @@ package teen
 
 import (
 	"fmt"
+	"server/game"
 	"server/game/room"
 	"server/msg"
+	"time"
+
+	"github.com/name5566/leaf/log"
 )
 
 // 看牌
-func lookCard(room *room.Room, sitPos int32) {
+func seen(room *room.Room, sitPos int32) {
+	log.Debug("看牌的位置: %d", room.RoundSitPos)
 	SendRoundMsgToAll(room, &msg.ReqRound{
 		Round:       int32(room.Round),
 		RoundSitPos: int32(sitPos),
@@ -17,23 +22,45 @@ func lookCard(room *room.Room, sitPos int32) {
 }
 
 // 弃牌
-func discardCard(room *room.Room, sitPos int32) {
+func packed(room *room.Room, sitPos int32) {
+	player := room.GetPlayerBySitPos(sitPos)
+	player.IsPacked = true
+	if isOnlyOne, sp := room.IsOnlyOneNotPacked(); isOnlyOne {
+		// 如果所有玩家都弃牌,判定当前座位玩家获胜
+		room.PlayerWin(sp)
+	}
+	room.SetNextRound()
+	log.Debug("弃牌的位置: %d", room.RoundSitPos)
 	SendRoundMsgToAll(room, &msg.ReqRound{
 		Round:       int32(room.Round),
 		RoundSitPos: int32(sitPos),
 		PlayerOpt:   &msg.PlayerOpt{OptType: msg.PlayerOptType_OPT_PACKED},
 		UserId:      room.GetPlayerBySitPos(sitPos).GetUserId(),
 	})
+	checkRoomStatus(room)
 }
 
 // 跟注
-func call(room *room.Room, sitPos int32) {
+func chaal(room *room.Room, sitPos int32) {
+	room.SetNextRound()
 	SendRoundMsgToAll(room, &msg.ReqRound{
 		Round:       int32(room.Round),
 		RoundSitPos: int32(sitPos),
 		PlayerOpt:   &msg.PlayerOpt{OptType: msg.PlayerOptType_OPT_CHAAL},
 		UserId:      room.GetPlayerBySitPos(sitPos).GetUserId(),
 	})
+	checkRoomStatus(room)
+}
+
+// 亮牌
+func show(room *room.Room, sitPos int32) {
+	room.SetNextRound()
+	SendRoundMsgToAll(room, &msg.ReqRound{
+		Round:       int32(room.Round),
+		RoundSitPos: int32(sitPos),
+		PlayerOpt:   &msg.PlayerOpt{OptType: msg.PlayerOptType_OPT_SHOW},
+		UserId:      room.GetPlayerBySitPos(sitPos).GetUserId(),
+	})
 }
 
 // 取消操作超时
@@ -41,3 +68,11 @@ func cancelOptTimeout(player *room.Player, sitPos int32) {
 	eventID := fmt.Sprintf("player_%s_opt_%s", player.Id, sitPos)
 	timerMgr.RemoveEvent(eventID)
 }
+
+// 房间状态检测
+func checkRoomStatus(r *room.Room) {
+	log.Debug("下一个操作的玩家座位:%d", r.RoundSitPos)
+	game.Module.Skeleton.AfterFunc(time.Second*1, func() {
+		notifyPlayerAction(r, int32(r.RoundSitPos), msg.PlayerOptType_OPT_SELECT)
+	})
+}

+ 42 - 0
src/server/game/teen/robot.go

@@ -4,8 +4,10 @@ import (
 	"encoding/json"
 	"math/rand"
 	"os"
+	"server/game"
 	"server/game/room"
 	"server/msg"
+	"time"
 
 	"github.com/name5566/leaf/log"
 )
@@ -49,5 +51,45 @@ func randomIndianHeadImage() string {
 
 // 人机操作
 func robotOpt(r *room.Room, sitPos int32, optType msg.PlayerOptType) {
+	if optType == msg.PlayerOptType_OPT_SELECT {
+		robotSelect(r, sitPos)
+	}
+	// switch optType {
+	// case msg.PlayerOptType_OPT_SELECT:
+	// 	robotSelect(r, sitPos)
+	// case msg.PlayerOptType_OPT_CHAAL:
+	// 	robotChaal(r, sitPos)
+	// case msg.PlayerOptType_OPT_SHOW:
+	// 	robotShow(r, sitPos)
+	// case msg.PlayerOptType_OPT_PACKED:
+	// 	robotPacked(r, sitPos)
+	// }
+}
+
+func robotSelect(r *room.Room, sitPos int32) {
+	log.Debug("robotSelect, sitPos: %d", sitPos)
+	game.Module.Skeleton.AfterFunc(time.Second*1, func() {
+		robotChaal(r, sitPos)
+	})
+
+}
+
+func robotPack(r *room.Room, sitPos int32) {
+
+}
+
+func robotChaal(r *room.Room, sitPos int32) {
+	//获取1-5的随机数
+	chaalAmount := rand.Intn(5) + 1
+	game.Module.Skeleton.AfterFunc(time.Second*time.Duration(chaalAmount), func() {
+		chaal(r, sitPos)
+	})
+}
+
+func robotShow(r *room.Room, sitPos int32) {
+
+}
+
+func robotPacked(r *room.Room, sitPos int32) {
 
 }

+ 18 - 10
src/server/game/teen/round.go

@@ -6,6 +6,8 @@ import (
 	"server/game/room"
 	"server/msg"
 	"time"
+
+	"github.com/name5566/leaf/log"
 )
 
 // 创建定时管理器
@@ -18,6 +20,7 @@ func startDealCard(r *room.Room) {
 	if r.Status == room.RoomStatusWaiting {
 		r.Status = room.RoomStatusDealing
 		dealer := r.GetDealerPlayer()
+		r.RoundSitPos = int(dealer.SitPos)
 		// 找到庄家下家依次发牌
 		for i, player := range r.GetPlayersBySitPos(dealer.SitPos) {
 			player.HandCards = getThreeCards(r)
@@ -70,11 +73,14 @@ func notifyPlayerAction(room *room.Room, sitPos int32, optType msg.PlayerOptType
 		//人机直接操作
 		robotOpt(room, sitPos, optType)
 	}
+	log.Debug("通知玩家开始操作: %d", sitPos)
 
 	players := room.GetPlayersBySitPos(sitPos)
 	for _, p := range players {
 		if p.Agent != nil {
 			p.Agent.WriteMsg(&msg.ReqPlayerAction{SitPos: int32(sitPos), PlayerOpt: &msg.PlayerOpt{OptType: optType}})
+		}
+		if sitPos == p.SitPos && !p.IsRobot {
 			addPlayerOptTimeout(room, player, time.Second*time.Duration(GameConfig.ThinkTime))
 		}
 	}
@@ -88,22 +94,23 @@ func addPlayerOptTimeout(room *room.Room, player *room.Player, timeout time.Dura
 	eventID := fmt.Sprintf("player_%s_opt_%s", player.Id, player.SitPos)
 	timerMgr.AddEvent(eventID, timeout, func() {
 		// 超时处理逻辑,弃牌
-		discardCard(room, player.SitPos)
+		packed(room, player.SitPos)
 	})
 }
 
 // 收到玩家操作
 func recvPlayerOptAction(room *room.Room, sitPos int32, optType msg.PlayerOptType) {
 	player := room.GetPlayerBySitPos(sitPos)
-	if player.Agent != nil {
-		cancelOptTimeout(player, sitPos)
-		if optType == msg.PlayerOptType_OPT_SEEN { // 看牌
-			lookCard(room, sitPos)
-		} else if optType == msg.PlayerOptType_OPT_PACKED { // 弃牌
-			discardCard(room, sitPos)
-		} else if optType == msg.PlayerOptType_OPT_CHAAL { // 跟注
-			call(room, sitPos)
-		}
+	cancelOptTimeout(player, sitPos)
+	log.Debug("recvPlayerOptAction, optType:%v", optType)
+	if optType == msg.PlayerOptType_OPT_SEEN { // 看牌
+		seen(room, sitPos)
+	} else if optType == msg.PlayerOptType_OPT_PACKED { // 弃牌
+		packed(room, sitPos)
+	} else if optType == msg.PlayerOptType_OPT_CHAAL { // 跟注
+		chaal(room, sitPos)
+	} else if optType == msg.PlayerOptType_OPT_SHOW { // 亮牌
+		show(room, sitPos)
 	}
 }
 
@@ -114,4 +121,5 @@ func SendRoundMsgToAll(room *room.Room, msg *msg.ReqRound) {
 			player.Agent.WriteMsg(msg)
 		}
 	}
+	room.AddPlayerOpt(msg)
 }

+ 5 - 1
src/server/game/teen/teen.go

@@ -65,7 +65,6 @@ func createRoom(userId string, roomId string, agent gate.Agent) {
 	//先移除掉除自己之外的机器人
 	userData.Teen_Patti_Room.RemoveAllRobot()
 	sitPosList := randomSitPos(userData.Teen_Patti_Room, random)
-	log.Debug("sitPosList: %v, random: %d", sitPosList, random)
 	for i := 0; i < random; i++ {
 		userData.Teen_Patti_Room.Players = append(userData.Teen_Patti_Room.Players, &room.Player{
 			Id:       fmt.Sprintf("%d", i),
@@ -73,8 +72,13 @@ func createRoom(userId string, roomId string, agent gate.Agent) {
 			IsRobot:  true,
 			UserData: createRobotUserData(),
 			SitPos:   int32(sitPosList[i]),
+			IsPacked: false,
+			IsSeen:   false,
+			IsShow:   false,
+			IsDealer: false,
 		})
 	}
+
 	randomDealer(userData.Teen_Patti_Room)
 }
 

+ 1 - 1
src/server/gate/router.go

@@ -8,8 +8,8 @@ import (
 )
 
 func init() {
-	msg.Processor.SetRouter(&msg.Hello{}, game.ChanRPC)
 	msg.Processor.SetRouter(&msg.ResLogin{}, login.ChanRPC)
 	msg.Processor.SetRouter(&msg.ResGameInfo{}, hall.ChanRPC)
 	msg.Processor.SetRouter(&msg.ResJoinRoom{}, hall.ChanRPC)
+	msg.Processor.SetRouter(&msg.ResPlayerOptAction{}, game.ChanRPC)
 }

+ 83 - 69
src/server/msg/common.pb.go

@@ -1014,6 +1014,7 @@ type PlayerOpt struct {
 	OptType   PlayerOptType `protobuf:"varint,1,opt,name=opt_type,json=optType,proto3,enum=PlayerOptType" json:"opt_type,omitempty"` // 操作类型
 	BetAmount int32         `protobuf:"varint,2,opt,name=bet_amount,json=betAmount,proto3" json:"bet_amount,omitempty"`              // 下注金额
 	Timestamp int64         `protobuf:"varint,3,opt,name=timestamp,proto3" json:"timestamp,omitempty"`                               // 操作时间戳
+	SeenCards []*ReqCard    `protobuf:"bytes,4,rep,name=seenCards,proto3" json:"seenCards,omitempty"`                                // 看牌的牌
 }
 
 func (x *PlayerOpt) Reset() {
@@ -1069,6 +1070,13 @@ func (x *PlayerOpt) GetTimestamp() int64 {
 	return 0
 }
 
+func (x *PlayerOpt) GetSeenCards() []*ReqCard {
+	if x != nil {
+		return x.SeenCards
+	}
+	return nil
+}
+
 type ReqRoom struct {
 	state         protoimpl.MessageState
 	sizeCache     protoimpl.SizeCache
@@ -1210,7 +1218,7 @@ type ReqPlayer struct {
 
 	Id        string     `protobuf:"bytes,1,opt,name=Id,proto3" json:"Id,omitempty"`
 	HandCards []*ReqCard `protobuf:"bytes,2,rep,name=HandCards,proto3" json:"HandCards,omitempty"`
-	SitPos    int32      `protobuf:"varint,3,opt,name=SitPos,proto3" json:"SitPos,omitempty"`
+	SitPos    *int32     `protobuf:"varint,3,opt,name=SitPos,proto3,oneof" json:"SitPos,omitempty"`
 	IsDealer  bool       `protobuf:"varint,4,opt,name=IsDealer,proto3" json:"IsDealer,omitempty"`
 }
 
@@ -1261,8 +1269,8 @@ func (x *ReqPlayer) GetHandCards() []*ReqCard {
 }
 
 func (x *ReqPlayer) GetSitPos() int32 {
-	if x != nil {
-		return x.SitPos
+	if x != nil && x.SitPos != nil {
+		return *x.SitPos
 	}
 	return 0
 }
@@ -1590,62 +1598,66 @@ var file_common_proto_rawDesc = []byte{
 	0x09, 0x52, 0x06, 0x72, 0x6f, 0x6f, 0x6d, 0x49, 0x64, 0x12, 0x16, 0x0a, 0x06, 0x67, 0x61, 0x6d,
 	0x65, 0x49, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x67, 0x61, 0x6d, 0x65, 0x49,
 	0x64, 0x12, 0x16, 0x0a, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28,
-	0x09, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x22, 0x73, 0x0a, 0x09, 0x50, 0x6c, 0x61,
-	0x79, 0x65, 0x72, 0x4f, 0x70, 0x74, 0x12, 0x29, 0x0a, 0x08, 0x6f, 0x70, 0x74, 0x5f, 0x74, 0x79,
-	0x70, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x0e, 0x2e, 0x50, 0x6c, 0x61, 0x79, 0x65,
-	0x72, 0x4f, 0x70, 0x74, 0x54, 0x79, 0x70, 0x65, 0x52, 0x07, 0x6f, 0x70, 0x74, 0x54, 0x79, 0x70,
-	0x65, 0x12, 0x1d, 0x0a, 0x0a, 0x62, 0x65, 0x74, 0x5f, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x18,
-	0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x09, 0x62, 0x65, 0x74, 0x41, 0x6d, 0x6f, 0x75, 0x6e, 0x74,
-	0x12, 0x1c, 0x0a, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x18, 0x03, 0x20,
-	0x01, 0x28, 0x03, 0x52, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x22, 0xa0,
-	0x01, 0x0a, 0x07, 0x52, 0x65, 0x71, 0x52, 0x6f, 0x6f, 0x6d, 0x12, 0x0e, 0x0a, 0x02, 0x49, 0x64,
-	0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x49, 0x64, 0x12, 0x2a, 0x0a, 0x0a, 0x52, 0x65,
-	0x71, 0x50, 0x6c, 0x61, 0x79, 0x65, 0x72, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0a,
-	0x2e, 0x52, 0x65, 0x71, 0x50, 0x6c, 0x61, 0x79, 0x65, 0x72, 0x52, 0x0a, 0x52, 0x65, 0x71, 0x50,
-	0x6c, 0x61, 0x79, 0x65, 0x72, 0x73, 0x12, 0x16, 0x0a, 0x06, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73,
-	0x18, 0x03, 0x20, 0x01, 0x28, 0x05, 0x52, 0x06, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x14,
-	0x0a, 0x05, 0x52, 0x6f, 0x75, 0x6e, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x05, 0x52, 0x05, 0x52,
-	0x6f, 0x75, 0x6e, 0x64, 0x12, 0x2b, 0x0a, 0x09, 0x47, 0x61, 0x6d, 0x65, 0x52, 0x6f, 0x75, 0x6e,
-	0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0d, 0x2e, 0x52, 0x65, 0x71, 0x47, 0x61, 0x6d,
-	0x65, 0x52, 0x6f, 0x75, 0x6e, 0x64, 0x52, 0x09, 0x47, 0x61, 0x6d, 0x65, 0x52, 0x6f, 0x75, 0x6e,
-	0x64, 0x22, 0x35, 0x0a, 0x07, 0x52, 0x65, 0x71, 0x43, 0x61, 0x72, 0x64, 0x12, 0x14, 0x0a, 0x05,
-	0x43, 0x6f, 0x6c, 0x6f, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, 0x05, 0x43, 0x6f, 0x6c,
-	0x6f, 0x72, 0x12, 0x14, 0x0a, 0x05, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28,
-	0x05, 0x52, 0x05, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x22, 0x77, 0x0a, 0x09, 0x52, 0x65, 0x71, 0x50,
-	0x6c, 0x61, 0x79, 0x65, 0x72, 0x12, 0x0e, 0x0a, 0x02, 0x49, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28,
-	0x09, 0x52, 0x02, 0x49, 0x64, 0x12, 0x26, 0x0a, 0x09, 0x48, 0x61, 0x6e, 0x64, 0x43, 0x61, 0x72,
-	0x64, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x08, 0x2e, 0x52, 0x65, 0x71, 0x43, 0x61,
-	0x72, 0x64, 0x52, 0x09, 0x48, 0x61, 0x6e, 0x64, 0x43, 0x61, 0x72, 0x64, 0x73, 0x12, 0x16, 0x0a,
-	0x06, 0x53, 0x69, 0x74, 0x50, 0x6f, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x05, 0x52, 0x06, 0x53,
-	0x69, 0x74, 0x50, 0x6f, 0x73, 0x12, 0x1a, 0x0a, 0x08, 0x49, 0x73, 0x44, 0x65, 0x61, 0x6c, 0x65,
-	0x72, 0x18, 0x04, 0x20, 0x01, 0x28, 0x08, 0x52, 0x08, 0x49, 0x73, 0x44, 0x65, 0x61, 0x6c, 0x65,
-	0x72, 0x22, 0x31, 0x0a, 0x0c, 0x52, 0x65, 0x71, 0x47, 0x61, 0x6d, 0x65, 0x52, 0x6f, 0x75, 0x6e,
-	0x64, 0x12, 0x21, 0x0a, 0x06, 0x52, 0x6f, 0x75, 0x6e, 0x64, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28,
-	0x0b, 0x32, 0x09, 0x2e, 0x52, 0x65, 0x71, 0x52, 0x6f, 0x75, 0x6e, 0x64, 0x52, 0x06, 0x52, 0x6f,
-	0x75, 0x6e, 0x64, 0x73, 0x22, 0x84, 0x01, 0x0a, 0x08, 0x52, 0x65, 0x71, 0x52, 0x6f, 0x75, 0x6e,
-	0x64, 0x12, 0x14, 0x0a, 0x05, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05,
-	0x52, 0x05, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x12, 0x20, 0x0a, 0x0b, 0x72, 0x6f, 0x75, 0x6e, 0x64,
-	0x53, 0x69, 0x74, 0x50, 0x6f, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0b, 0x72, 0x6f,
-	0x75, 0x6e, 0x64, 0x53, 0x69, 0x74, 0x50, 0x6f, 0x73, 0x12, 0x28, 0x0a, 0x09, 0x70, 0x6c, 0x61,
-	0x79, 0x65, 0x72, 0x4f, 0x70, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0a, 0x2e, 0x50,
-	0x6c, 0x61, 0x79, 0x65, 0x72, 0x4f, 0x70, 0x74, 0x52, 0x09, 0x70, 0x6c, 0x61, 0x79, 0x65, 0x72,
-	0x4f, 0x70, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x18, 0x04, 0x20,
-	0x01, 0x28, 0x09, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x22, 0x46, 0x0a, 0x08, 0x4d,
-	0x73, 0x67, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x12, 0x1d, 0x0a, 0x0a, 0x65, 0x72, 0x72, 0x6f, 0x72,
-	0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, 0x09, 0x65, 0x72, 0x72,
-	0x6f, 0x72, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x1b, 0x0a, 0x09, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x5f,
-	0x6d, 0x73, 0x67, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x65, 0x72, 0x72, 0x6f, 0x72,
-	0x4d, 0x73, 0x67, 0x22, 0x1b, 0x0a, 0x05, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x12, 0x12, 0x0a, 0x04,
-	0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65,
-	0x2a, 0x76, 0x0a, 0x0d, 0x50, 0x6c, 0x61, 0x79, 0x65, 0x72, 0x4f, 0x70, 0x74, 0x54, 0x79, 0x70,
-	0x65, 0x12, 0x0c, 0x0a, 0x08, 0x4f, 0x50, 0x54, 0x5f, 0x4e, 0x4f, 0x4e, 0x45, 0x10, 0x00, 0x12,
-	0x0c, 0x0a, 0x08, 0x4f, 0x50, 0x54, 0x5f, 0x53, 0x45, 0x45, 0x4e, 0x10, 0x01, 0x12, 0x0e, 0x0a,
-	0x0a, 0x4f, 0x50, 0x54, 0x5f, 0x50, 0x41, 0x43, 0x4b, 0x45, 0x44, 0x10, 0x02, 0x12, 0x0d, 0x0a,
-	0x09, 0x4f, 0x50, 0x54, 0x5f, 0x43, 0x48, 0x41, 0x41, 0x4c, 0x10, 0x03, 0x12, 0x0c, 0x0a, 0x08,
-	0x4f, 0x50, 0x54, 0x5f, 0x42, 0x49, 0x4e, 0x44, 0x10, 0x04, 0x12, 0x0e, 0x0a, 0x0a, 0x4f, 0x50,
-	0x54, 0x5f, 0x53, 0x45, 0x4c, 0x45, 0x43, 0x54, 0x10, 0x05, 0x12, 0x0c, 0x0a, 0x08, 0x4f, 0x50,
-	0x54, 0x5f, 0x53, 0x48, 0x4f, 0x57, 0x10, 0x06, 0x42, 0x07, 0x5a, 0x05, 0x2e, 0x2f, 0x6d, 0x73,
-	0x67, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
+	0x09, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x22, 0x9b, 0x01, 0x0a, 0x09, 0x50, 0x6c,
+	0x61, 0x79, 0x65, 0x72, 0x4f, 0x70, 0x74, 0x12, 0x29, 0x0a, 0x08, 0x6f, 0x70, 0x74, 0x5f, 0x74,
+	0x79, 0x70, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x0e, 0x2e, 0x50, 0x6c, 0x61, 0x79,
+	0x65, 0x72, 0x4f, 0x70, 0x74, 0x54, 0x79, 0x70, 0x65, 0x52, 0x07, 0x6f, 0x70, 0x74, 0x54, 0x79,
+	0x70, 0x65, 0x12, 0x1d, 0x0a, 0x0a, 0x62, 0x65, 0x74, 0x5f, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74,
+	0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x09, 0x62, 0x65, 0x74, 0x41, 0x6d, 0x6f, 0x75, 0x6e,
+	0x74, 0x12, 0x1c, 0x0a, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x18, 0x03,
+	0x20, 0x01, 0x28, 0x03, 0x52, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x12,
+	0x26, 0x0a, 0x09, 0x73, 0x65, 0x65, 0x6e, 0x43, 0x61, 0x72, 0x64, 0x73, 0x18, 0x04, 0x20, 0x03,
+	0x28, 0x0b, 0x32, 0x08, 0x2e, 0x52, 0x65, 0x71, 0x43, 0x61, 0x72, 0x64, 0x52, 0x09, 0x73, 0x65,
+	0x65, 0x6e, 0x43, 0x61, 0x72, 0x64, 0x73, 0x22, 0xa0, 0x01, 0x0a, 0x07, 0x52, 0x65, 0x71, 0x52,
+	0x6f, 0x6f, 0x6d, 0x12, 0x0e, 0x0a, 0x02, 0x49, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52,
+	0x02, 0x49, 0x64, 0x12, 0x2a, 0x0a, 0x0a, 0x52, 0x65, 0x71, 0x50, 0x6c, 0x61, 0x79, 0x65, 0x72,
+	0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0a, 0x2e, 0x52, 0x65, 0x71, 0x50, 0x6c, 0x61,
+	0x79, 0x65, 0x72, 0x52, 0x0a, 0x52, 0x65, 0x71, 0x50, 0x6c, 0x61, 0x79, 0x65, 0x72, 0x73, 0x12,
+	0x16, 0x0a, 0x06, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x05, 0x52,
+	0x06, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x14, 0x0a, 0x05, 0x52, 0x6f, 0x75, 0x6e, 0x64,
+	0x18, 0x04, 0x20, 0x01, 0x28, 0x05, 0x52, 0x05, 0x52, 0x6f, 0x75, 0x6e, 0x64, 0x12, 0x2b, 0x0a,
+	0x09, 0x47, 0x61, 0x6d, 0x65, 0x52, 0x6f, 0x75, 0x6e, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b,
+	0x32, 0x0d, 0x2e, 0x52, 0x65, 0x71, 0x47, 0x61, 0x6d, 0x65, 0x52, 0x6f, 0x75, 0x6e, 0x64, 0x52,
+	0x09, 0x47, 0x61, 0x6d, 0x65, 0x52, 0x6f, 0x75, 0x6e, 0x64, 0x22, 0x35, 0x0a, 0x07, 0x52, 0x65,
+	0x71, 0x43, 0x61, 0x72, 0x64, 0x12, 0x14, 0x0a, 0x05, 0x43, 0x6f, 0x6c, 0x6f, 0x72, 0x18, 0x01,
+	0x20, 0x01, 0x28, 0x05, 0x52, 0x05, 0x43, 0x6f, 0x6c, 0x6f, 0x72, 0x12, 0x14, 0x0a, 0x05, 0x50,
+	0x6f, 0x69, 0x6e, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x05, 0x50, 0x6f, 0x69, 0x6e,
+	0x74, 0x22, 0x87, 0x01, 0x0a, 0x09, 0x52, 0x65, 0x71, 0x50, 0x6c, 0x61, 0x79, 0x65, 0x72, 0x12,
+	0x0e, 0x0a, 0x02, 0x49, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x49, 0x64, 0x12,
+	0x26, 0x0a, 0x09, 0x48, 0x61, 0x6e, 0x64, 0x43, 0x61, 0x72, 0x64, 0x73, 0x18, 0x02, 0x20, 0x03,
+	0x28, 0x0b, 0x32, 0x08, 0x2e, 0x52, 0x65, 0x71, 0x43, 0x61, 0x72, 0x64, 0x52, 0x09, 0x48, 0x61,
+	0x6e, 0x64, 0x43, 0x61, 0x72, 0x64, 0x73, 0x12, 0x1b, 0x0a, 0x06, 0x53, 0x69, 0x74, 0x50, 0x6f,
+	0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x05, 0x48, 0x00, 0x52, 0x06, 0x53, 0x69, 0x74, 0x50, 0x6f,
+	0x73, 0x88, 0x01, 0x01, 0x12, 0x1a, 0x0a, 0x08, 0x49, 0x73, 0x44, 0x65, 0x61, 0x6c, 0x65, 0x72,
+	0x18, 0x04, 0x20, 0x01, 0x28, 0x08, 0x52, 0x08, 0x49, 0x73, 0x44, 0x65, 0x61, 0x6c, 0x65, 0x72,
+	0x42, 0x09, 0x0a, 0x07, 0x5f, 0x53, 0x69, 0x74, 0x50, 0x6f, 0x73, 0x22, 0x31, 0x0a, 0x0c, 0x52,
+	0x65, 0x71, 0x47, 0x61, 0x6d, 0x65, 0x52, 0x6f, 0x75, 0x6e, 0x64, 0x12, 0x21, 0x0a, 0x06, 0x52,
+	0x6f, 0x75, 0x6e, 0x64, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x09, 0x2e, 0x52, 0x65,
+	0x71, 0x52, 0x6f, 0x75, 0x6e, 0x64, 0x52, 0x06, 0x52, 0x6f, 0x75, 0x6e, 0x64, 0x73, 0x22, 0x84,
+	0x01, 0x0a, 0x08, 0x52, 0x65, 0x71, 0x52, 0x6f, 0x75, 0x6e, 0x64, 0x12, 0x14, 0x0a, 0x05, 0x72,
+	0x6f, 0x75, 0x6e, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, 0x05, 0x72, 0x6f, 0x75, 0x6e,
+	0x64, 0x12, 0x20, 0x0a, 0x0b, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x53, 0x69, 0x74, 0x50, 0x6f, 0x73,
+	0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0b, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x53, 0x69, 0x74,
+	0x50, 0x6f, 0x73, 0x12, 0x28, 0x0a, 0x09, 0x70, 0x6c, 0x61, 0x79, 0x65, 0x72, 0x4f, 0x70, 0x74,
+	0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0a, 0x2e, 0x50, 0x6c, 0x61, 0x79, 0x65, 0x72, 0x4f,
+	0x70, 0x74, 0x52, 0x09, 0x70, 0x6c, 0x61, 0x79, 0x65, 0x72, 0x4f, 0x70, 0x74, 0x12, 0x16, 0x0a,
+	0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x75,
+	0x73, 0x65, 0x72, 0x49, 0x64, 0x22, 0x46, 0x0a, 0x08, 0x4d, 0x73, 0x67, 0x45, 0x72, 0x72, 0x6f,
+	0x72, 0x12, 0x1d, 0x0a, 0x0a, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18,
+	0x01, 0x20, 0x01, 0x28, 0x05, 0x52, 0x09, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x43, 0x6f, 0x64, 0x65,
+	0x12, 0x1b, 0x0a, 0x09, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x5f, 0x6d, 0x73, 0x67, 0x18, 0x02, 0x20,
+	0x01, 0x28, 0x09, 0x52, 0x08, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x4d, 0x73, 0x67, 0x22, 0x1b, 0x0a,
+	0x05, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01,
+	0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x2a, 0x76, 0x0a, 0x0d, 0x50, 0x6c,
+	0x61, 0x79, 0x65, 0x72, 0x4f, 0x70, 0x74, 0x54, 0x79, 0x70, 0x65, 0x12, 0x0c, 0x0a, 0x08, 0x4f,
+	0x50, 0x54, 0x5f, 0x4e, 0x4f, 0x4e, 0x45, 0x10, 0x00, 0x12, 0x0c, 0x0a, 0x08, 0x4f, 0x50, 0x54,
+	0x5f, 0x53, 0x45, 0x45, 0x4e, 0x10, 0x01, 0x12, 0x0e, 0x0a, 0x0a, 0x4f, 0x50, 0x54, 0x5f, 0x50,
+	0x41, 0x43, 0x4b, 0x45, 0x44, 0x10, 0x02, 0x12, 0x0d, 0x0a, 0x09, 0x4f, 0x50, 0x54, 0x5f, 0x43,
+	0x48, 0x41, 0x41, 0x4c, 0x10, 0x03, 0x12, 0x0c, 0x0a, 0x08, 0x4f, 0x50, 0x54, 0x5f, 0x42, 0x49,
+	0x4e, 0x44, 0x10, 0x04, 0x12, 0x0e, 0x0a, 0x0a, 0x4f, 0x50, 0x54, 0x5f, 0x53, 0x45, 0x4c, 0x45,
+	0x43, 0x54, 0x10, 0x05, 0x12, 0x0c, 0x0a, 0x08, 0x4f, 0x50, 0x54, 0x5f, 0x53, 0x48, 0x4f, 0x57,
+	0x10, 0x06, 0x42, 0x07, 0x5a, 0x05, 0x2e, 0x2f, 0x6d, 0x73, 0x67, 0x62, 0x06, 0x70, 0x72, 0x6f,
+	0x74, 0x6f, 0x33,
 }
 
 var (
@@ -1696,16 +1708,17 @@ var file_common_proto_depIdxs = []int32{
 	16, // 4: ReqPlayerAction.playerOpt:type_name -> PlayerOpt
 	16, // 5: ResPlayerOptAction.playerOpt:type_name -> PlayerOpt
 	0,  // 6: PlayerOpt.opt_type:type_name -> PlayerOptType
-	19, // 7: ReqRoom.ReqPlayers:type_name -> ReqPlayer
-	20, // 8: ReqRoom.GameRound:type_name -> ReqGameRound
-	18, // 9: ReqPlayer.HandCards:type_name -> ReqCard
-	21, // 10: ReqGameRound.Rounds:type_name -> ReqRound
-	16, // 11: ReqRound.playerOpt:type_name -> PlayerOpt
-	12, // [12:12] is the sub-list for method output_type
-	12, // [12:12] is the sub-list for method input_type
-	12, // [12:12] is the sub-list for extension type_name
-	12, // [12:12] is the sub-list for extension extendee
-	0,  // [0:12] is the sub-list for field type_name
+	18, // 7: PlayerOpt.seenCards:type_name -> ReqCard
+	19, // 8: ReqRoom.ReqPlayers:type_name -> ReqPlayer
+	20, // 9: ReqRoom.GameRound:type_name -> ReqGameRound
+	18, // 10: ReqPlayer.HandCards:type_name -> ReqCard
+	21, // 11: ReqGameRound.Rounds:type_name -> ReqRound
+	16, // 12: ReqRound.playerOpt:type_name -> PlayerOpt
+	13, // [13:13] is the sub-list for method output_type
+	13, // [13:13] is the sub-list for method input_type
+	13, // [13:13] is the sub-list for extension type_name
+	13, // [13:13] is the sub-list for extension extendee
+	0,  // [0:13] is the sub-list for field type_name
 }
 
 func init() { file_common_proto_init() }
@@ -1991,6 +2004,7 @@ func file_common_proto_init() {
 			}
 		}
 	}
+	file_common_proto_msgTypes[18].OneofWrappers = []interface{}{}
 	type x struct{}
 	out := protoimpl.TypeBuilder{
 		File: protoimpl.DescBuilder{