From 0d95077d5bfadbbe8cf498d200967e32bb2b9c95 Mon Sep 17 00:00:00 2001 From: tangmingyou Date: Thu, 23 Mar 2023 18:00:33 +0800 Subject: [PATCH] offline delay auto betting --- api/poker.pb.go | 85 ++++++++++++----------- api/poker.proto | 1 + api/proto.js | 46 ++++++------- internal/game/actions.go | 6 ++ internal/game/player.go | 19 ++++-- internal/game/table.go | 36 ++++++++-- internal/letter/proto_writer.go | 3 +- internal/server/ws.go | 31 ++++----- internal/service/auth.go | 6 +- internal/service/event/auto_betting.go | 36 ++++++++++ internal/service/event/offline.go | 58 ++++++++++++++++ internal/service/event/online.go | 27 ++++++++ internal/service/online.go | 37 ---------- internal/service/store/store.go | 79 ++++++++++++---------- internal/service/table.go | 94 +++++++++++++------------- internal/session/net_account.go | 7 ++ internal/session/net_client.go | 12 ++++ internal/session/player.go | 19 ------ tool/watcher/watcher.go | 47 +++++++++++++ 19 files changed, 416 insertions(+), 233 deletions(-) create mode 100644 internal/service/event/auto_betting.go create mode 100644 internal/service/event/offline.go create mode 100644 internal/service/event/online.go delete mode 100644 internal/service/online.go delete mode 100644 internal/session/player.go create mode 100644 tool/watcher/watcher.go diff --git a/api/poker.pb.go b/api/poker.pb.go index 023d71a..9cd8f1d 100644 --- a/api/poker.pb.go +++ b/api/poker.pb.go @@ -250,8 +250,8 @@ type ResFail struct { sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields - Code int32 `protobuf:"varint,1,opt,name=code,proto3" json:"code,omitempty"` - Msg string `protobuf:"bytes,3,opt,name=msg,proto3" json:"msg,omitempty"` // 失败消息 + Code int32 `protobuf:"varint,1,opt,name=code,proto3" json:"code,omitempty"` // 401跳登录,402未加入牌桌,403挤号 + Msg string `protobuf:"bytes,3,opt,name=msg,proto3" json:"msg,omitempty"` // 失败消息 } func (x *ResFail) Reset() { @@ -1264,7 +1264,6 @@ type TablePlayer struct { Avatar string `protobuf:"bytes,4,opt,name=avatar,proto3" json:"avatar,omitempty"` Chip int32 `protobuf:"varint,5,opt,name=chip,proto3" json:"chip,omitempty"` // 筹码 Status int32 `protobuf:"varint,6,opt,name=status,proto3" json:"status,omitempty"` // 玩家状态: 1待准备开始,2已准备开始,已开始(3等待其他玩家动作,4待大盲注,5待小盲注,6待跟注,7已弃) - LastStatus int32 `protobuf:"varint,7,opt,name=lastStatus,proto3" json:"lastStatus,omitempty"` // TODO 删, 前端通过消息监听获取下注金额信息 Master bool `protobuf:"varint,8,opt,name=master,proto3" json:"master,omitempty"` // 是否房主 RoundBetTimes int32 `protobuf:"varint,11,opt,name=roundBetTimes,proto3" json:"roundBetTimes,omitempty"` // 该局下注次数 TotalBetChip int32 `protobuf:"varint,13,opt,name=totalBetChip,proto3" json:"totalBetChip,omitempty"` // 该局牌已下注筹码 @@ -1347,13 +1346,6 @@ func (x *TablePlayer) GetStatus() int32 { return 0 } -func (x *TablePlayer) GetLastStatus() int32 { - if x != nil { - return x.LastStatus - } - return 0 -} - func (x *TablePlayer) GetMaster() bool { if x != nil { return x.Master @@ -1700,8 +1692,9 @@ type ReqBetting struct { sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields - BetType int32 `protobuf:"varint,1,opt,name=betType,proto3" json:"betType,omitempty"` // 1跟注,2加注或跟注(-[0]+),3All-In,4弃牌,5过牌 - BetChip int32 `protobuf:"varint,2,opt,name=betChip,proto3" json:"betChip,omitempty"` // 跟注/加注金额 + BetType int32 `protobuf:"varint,1,opt,name=betType,proto3" json:"betType,omitempty"` // 1跟注,2加注或跟注(-[0]+),3All-In,4弃牌,5过牌 + BetChip int32 `protobuf:"varint,2,opt,name=betChip,proto3" json:"betChip,omitempty"` // 跟注/加注金额 + Operator int32 `protobuf:"varint,3,opt,name=operator,proto3" json:"operator,omitempty"` // 0玩家操作,1自动操作 } func (x *ReqBetting) Reset() { @@ -1750,6 +1743,13 @@ func (x *ReqBetting) GetBetChip() int32 { return 0 } +func (x *ReqBetting) GetOperator() int32 { + if x != nil { + return x.Operator + } + return 0 +} + // 通知玩家某个其他玩家动作 type ResNoticePlayerLine struct { state protoimpl.MessageState @@ -2059,7 +2059,7 @@ var file_poker_proto_rawDesc = []byte{ 0x0a, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x43, 0x61, 0x72, 0x64, 0x12, 0x2a, 0x0a, 0x07, 0x70, 0x6c, 0x61, 0x79, 0x65, 0x72, 0x73, 0x18, 0x0f, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x10, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x54, 0x61, 0x62, 0x6c, 0x65, 0x50, 0x6c, 0x61, 0x79, 0x65, 0x72, 0x52, 0x07, - 0x70, 0x6c, 0x61, 0x79, 0x65, 0x72, 0x73, 0x22, 0x8f, 0x03, 0x0a, 0x0b, 0x54, 0x61, 0x62, 0x6c, + 0x70, 0x6c, 0x61, 0x79, 0x65, 0x72, 0x73, 0x22, 0xef, 0x02, 0x0a, 0x0b, 0x54, 0x61, 0x62, 0x6c, 0x65, 0x50, 0x6c, 0x61, 0x79, 0x65, 0x72, 0x12, 0x14, 0x0a, 0x05, 0x72, 0x6f, 0x62, 0x6f, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x05, 0x72, 0x6f, 0x62, 0x6f, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x02, 0x69, 0x64, 0x12, 0x1a, 0x0a, @@ -2068,9 +2068,7 @@ var file_poker_proto_rawDesc = []byte{ 0x74, 0x61, 0x72, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x61, 0x76, 0x61, 0x74, 0x61, 0x72, 0x12, 0x12, 0x0a, 0x04, 0x63, 0x68, 0x69, 0x70, 0x18, 0x05, 0x20, 0x01, 0x28, 0x05, 0x52, 0x04, 0x63, 0x68, 0x69, 0x70, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, - 0x06, 0x20, 0x01, 0x28, 0x05, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x1e, 0x0a, - 0x0a, 0x6c, 0x61, 0x73, 0x74, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x07, 0x20, 0x01, 0x28, - 0x05, 0x52, 0x0a, 0x6c, 0x61, 0x73, 0x74, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x16, 0x0a, + 0x06, 0x20, 0x01, 0x28, 0x05, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x16, 0x0a, 0x06, 0x6d, 0x61, 0x73, 0x74, 0x65, 0x72, 0x18, 0x08, 0x20, 0x01, 0x28, 0x08, 0x52, 0x06, 0x6d, 0x61, 0x73, 0x74, 0x65, 0x72, 0x12, 0x24, 0x0a, 0x0d, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x42, 0x65, 0x74, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0d, 0x72, 0x6f, @@ -2101,36 +2099,37 @@ var file_poker_proto_rawDesc = []byte{ 0x71, 0x47, 0x61, 0x6d, 0x65, 0x53, 0x74, 0x61, 0x72, 0x74, 0x22, 0x15, 0x0a, 0x13, 0x52, 0x65, 0x71, 0x44, 0x69, 0x73, 0x6d, 0x69, 0x73, 0x73, 0x47, 0x61, 0x6d, 0x65, 0x54, 0x61, 0x62, 0x6c, 0x65, 0x22, 0x15, 0x0a, 0x13, 0x52, 0x65, 0x73, 0x44, 0x69, 0x73, 0x6d, 0x69, 0x73, 0x73, 0x47, - 0x61, 0x6d, 0x65, 0x54, 0x61, 0x62, 0x6c, 0x65, 0x22, 0x40, 0x0a, 0x0a, 0x52, 0x65, 0x71, 0x42, + 0x61, 0x6d, 0x65, 0x54, 0x61, 0x62, 0x6c, 0x65, 0x22, 0x5c, 0x0a, 0x0a, 0x52, 0x65, 0x71, 0x42, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x12, 0x18, 0x0a, 0x07, 0x62, 0x65, 0x74, 0x54, 0x79, 0x70, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, 0x07, 0x62, 0x65, 0x74, 0x54, 0x79, 0x70, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x62, 0x65, 0x74, 0x43, 0x68, 0x69, 0x70, 0x18, 0x02, 0x20, 0x01, 0x28, - 0x05, 0x52, 0x07, 0x62, 0x65, 0x74, 0x43, 0x68, 0x69, 0x70, 0x22, 0x73, 0x0a, 0x13, 0x52, 0x65, - 0x73, 0x4e, 0x6f, 0x74, 0x69, 0x63, 0x65, 0x50, 0x6c, 0x61, 0x79, 0x65, 0x72, 0x4c, 0x69, 0x6e, - 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x70, 0x6c, 0x61, 0x79, 0x65, 0x72, 0x49, 0x64, 0x18, 0x01, 0x20, - 0x01, 0x28, 0x03, 0x52, 0x08, 0x70, 0x6c, 0x61, 0x79, 0x65, 0x72, 0x49, 0x64, 0x12, 0x14, 0x0a, - 0x05, 0x6c, 0x69, 0x6e, 0x65, 0x31, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x6c, 0x69, - 0x6e, 0x65, 0x31, 0x12, 0x14, 0x0a, 0x05, 0x6c, 0x69, 0x6e, 0x65, 0x32, 0x18, 0x0c, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x05, 0x6c, 0x69, 0x6e, 0x65, 0x32, 0x12, 0x14, 0x0a, 0x05, 0x6c, 0x69, 0x6e, - 0x65, 0x33, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x6c, 0x69, 0x6e, 0x65, 0x33, 0x22, - 0x3e, 0x0a, 0x10, 0x52, 0x65, 0x73, 0x47, 0x61, 0x6d, 0x65, 0x45, 0x6e, 0x64, 0x53, 0x65, 0x74, - 0x74, 0x6c, 0x65, 0x12, 0x2a, 0x0a, 0x04, 0x67, 0x61, 0x6d, 0x65, 0x18, 0x0b, 0x20, 0x01, 0x28, - 0x0b, 0x32, 0x16, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x52, 0x65, 0x73, 0x47, 0x61, 0x6d, 0x65, 0x46, - 0x75, 0x6c, 0x6c, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x04, 0x67, 0x61, 0x6d, 0x65, 0x22, - 0x92, 0x01, 0x0a, 0x11, 0x52, 0x65, 0x73, 0x43, 0x61, 0x6c, 0x63, 0x57, 0x69, 0x6e, 0x6e, 0x65, - 0x72, 0x43, 0x68, 0x69, 0x70, 0x12, 0x40, 0x0a, 0x08, 0x77, 0x69, 0x6e, 0x73, 0x43, 0x68, 0x69, - 0x70, 0x18, 0x09, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x24, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x52, 0x65, - 0x73, 0x43, 0x61, 0x6c, 0x63, 0x57, 0x69, 0x6e, 0x6e, 0x65, 0x72, 0x43, 0x68, 0x69, 0x70, 0x2e, - 0x57, 0x69, 0x6e, 0x73, 0x43, 0x68, 0x69, 0x70, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x08, 0x77, - 0x69, 0x6e, 0x73, 0x43, 0x68, 0x69, 0x70, 0x1a, 0x3b, 0x0a, 0x0d, 0x57, 0x69, 0x6e, 0x73, 0x43, - 0x68, 0x69, 0x70, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, - 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, - 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, - 0x3a, 0x02, 0x38, 0x01, 0x22, 0x28, 0x0a, 0x10, 0x52, 0x65, 0x73, 0x47, 0x61, 0x6d, 0x65, 0x4e, - 0x65, 0x78, 0x74, 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, 0x42, 0x0b, - 0x5a, 0x09, 0x2e, 0x2f, 0x61, 0x70, 0x69, 0x3b, 0x61, 0x70, 0x69, 0x62, 0x06, 0x70, 0x72, 0x6f, - 0x74, 0x6f, 0x33, + 0x05, 0x52, 0x07, 0x62, 0x65, 0x74, 0x43, 0x68, 0x69, 0x70, 0x12, 0x1a, 0x0a, 0x08, 0x6f, 0x70, + 0x65, 0x72, 0x61, 0x74, 0x6f, 0x72, 0x18, 0x03, 0x20, 0x01, 0x28, 0x05, 0x52, 0x08, 0x6f, 0x70, + 0x65, 0x72, 0x61, 0x74, 0x6f, 0x72, 0x22, 0x73, 0x0a, 0x13, 0x52, 0x65, 0x73, 0x4e, 0x6f, 0x74, + 0x69, 0x63, 0x65, 0x50, 0x6c, 0x61, 0x79, 0x65, 0x72, 0x4c, 0x69, 0x6e, 0x65, 0x12, 0x1a, 0x0a, + 0x08, 0x70, 0x6c, 0x61, 0x79, 0x65, 0x72, 0x49, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, + 0x08, 0x70, 0x6c, 0x61, 0x79, 0x65, 0x72, 0x49, 0x64, 0x12, 0x14, 0x0a, 0x05, 0x6c, 0x69, 0x6e, + 0x65, 0x31, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x6c, 0x69, 0x6e, 0x65, 0x31, 0x12, + 0x14, 0x0a, 0x05, 0x6c, 0x69, 0x6e, 0x65, 0x32, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, + 0x6c, 0x69, 0x6e, 0x65, 0x32, 0x12, 0x14, 0x0a, 0x05, 0x6c, 0x69, 0x6e, 0x65, 0x33, 0x18, 0x0d, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x6c, 0x69, 0x6e, 0x65, 0x33, 0x22, 0x3e, 0x0a, 0x10, 0x52, + 0x65, 0x73, 0x47, 0x61, 0x6d, 0x65, 0x45, 0x6e, 0x64, 0x53, 0x65, 0x74, 0x74, 0x6c, 0x65, 0x12, + 0x2a, 0x0a, 0x04, 0x67, 0x61, 0x6d, 0x65, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x16, 0x2e, + 0x61, 0x70, 0x69, 0x2e, 0x52, 0x65, 0x73, 0x47, 0x61, 0x6d, 0x65, 0x46, 0x75, 0x6c, 0x6c, 0x53, + 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x04, 0x67, 0x61, 0x6d, 0x65, 0x22, 0x92, 0x01, 0x0a, 0x11, + 0x52, 0x65, 0x73, 0x43, 0x61, 0x6c, 0x63, 0x57, 0x69, 0x6e, 0x6e, 0x65, 0x72, 0x43, 0x68, 0x69, + 0x70, 0x12, 0x40, 0x0a, 0x08, 0x77, 0x69, 0x6e, 0x73, 0x43, 0x68, 0x69, 0x70, 0x18, 0x09, 0x20, + 0x03, 0x28, 0x0b, 0x32, 0x24, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x52, 0x65, 0x73, 0x43, 0x61, 0x6c, + 0x63, 0x57, 0x69, 0x6e, 0x6e, 0x65, 0x72, 0x43, 0x68, 0x69, 0x70, 0x2e, 0x57, 0x69, 0x6e, 0x73, + 0x43, 0x68, 0x69, 0x70, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x08, 0x77, 0x69, 0x6e, 0x73, 0x43, + 0x68, 0x69, 0x70, 0x1a, 0x3b, 0x0a, 0x0d, 0x57, 0x69, 0x6e, 0x73, 0x43, 0x68, 0x69, 0x70, 0x45, + 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x03, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, + 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, + 0x22, 0x28, 0x0a, 0x10, 0x52, 0x65, 0x73, 0x47, 0x61, 0x6d, 0x65, 0x4e, 0x65, 0x78, 0x74, 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, 0x42, 0x0b, 0x5a, 0x09, 0x2e, 0x2f, + 0x61, 0x70, 0x69, 0x3b, 0x61, 0x70, 0x69, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( diff --git a/api/poker.proto b/api/poker.proto index 61f75eb..5c27f03 100644 --- a/api/poker.proto +++ b/api/poker.proto @@ -191,6 +191,7 @@ message ResDismissGameTable { message ReqBetting { int32 betType = 1; // 1跟注,2加注或跟注(-[0]+),3All-In,4弃牌,5过牌 int32 betChip = 2; // 跟注/加注金额 + int32 operator = 3; // 0玩家操作,1自动操作 } // 通知玩家某个其他玩家动作 diff --git a/api/proto.js b/api/proto.js index b281e3b..deced73 100644 --- a/api/proto.js +++ b/api/proto.js @@ -5265,7 +5265,6 @@ export const api = $root.api = (() => { * @property {string|null} [avatar] TablePlayer avatar * @property {number|null} [chip] TablePlayer chip * @property {number|null} [status] TablePlayer status - * @property {number|null} [lastStatus] TablePlayer lastStatus * @property {boolean|null} [master] TablePlayer master * @property {number|null} [roundBetTimes] TablePlayer roundBetTimes * @property {number|null} [totalBetChip] TablePlayer totalBetChip @@ -5338,14 +5337,6 @@ export const api = $root.api = (() => { */ TablePlayer.prototype.status = 0; - /** - * TablePlayer lastStatus. - * @member {number} lastStatus - * @memberof api.TablePlayer - * @instance - */ - TablePlayer.prototype.lastStatus = 0; - /** * TablePlayer master. * @member {boolean} master @@ -5430,8 +5421,6 @@ export const api = $root.api = (() => { writer.uint32(/* id 5, wireType 0 =*/40).int32(message.chip); if (message.status != null && Object.hasOwnProperty.call(message, "status")) writer.uint32(/* id 6, wireType 0 =*/48).int32(message.status); - if (message.lastStatus != null && Object.hasOwnProperty.call(message, "lastStatus")) - writer.uint32(/* id 7, wireType 0 =*/56).int32(message.lastStatus); if (message.master != null && Object.hasOwnProperty.call(message, "master")) writer.uint32(/* id 8, wireType 0 =*/64).bool(message.master); if (message.roundBetTimes != null && Object.hasOwnProperty.call(message, "roundBetTimes")) @@ -5503,10 +5492,6 @@ export const api = $root.api = (() => { message.status = reader.int32(); break; } - case 7: { - message.lastStatus = reader.int32(); - break; - } case 8: { message.master = reader.bool(); break; @@ -5586,9 +5571,6 @@ export const api = $root.api = (() => { if (message.status != null && message.hasOwnProperty("status")) if (!$util.isInteger(message.status)) return "status: integer expected"; - if (message.lastStatus != null && message.hasOwnProperty("lastStatus")) - if (!$util.isInteger(message.lastStatus)) - return "lastStatus: integer expected"; if (message.master != null && message.hasOwnProperty("master")) if (typeof message.master !== "boolean") return "master: boolean expected"; @@ -5651,8 +5633,6 @@ export const api = $root.api = (() => { message.chip = object.chip | 0; if (object.status != null) message.status = object.status | 0; - if (object.lastStatus != null) - message.lastStatus = object.lastStatus | 0; if (object.master != null) message.master = Boolean(object.master); if (object.roundBetTimes != null) @@ -5708,7 +5688,6 @@ export const api = $root.api = (() => { object.avatar = ""; object.chip = 0; object.status = 0; - object.lastStatus = 0; object.master = false; object.roundBetTimes = 0; object.handType = null; @@ -5730,8 +5709,6 @@ export const api = $root.api = (() => { object.chip = message.chip; if (message.status != null && message.hasOwnProperty("status")) object.status = message.status; - if (message.lastStatus != null && message.hasOwnProperty("lastStatus")) - object.lastStatus = message.lastStatus; if (message.master != null && message.hasOwnProperty("master")) object.master = message.master; if (message.roundBetTimes != null && message.hasOwnProperty("roundBetTimes")) @@ -7078,6 +7055,7 @@ export const api = $root.api = (() => { * @interface IReqBetting * @property {number|null} [betType] ReqBetting betType * @property {number|null} [betChip] ReqBetting betChip + * @property {number|null} [operator] ReqBetting operator */ /** @@ -7111,6 +7089,14 @@ export const api = $root.api = (() => { */ ReqBetting.prototype.betChip = 0; + /** + * ReqBetting operator. + * @member {number} operator + * @memberof api.ReqBetting + * @instance + */ + ReqBetting.prototype.operator = 0; + /** * Creates a new ReqBetting instance using the specified properties. * @function create @@ -7139,6 +7125,8 @@ export const api = $root.api = (() => { writer.uint32(/* id 1, wireType 0 =*/8).int32(message.betType); if (message.betChip != null && Object.hasOwnProperty.call(message, "betChip")) writer.uint32(/* id 2, wireType 0 =*/16).int32(message.betChip); + if (message.operator != null && Object.hasOwnProperty.call(message, "operator")) + writer.uint32(/* id 3, wireType 0 =*/24).int32(message.operator); return writer; }; @@ -7181,6 +7169,10 @@ export const api = $root.api = (() => { message.betChip = reader.int32(); break; } + case 3: { + message.operator = reader.int32(); + break; + } default: reader.skipType(tag & 7); break; @@ -7222,6 +7214,9 @@ export const api = $root.api = (() => { if (message.betChip != null && message.hasOwnProperty("betChip")) if (!$util.isInteger(message.betChip)) return "betChip: integer expected"; + if (message.operator != null && message.hasOwnProperty("operator")) + if (!$util.isInteger(message.operator)) + return "operator: integer expected"; return null; }; @@ -7241,6 +7236,8 @@ export const api = $root.api = (() => { message.betType = object.betType | 0; if (object.betChip != null) message.betChip = object.betChip | 0; + if (object.operator != null) + message.operator = object.operator | 0; return message; }; @@ -7260,11 +7257,14 @@ export const api = $root.api = (() => { if (options.defaults) { object.betType = 0; object.betChip = 0; + object.operator = 0; } if (message.betType != null && message.hasOwnProperty("betType")) object.betType = message.betType; if (message.betChip != null && message.hasOwnProperty("betChip")) object.betChip = message.betChip; + if (message.operator != null && message.hasOwnProperty("operator")) + object.operator = message.operator; return object; }; diff --git a/internal/game/actions.go b/internal/game/actions.go index 991a0d8..4f08d2d 100644 --- a/internal/game/actions.go +++ b/internal/game/actions.go @@ -6,6 +6,7 @@ import ( "github.com/golang/protobuf/proto" "texas-poker-bk/api" "texas-poker-bk/tool/collect" + "time" ) type PlayerBetting struct { @@ -274,6 +275,11 @@ func SetNextPlayer4Limited(roundStart bool, t *Table, current *Player) { } // TODO 筹码不够跟注时可 all in + // 玩家离线,n秒后自动投注 + if !nextP.Client.IsOnline() { + nextP.OfflineAutoBettingDelayKey = nextP.GameTable. + RefAutoBettingDelayQueue.Add(60*time.Second, nextP.Id) + } } func AllIn4NoLimited(player *Player, betChip int32) proto.Message { diff --git a/internal/game/player.go b/internal/game/player.go index a4d165c..382aaad 100644 --- a/internal/game/player.go +++ b/internal/game/player.go @@ -3,6 +3,7 @@ package game import ( "sync" "texas-poker-bk/internal/letter" + "texas-poker-bk/tool/collect" ) var ( @@ -27,10 +28,12 @@ type Player struct { Hand *Hand // 手牌牌型 RoundCheckRaiseOnly bool // 该回合仅过牌下注 - GameTable *Table // 当前牌桌 - ProtoWriter letter.ProtoWriter - Lock *sync.Mutex - StatusLock *sync.Mutex + GameTable *Table // 当前牌桌 + Client letter.ProtoClient + Lock *sync.Mutex + StatusLock *sync.Mutex + + OfflineAutoBettingDelayKey int64 } func (p *Player) Init() { @@ -84,3 +87,11 @@ func (p *Player) SetStatus(status int32) { p.Status = status } + +// AutoBetting 回合离线时自动操作 +func (p *Player) AutoBetting() (BetType int32, BetChip int32) { + if collect.In(5, p.BetOpts...) { + return 5, 0 + } + return 4, 0 +} diff --git a/internal/game/table.go b/internal/game/table.go index 52cb803..391905c 100644 --- a/internal/game/table.go +++ b/internal/game/table.go @@ -8,6 +8,18 @@ import ( "texas-poker-bk/tool/collect" ) +var ( +// LobbyTables 存储当前进行中的牌桌 +// LobbyTables *store.Store[int32, *Table] +) + +func init() { + //var tableZeroValue *Table = nil + //LobbyTables = store.NewStore(tableZeroValue, func(k int32) string { + // return strconv.Itoa(int(k)) + //}) +} + type Table struct { TableNo int32 // 牌桌编号 MasterId int64 // 房主Id @@ -43,6 +55,8 @@ type Table struct { Lock *sync.Mutex // 牌桌锁 PlayersLock *sync.Mutex + + RefAutoBettingDelayQueue *collect.DelayQueue[int64] } // InitGameAndPlayerStatus 初始化桌面 @@ -171,6 +185,20 @@ func (t *Table) JoinPlayer(player *Player) (int, error) { return -1, errors.New("牌桌玩家已满") } +// RemovePlayer 从牌桌移除玩家 +func (t *Table) RemovePlayer(playerId int64) (found bool, player *Player) { + t.PlayersLock.Lock() + defer t.PlayersLock.Unlock() + // 从牌桌移除 + for i, p := range t.Players { + if p.Id == playerId { + t.Players[i] = nil + return true, p + } + } + return false, nil +} + // BuildResGameFullStatus 构建牌桌当前游戏状态消息 func (t *Table) BuildResGameFullStatus() *api.ResGameFullStatus { resGame := &api.ResGameFullStatus{ @@ -240,7 +268,7 @@ func (t *Table) NoticeGameFullStatus() { resGame := t.BuildResGameFullStatus() gameEnd := collect.In(t.Stage, 1, 7, 9) for _, player := range t.Players { - if player != nil && player.ProtoWriter != nil { + if player != nil && player.Client != nil { resGame.PlayerId = player.Id // 除结算时,只返回自己的手牌 if !gameEnd { @@ -253,7 +281,7 @@ func (t *Table) NoticeGameFullStatus() { } } } - player.ProtoWriter.Write(resGame) + player.Client.Write(resGame) } } } @@ -261,8 +289,8 @@ func (t *Table) NoticeGameFullStatus() { // NoticeAllPlayer 发送消息到牌桌所有玩家 func (t *Table) NoticeAllPlayer(message proto.Message) { for _, player := range t.Players { - if player != nil && player.ProtoWriter != nil { - player.ProtoWriter.Write(message) + if player != nil && player.Client != nil { + player.Client.Write(message) } } } diff --git a/internal/letter/proto_writer.go b/internal/letter/proto_writer.go index 890b4d4..016f9cc 100644 --- a/internal/letter/proto_writer.go +++ b/internal/letter/proto_writer.go @@ -2,6 +2,7 @@ package letter import "github.com/golang/protobuf/proto" -type ProtoWriter interface { +type ProtoClient interface { + IsOnline() bool Write(message proto.Message) } diff --git a/internal/server/ws.go b/internal/server/ws.go index 6e606ae..30566d3 100644 --- a/internal/server/ws.go +++ b/internal/server/ws.go @@ -9,6 +9,7 @@ import ( "net/http" "texas-poker-bk/api" "texas-poker-bk/internal/conf" + "texas-poker-bk/internal/service/event" "texas-poker-bk/internal/session" "texas-poker-bk/tool/ip" "time" @@ -35,17 +36,6 @@ func RouteWs(ctx *gin.Context) { } func Upgrade(ctx *gin.Context) { - //token, exists := ctx.GetQuery("t") - //if !exists { - // ctx.JSON(http.StatusUnauthorized, gin.H{"api": "unauth"}) - // return - //} - //subject, err := service.DecodeSubject(token) - //if err != nil { - // ctx.JSON(http.StatusUnauthorized, gin.H{"api": "token failed"}) - // return - //} - conn, err := upgradeWs(ctx.Writer, ctx.Request) if err != nil { fmt.Println(err) @@ -86,10 +76,15 @@ const ( // 处理新建的websocket func handleNetClient(client *session.NetClient) { defer func() { - // 捕获aes解析错误 + // 捕获其他错误 if r := recover(); r != nil { - fmt.Println(r) - client.Write(&api.ResFail{Code: 401, Msg: r.(error).Error()}) + fmt.Println("recover error: ", r) + client.Write(&api.ResFail{Code: 500, Msg: r.(error).Error()}) + } + }() + defer func() { + if client.Account != nil { + event.OfflineWatcher.Add(client.Account.Id, true) } }() // https://github.com/gorilla/websocket/blob/a68708917c6a4f06314ab4e52493cc61359c9d42/examples/chat/conn.go#L50 @@ -104,7 +99,7 @@ func handleNetClient(client *session.NetClient) { return client.Conn.SetReadDeadline(time.Now().Add(pongWait)) }) - // TODO 1分钟后过期 + // TODO 未认证连接30s后过期 for { // 阻塞读取消息 _, bytes, err := client.Conn.ReadMessage() @@ -115,24 +110,28 @@ func handleNetClient(client *session.NetClient) { } else { // 读失败 log.Printf("ReadMessage error: %T, %v", err, err) - client.Close("read conn err:" + err.Error()) } + client.Write(&api.ResFail{Msg: "消息读失败:" + err.Error()}) + client.Close("ws read err: " + err.Error()) return } wrap := &api.ProtoWrap{} err = proto.Unmarshal(bytes, wrap) if err != nil { + client.Write(&api.ResFail{Msg: "消息解析失败:" + err.Error()}) client.Close("api unmarshal fail! " + err.Error()) return } msg, err := api.NewProtoInstance(wrap.Op) if err != nil { + client.WriteSeq(false, wrap.Seq, &api.ResFail{Msg: "消息体解析失败_1:" + err.Error()}) client.Close(err.Error()) return } err = proto.Unmarshal(wrap.Body, msg) if err != nil { + client.WriteSeq(false, wrap.Seq, &api.ResFail{Msg: "消息体解析失败_2:" + err.Error()}) client.Close("api body unmarshal fail! " + err.Error()) return } diff --git a/internal/service/auth.go b/internal/service/auth.go index bd9c056..3fa00e4 100644 --- a/internal/service/auth.go +++ b/internal/service/auth.go @@ -16,6 +16,7 @@ import ( "texas-poker-bk/internal/conf" "texas-poker-bk/internal/dao" "texas-poker-bk/internal/model/entity" + "texas-poker-bk/internal/service/event" "texas-poker-bk/internal/service/store" "texas-poker-bk/internal/session" "texas-poker-bk/tool/collect" @@ -245,12 +246,15 @@ func HandleReqIdentity(client *session.NetClient, msg *api.ReqIdentity) (proto.M current.Client.Write(&api.ResFail{Code: 403, Msg: "该账号在其他地方登录,您已下线"}) current.Client = account.Client if current.Player != nil { - current.Player.ProtoWriter = account.Client + current.Player.Client = account.Client } account = current } client.Account = account + // 在线通知消息 + event.OnlineWatcher.Add(account.Id, true) + // response res := &api.ResIdentity{Id: subject.Id, Username: subject.Name, Avatar: subject.Avatar} return res, nil diff --git a/internal/service/event/auto_betting.go b/internal/service/event/auto_betting.go new file mode 100644 index 0000000..e04c836 --- /dev/null +++ b/internal/service/event/auto_betting.go @@ -0,0 +1,36 @@ +package event + +import ( + "fmt" + "github.com/golang/protobuf/proto" + "texas-poker-bk/api" + "texas-poker-bk/internal/game" + "texas-poker-bk/internal/service/store" + "texas-poker-bk/tool/collect" + "time" +) + +var AutoBettingDelayQueue *collect.DelayQueue[int64] + +var HandleReqBetting func(player *game.Player, msg *api.ReqBetting) (proto.Message, error) + +func init() { + AutoBettingDelayQueue = collect.NewDelayQueue(handleAutoBetting) +} + +func handleAutoBetting(accountId int64, _ time.Time) { + fmt.Println("auto betting...", accountId) + account := store.NetAccounts.Get(accountId) + if account == nil || account.Player == nil || account.Player.Status != 6 { + return + } + // 自动下注逻辑 + betType, betChip := account.Player.AutoBetting() + res, err := HandleReqBetting(account.Player, &api.ReqBetting{ + BetType: betType, BetChip: betChip, Operator: 1, + }) + resFail, ok := res.(*api.ResFail) + if err != nil || ok { + fmt.Println("auto betting err: ", resFail, err) + } +} diff --git a/internal/service/event/offline.go b/internal/service/event/offline.go new file mode 100644 index 0000000..fe394f4 --- /dev/null +++ b/internal/service/event/offline.go @@ -0,0 +1,58 @@ +package event + +import ( + "fmt" + "texas-poker-bk/internal/service/store" + "texas-poker-bk/internal/session" + "texas-poker-bk/tool/collect" + "texas-poker-bk/tool/watcher" + "time" +) + +var ( + // OfflineWatcher 离线动作 + OfflineWatcher *watcher.Watcher[int64, bool] + + //OfflineQueue *collect.DelayQueue[*session.NetAccount] + +) + +func init() { + OfflineWatcher = watcher.NewWatcher(128, handleOffline) + go OfflineWatcher.Run() + +} + +func handleOffline(e *watcher.Event[int64, bool]) { + account := store.NetAccounts.Get(e.Publisher) + if account == nil { + return + } + fmt.Printf("account %d, %s handle offline.\n", account.Id, account.UserName) + if account == nil || store.NetAccounts.Get(account.Id) == nil { + return + } + if account.Player == nil { + // 不在游戏中,直接下线 + removeMemoryAccount(account) + return + } + if collect.In(account.Player.GameTable.Stage, 1, 9) { + // 牌局未在进行中直接移除玩家并下线 + account.Player.GameTable.RemovePlayer(account.Id) + account.SettlePlayerChip() + removeMemoryAccount(account) + return + } + // 判断当前是玩家回合(60s自动过牌/弃牌) + if account.Player.Status == 6 { + account.Player.GameTable.RefAutoBettingDelayQueue.Add(60*time.Second, account.Id) + } + // 待玩家回合时还是offline(自动过牌/弃牌) +} + +func removeMemoryAccount(account *session.NetAccount) { + store.NetAccounts.Delete(account.Id) + store.TokenVersions.Delete(account.Id) + // TODO 持久化账户金额 +} diff --git a/internal/service/event/online.go b/internal/service/event/online.go new file mode 100644 index 0000000..afbd31e --- /dev/null +++ b/internal/service/event/online.go @@ -0,0 +1,27 @@ +package event + +import ( + "texas-poker-bk/internal/service/store" + "texas-poker-bk/tool/watcher" +) + +// OnlineWatcher 上线动作 +var OnlineWatcher *watcher.Watcher[int64, bool] + +func init() { + OnlineWatcher = watcher.NewWatcher(128, handleOnline) + go OnlineWatcher.Run() +} + +// handleOnline 玩家上线了,取消一些自动操作动作 +func handleOnline(e *watcher.Event[int64, bool]) { + account := store.NetAccounts.Get(e.Publisher) + if account == nil || account.Player == nil { + return + } + // 取消自动下注 + if account.Player.OfflineAutoBettingDelayKey != 0 { + AutoBettingDelayQueue.Cancel(account.Player.OfflineAutoBettingDelayKey) + account.Player.OfflineAutoBettingDelayKey = 0 + } +} diff --git a/internal/service/online.go b/internal/service/online.go deleted file mode 100644 index e8dc296..0000000 --- a/internal/service/online.go +++ /dev/null @@ -1,37 +0,0 @@ -package service - -import ( - "fmt" - "texas-poker-bk/internal/service/store" - "texas-poker-bk/internal/session" - "texas-poker-bk/tool/collect" - "time" -) - -func init() { - OfflineQueue := collect.NewDelayQueue(HandleUserOffline) - OfflineQueue.Add(1, nil) -} - -func HandleUserOffline(account *session.NetAccount, t time.Time) { - fmt.Printf("account %d, %s handle offline.\n", account.Id, account.UserName) - if account == nil || store.NetAccounts.Get(account.Id) == nil { - return - } - if account.Player == nil { - // 不在游戏中,直接下线 - removeOnlineUser(account) - return - } - if collect.In(account.Player.GameTable.Stage, 1, 9) { - // TODO account.Player.GameTable.RemovePlayer() - } - -} - -func removeOnlineUser(account *session.NetAccount) { - store.NetAccounts.Delete(account.Id) - store.TokenVersions.Delete(account.Id) - - // TODO 持久化账户金额 -} diff --git a/internal/service/store/store.go b/internal/service/store/store.go index d787b28..f4f04c2 100644 --- a/internal/service/store/store.go +++ b/internal/service/store/store.go @@ -12,12 +12,14 @@ import ( var ( TableNo *atomic.Int32 - LobbyTables *store[int32, *game.Table] + // LobbyTables 存储当前进行中的牌桌 + LobbyTables *Store[int32, *game.Table] // NetAccounts 存储一下在线用户, TODO 连接终端,未在牌局或牌局未开始中回收账号,机器人代打后回收账号 - NetAccounts *store[int64, *session.NetAccount] + NetAccounts *Store[int64, *session.NetAccount] - TokenVersions *store[int64, int32] + // TokenVersions 存储token版本号,登录时+1,一个账户同一时间只允许一个token有效 + TokenVersions *Store[int64, int32] ) const ( @@ -25,21 +27,51 @@ const ( DefaultExpiration time.Duration = cache.DefaultExpiration ) -type store[K any, V any] struct { +func init() { + TableNo = &atomic.Int32{} // 牌桌编号计数器 + + var tableZeroValue *game.Table = nil + LobbyTables = NewStore(tableZeroValue, func(k int32) string { + return strconv.Itoa(int(k)) + }) + + var accountZeroValue *session.NetAccount = nil + NetAccounts = NewStore(accountZeroValue, func(k int64) string { + return strconv.FormatInt(k, 10) + }) + + TokenVersions = NewStore(int32(-1), func(k int64) string { + return strconv.FormatInt(k, 10) + }) +} + +type Store[K any, V any] struct { c *cache.Cache zeroValue V k2str func(K) string } -func (s *store[K, V]) SetDefault(k K, v V) { +func NewStore[K any, V any](zeroValue V, k2str func(K) string) *Store[K, V] { + return NewExpireStore(cache.NoExpiration, cache.NoExpiration, zeroValue, k2str) +} + +func NewExpireStore[K any, V any](defaultExpiration, cleanupInterval time.Duration, zeroValue V, k2str func(K) string) *Store[K, V] { + return &Store[K, V]{ + c: cache.New(defaultExpiration, cleanupInterval), + zeroValue: zeroValue, + k2str: k2str, + } +} + +func (s *Store[K, V]) SetDefault(k K, v V) { s.c.SetDefault(s.k2str(k), v) } -func (s *store[K, V]) Set(k K, v V, d time.Duration) { +func (s *Store[K, V]) Set(k K, v V, d time.Duration) { s.c.Set(s.k2str(k), v, d) } -func (s *store[K, V]) Get(k K) V { +func (s *Store[K, V]) Get(k K) V { v, found := s.c.Get(s.k2str(k)) if !found { return s.zeroValue @@ -47,43 +79,16 @@ func (s *store[K, V]) Get(k K) V { return v.(V) } -func (s *store[K, V]) Delete(k K) { +func (s *Store[K, V]) Delete(k K) { s.c.Delete(s.k2str(k)) } -func (s *store[K, V]) ForEach(f func(k string, v V)) { +func (s *Store[K, V]) ForEach(f func(k string, v V)) { for k, v := range s.c.Items() { f(k, v.Object.(V)) } } -func (s *store[K, V]) Count() int { +func (s *Store[K, V]) Count() int { return s.c.ItemCount() } - -func init() { - LobbyTables = &store[int32, *game.Table]{ - c: cache.New(cache.NoExpiration, cache.NoExpiration), - zeroValue: nil, - k2str: func(k int32) string { - return strconv.Itoa(int(k)) - }, - } - NetAccounts = &store[int64, *session.NetAccount]{ - c: cache.New(cache.NoExpiration, cache.NoExpiration), - zeroValue: nil, - k2str: func(k int64) string { - return strconv.FormatInt(k, 10) - }, - } - - TokenVersions = &store[int64, int32]{ - c: cache.New(cache.NoExpiration, cache.NoExpiration), - zeroValue: -1, - k2str: func(k int64) string { - return strconv.FormatInt(k, 10) - }, - } - - TableNo = &atomic.Int32{} // 牌桌编号计数器 -} diff --git a/internal/service/table.go b/internal/service/table.go index d2ead7f..57815cb 100644 --- a/internal/service/table.go +++ b/internal/service/table.go @@ -7,11 +7,17 @@ import ( "sort" "texas-poker-bk/api" "texas-poker-bk/internal/game" + "texas-poker-bk/internal/service/event" "texas-poker-bk/internal/service/store" "texas-poker-bk/internal/session" "texas-poker-bk/tool/collect" ) +func init() { + // TODO 循环依赖 ioc 参考 + event.HandleReqBetting = HandleReqBetting +} + // HandleReqCreateTable 创建桌面 func HandleReqCreateTable(account *session.NetAccount, msg *api.ReqCreateTable) (proto.Message, error) { account.Lock.Lock() @@ -37,14 +43,15 @@ func HandleReqCreateTable(account *session.NetAccount, msg *api.ReqCreateTable) TableNo: store.TableNo.Add(1), MasterId: account.Id, - PlayerNum: playerNum + 1, - RobotNum: robotNum, - Players: make([]*game.Player, playerNum+1), - Robots: make([]*game.Robot, robotNum), - BigBlindChip: msg.BigBlind, - SmallBlindChip: msg.BigBlind / 2, - LimitInAmount: msg.BigBlind * 100, - TexasType: msg.TexasType, + PlayerNum: playerNum + 1, + RobotNum: robotNum, + Players: make([]*game.Player, playerNum+1), + Robots: make([]*game.Robot, robotNum), + BigBlindChip: msg.BigBlind, + SmallBlindChip: msg.BigBlind / 2, + LimitInAmount: msg.BigBlind * 100, + TexasType: msg.TexasType, + RefAutoBettingDelayQueue: event.AutoBettingDelayQueue, } // 游戏类型 对应 下注处理流程 switch msg.TexasType { @@ -69,7 +76,7 @@ func HandleReqCreateTable(account *session.NetAccount, msg *api.ReqCreateTable) TotalBetChip: 0, Cards: [2]*game.Card{}, GameTable: table, - ProtoWriter: account.Client, + Client: account.Client, } owner.Init() // 挂载 player 到 account 上 @@ -89,7 +96,7 @@ func HandleReqCreateTable(account *session.NetAccount, msg *api.ReqCreateTable) } // HandleReqLobbyView 返回当前所有桌面和玩家数量 -func HandleReqLobbyView(account *session.NetAccount, msg *api.ReqLobbyView) (proto.Message, error) { +func HandleReqLobbyView(account *session.NetAccount, _ *api.ReqLobbyView) (proto.Message, error) { res := &api.ResLobbyView{} if store.LobbyTables.Count() == 0 { return res, nil @@ -119,7 +126,7 @@ func HandleReqLobbyView(account *session.NetAccount, msg *api.ReqLobbyView) (pro } } if collect.IsNotEmptySlice(t.Robots) { - for _, _ = range t.Robots { + for range t.Robots { table.Players[playerIdx] = &api.LobbyPlayer{Robot: true} playerIdx++ } @@ -171,7 +178,7 @@ func HandleReqJoinTable(account *session.NetAccount, msg *api.ReqJoinTable) (pro TotalBetChip: 0, Cards: [2]*game.Card{nil, nil}, GameTable: table, - ProtoWriter: account.Client, + Client: account.Client, } player.Init() index, err := table.JoinPlayer(player) @@ -205,17 +212,13 @@ func HandleReqKickOutTable(player *game.Player, msg *api.ReqKickOutTable) (proto if player.GameTable.Stage != 1 { return &api.ResFail{Msg: "牌局进行中不能踢人"}, nil } - for i, p := range player.GameTable.Players { - if p != nil && p.Id == msg.PlayerId { - // 从牌桌移除该玩家 - player.GameTable.Players[i] = nil - // 被对踢出人发送消息 - account := store.NetAccounts.Get(p.Id) - account.Player = nil // 解除账户绑定 - account.IncrementBalance(player.Chip) // 筹码返还账户 - p.ProtoWriter.Write(&api.ResKickOutTable{}) // 被踢玩家消息 - break - } + + found, p := player.GameTable.RemovePlayer(msg.PlayerId) + if found { + // 结算玩家金额 + store.NetAccounts.Get(p.Id).SettlePlayerChip() + // 被踢玩家消息 + player.Client.Write(&api.ResKickOutTable{}) } // 通知牌桌所有玩家 player.GameTable.NoticeGameFullStatus() @@ -223,7 +226,7 @@ func HandleReqKickOutTable(player *game.Player, msg *api.ReqKickOutTable) (proto } // HandleReqLeaveTable 离开桌面 -func HandleReqLeaveTable(player *game.Player, msg *api.ReqLeaveTable) (proto.Message, error) { +func HandleReqLeaveTable(player *game.Player, _ *api.ReqLeaveTable) (proto.Message, error) { player.GameTable.PlayersLock.Lock() defer player.GameTable.PlayersLock.Unlock() @@ -237,16 +240,10 @@ func HandleReqLeaveTable(player *game.Player, msg *api.ReqLeaveTable) (proto.Mes return &api.ResFail{Msg: "房主不能退出,请解散房间"}, nil } // 从牌桌移除 - for i, p := range player.GameTable.Players { - if p.Id == player.Id { - player.GameTable.Players[i] = nil - break - } + found, p := player.GameTable.RemovePlayer(player.Id) + if found { + store.NetAccounts.Get(p.Id).SettlePlayerChip() } - account := store.NetAccounts.Get(player.Id) - account.Player = nil // 解除账户绑定 - account.IncrementBalance(player.Chip) // 筹码返还账户 - // 通知牌桌其他玩家 player.GameTable.NoticeGameFullStatus() @@ -254,7 +251,7 @@ func HandleReqLeaveTable(player *game.Player, msg *api.ReqLeaveTable) (proto.Mes } // HandleReqGameFullStatus 获取牌桌当前所有状态 -func HandleReqGameFullStatus(player *game.Player, msg *api.ReqGameFullStatus) (proto.Message, error) { +func HandleReqGameFullStatus(player *game.Player, _ *api.ReqGameFullStatus) (proto.Message, error) { if player.GameTable == nil { return &api.ResFail{Msg: "当前未加入牌桌"}, nil } @@ -274,7 +271,7 @@ func HandleReqGameFullStatus(player *game.Player, msg *api.ReqGameFullStatus) (p } // HandleReqReadyStart 准备开始游戏 -func HandleReqReadyStart(player *game.Player, msg *api.ReqReadyStart) (proto.Message, error) { +func HandleReqReadyStart(player *game.Player, _ *api.ReqReadyStart) (proto.Message, error) { player.Lock.Lock() defer player.Lock.Unlock() if player.Id == player.GameTable.MasterId { @@ -297,9 +294,11 @@ func HandleReqReadyStart(player *game.Player, msg *api.ReqReadyStart) (proto.Mes return &api.ResSuccess{}, nil } -func HandleReqCancelReady(player *game.Player, msg *api.ReqCancelReady) (proto.Message, error) { - player.Lock.Lock() - defer player.Lock.Unlock() +func HandleReqCancelReady(player *game.Player, _ *api.ReqCancelReady) (proto.Message, error) { + // 和 GameStart 互斥 + player.GameTable.Lock.Lock() + defer player.GameTable.Lock.Unlock() + if player.Id == player.GameTable.MasterId { return &api.ResFail{Msg: "房主不用准备"}, nil } @@ -312,7 +311,7 @@ func HandleReqCancelReady(player *game.Player, msg *api.ReqCancelReady) (proto.M } // HandleReqDismissGameTable 解散牌桌 -func HandleReqDismissGameTable(player *game.Player, msg *api.ReqDismissGameTable) (proto.Message, error) { +func HandleReqDismissGameTable(player *game.Player, _ *api.ReqDismissGameTable) (proto.Message, error) { player.GameTable.Lock.Lock() defer player.GameTable.Lock.Unlock() if !collect.In(player.GameTable.Stage, 1, 7) { @@ -330,23 +329,16 @@ func HandleReqDismissGameTable(player *game.Player, msg *api.ReqDismissGameTable account.Player = nil player.GameTable.Players[i] = nil if p.Id != player.Id { - p.ProtoWriter.Write(&api.ResDismissGameTable{}) + p.Client.Write(&api.ResDismissGameTable{}) } } return &api.ResDismissGameTable{}, nil } // HandleReqGameStart 游戏开始,初始化,扣除大小盲注,每个玩家发2张牌 -func HandleReqGameStart(player *game.Player, msg *api.ReqGameStart) (proto.Message, error) { +func HandleReqGameStart(player *game.Player, _ *api.ReqGameStart) (proto.Message, error) { player.GameTable.Lock.Lock() defer player.GameTable.Lock.Unlock() - // 锁定牌桌所有玩家 - for _, p := range player.GameTable.Players { - if p != nil { - p.Lock.Lock() - defer p.Lock.Unlock() - } - } table := player.GameTable if player.Id != table.MasterId { @@ -355,6 +347,9 @@ func HandleReqGameStart(player *game.Player, msg *api.ReqGameStart) (proto.Messa if !collect.In(table.Stage, 1, 7) { return &api.ResFail{Msg: fmt.Sprintf("牌局状态有误%d", table.Stage)}, nil } + if table.PlayerCount() < 2 { + return &api.ResFail{Msg: "至少需两位玩家可开始"}, nil + } // 检查所有玩家准备状态 for _, p := range table.Players { if p != nil && p.Id != table.MasterId && p.Status != 2 { @@ -434,6 +429,9 @@ func HandleReqGameStart(player *game.Player, msg *api.ReqGameStart) (proto.Messa // HandleReqBetting 下注 func HandleReqBetting(player *game.Player, msg *api.ReqBetting) (proto.Message, error) { + player.Lock.Lock() + defer player.Lock.Unlock() + if player.Status != 6 { return &api.ResFail{Msg: "当前未轮到您下注"}, nil } diff --git a/internal/session/net_account.go b/internal/session/net_account.go index 36adeca..35fdb7d 100644 --- a/internal/session/net_account.go +++ b/internal/session/net_account.go @@ -37,3 +37,10 @@ func (account *NetAccount) GetBalance() int32 { defer account.BalanceLock.RUnlock() return account.Balance } + +func (account *NetAccount) SettlePlayerChip() { + // 返还玩家账户金额 + account.IncrementBalance(account.Player.Chip) // 牌桌筹码返还到账户 TODO 持久化 + account.Player.Chip = 0 + account.Player = nil // 解除账户绑定 +} diff --git a/internal/session/net_client.go b/internal/session/net_client.go index d7881f1..6588cf5 100644 --- a/internal/session/net_client.go +++ b/internal/session/net_client.go @@ -6,6 +6,7 @@ import ( "github.com/gorilla/websocket" "log" "sync" + "sync/atomic" "texas-poker-bk/api" "time" ) @@ -14,24 +15,35 @@ import ( type NetClient struct { // Id int64 Conn *websocket.Conn + Online *atomic.Bool Account *NetAccount WriteLock *sync.Mutex } func NewNetClient(conn *websocket.Conn) *NetClient { + online := &atomic.Bool{} + online.Store(true) + return &NetClient{ Conn: conn, WriteLock: new(sync.Mutex), + Online: online, } } func (c *NetClient) Close(reason string) { + log.Println("close NetClient reason: ", reason) + c.Online.Store(false) err := c.Conn.Close() if err != nil { log.Println("Close Error", err) } } +func (c *NetClient) IsOnline() bool { + return c.Online.Load() +} + func (c *NetClient) Write(msg proto.Message) { c.WriteSeq(true, 0, msg) } diff --git a/internal/session/player.go b/internal/session/player.go deleted file mode 100644 index 25f5bf6..0000000 --- a/internal/session/player.go +++ /dev/null @@ -1,19 +0,0 @@ -package session - -import ( - "sync" - "texas-poker-bk/internal/game" -) - -// Player 玩家: table,balance,(hand card) -type Player struct { - Account *NetAccount - - Id int64 - Username string - Avatar string - Cards [2]*game.Card // 手牌 - Chip int // 玩家筹码 - Status int32 // 状态: 1待准备开始,2已准备开始,3等待其他玩家动作,4待大盲注,5待小盲注,6待跟注,7已弃 - StatusLock *sync.Mutex -} diff --git a/tool/watcher/watcher.go b/tool/watcher/watcher.go new file mode 100644 index 0000000..a0b9580 --- /dev/null +++ b/tool/watcher/watcher.go @@ -0,0 +1,47 @@ +package watcher + +import ( + "time" +) + +type Event[P any, T any] struct { + Publisher P + Payload T + UnixMilli int64 +} + +type Watcher[P any, T any] struct { + events chan *Event[P, T] + stop chan bool + handler func(event *Event[P, T]) +} + +func NewWatcher[P any, T any](buffer int, handler func(event *Event[P, T])) *Watcher[P, T] { + return &Watcher[P, T]{ + events: make(chan *Event[P, T], buffer), + stop: make(chan bool), + handler: handler, + } +} + +func (w *Watcher[P, T]) Run() { + for { + select { + case e := <-w.events: + w.handler(e) + case <-w.stop: + return + } + } +} + +func (w *Watcher[P, T]) Stop() { + w.stop <- true +} + +func (w *Watcher[P, T]) Add(publisher P, event T) { + e := &Event[P, T]{publisher, event, time.Now().UnixMilli()} + go func() { + w.events <- e + }() +}