package game import ( "fmt" "github.com/davy66666/poker-go/src/server/protocol" "log" "microGame/app/usercenter/cmd/api/internal/algorithm" "microGame/app/usercenter/model/room" "microGame/pkg/common" "microGame/pkg/result" "sync" "time" ) var once sync.Once const ( RUNNING uint8 = 1 GAMEOVER uint8 = 0 SB uint32 = 0 BB uint32 = 0 CHIPS uint32 = 1000 ) type Room struct { *room.Room Rid int64 Mutex sync.Mutex // 并发安全锁 Players []*Player observes []*Player // 站起的玩家 AutoSitDown []*Player // 自动坐下队列 Phase string // 当前阶段(pre-flop, flop, turn, river, showdown) remain int allin int n uint8 status uint8 //游戏是否进行中0:game over, 1:running isRunning bool //判断goroutine是否已经启动 SB uint32 // 小盲注 BB uint32 // 大盲注 Cards algorithm.Cards // 公共牌 Pot []uint32 // 奖池筹码数, 第一项为主池,其他项(若存在)为边池 Timeout time.Duration // 倒计时超时时间(秒) Button uint8 // 当前庄家座位号,从1开始 Chips []uint32 // 玩家本局下注的总筹码数,与Players一一对应 Bet uint32 // 当前回合 上一玩家下注额 Max uint8 // 房间最大玩家人数 MaxChips uint32 MinChips uint32 Done chan struct{} //销毁room 退出goroutine NextRound chan struct{} // 游戏自动开始 } func NewRoom(max uint8, sb, bb uint32, chips uint32, timeout uint8) *Room { if max <= 0 || max > 9 { max = 9 // default 9 Players } r := &Room{ Chips: make([]uint32, max), Players: make([]*Player, max), Pot: make([]uint32, 0, max), Timeout: time.Second * time.Duration(timeout), SB: sb, BB: bb, Max: max, Done: make(chan struct{}), } //room入库记录room 生成room id return r } type startDelay struct { kind uint8 } func (r *Room) New(m interface{}) *Room { if msg, ok := m.(*protocol.JoinRoom); ok { if len(msg.RoomNumber) == 0 { //r := room.FindRoom() //return r } //数据库查询room 如果存在直接返回 //r := room.GetRoom(msg.RoomNumber) //if r != nil { // //return r //} //room.Insert() return NewRoom(9, SB, BB, CHIPS, Timeout) } return nil } func (r *Room) isEmpty() bool { return len(r.Players) == 0 } func (r *Room) WriteMsg(msg interface{}, exc ...int64) { // 将 exc 列表转换成 map,提高排除用户的查询效率 excMap := make(map[int64]struct{}, len(exc)) for _, uid := range exc { excMap[uid] = struct{}{} } // 遍历玩家并广播消息 for _, v := range r.Players { if v == nil { continue } // 如果玩家的 UID 在排除列表中,则跳过该玩家 if _, excluded := excMap[v.GetUid()]; excluded { continue } v.WriteMsg(msg) } } func (r *Room) handleGameLogic() { fmt.Printf("房间 %d 处理游戏逻辑\n", r.Rid) // 示例:检查是否需要下一轮发牌,或者执行其他游戏逻辑 r.nextRound() } func (r *Room) startGame() { r.Mutex.Lock() // 如果已经有 Goroutine 运行,不重复启动 if r.isRunning { log.Printf("房间 %d 已经在运行\n", r.Rid) r.Mutex.Unlock() return } // 标记房间正在运行 r.isRunning = true r.Mutex.Unlock() // 启动新的 Goroutine 来管理房间 go func() { go common.RunWithRecover(func() { r.modifyRoomStatus() }) r.nextRound() // 监听房间状态并执行游戏逻辑 for { select { case <-r.NextRound: // 检查游戏状态并进行下一轮 r.nextRound() case <-r.Done: // 监听房间关闭信号 close(r.NextRound) log.Printf("房间 %d 关闭,退出 Goroutine\n", r.Rid) return } } }() } // **下一轮** func (r *Room) nextRound() bool { r.Mutex.Lock() log.Println("进入下一轮") // 将符合条件的观察者转为玩家 for k := 0; k < len(r.observes); k++ { observer := r.observes[k] if observer.chips > 0 && uint8(len(r.Players)) < r.Max { observer.SetSitDown() r.Players = append(r.Players, observer) // 删除观察者 r.observes = append(r.observes[:k], r.observes[k+1:]...) k-- // 调整索引,确保不会跳过下一个观察者 log.Printf("观察者 %d 转为玩家", observer.Uid) } } // 检查是否继续游戏 if len(r.Players) < 2 { log.Println("游戏结束") r.Mutex.Unlock() r.goNextGame() return false } //游戏正在进行 r.status = 1 r.Mutex.Unlock() //发牌 下注 fmt.Println("开始发牌!") startDelay := &startDelay{ kind: 0, } //处理游戏下注 r.startDelay(startDelay) //// 进入新的一轮 //// 5 秒后自动进入下一轮 go common.RunWithRecover(func() { r.Broadcast(result.WSSuccess("start", startDelay), true) r.goNextGame() }) return true } func (r *Room) goNextGame() { go func() { time.Sleep(5 * time.Second) r.NextRound <- struct{}{} }() } func (r *Room) modifyRoomStatus() { //r.Started = false r.status = 0 } func (r *Room) Broadcast(msg interface{}, all bool, exc ...int64) { // 将 exc 列表转换成 map,提高排除用户的查询效率 var excMap map[int64]struct{} if len(exc) > 0 { excMap = make(map[int64]struct{}, len(exc)) for _, uid := range exc { excMap[uid] = struct{}{} } } // 遍历玩家并广播消息 for _, v := range r.Players { if v == nil || (!all && v.IsGaming()) { continue } if _, excluded := excMap[v.GetUid()]; excluded { continue } v.WriteMsg(msg) } // 遍历观察者并广播消息 for _, v := range r.observes { if v == nil { continue } if _, excluded := excMap[v.Uid]; excluded { continue } v.WriteMsg(msg) } } func (r *Room) addPlayer(o *Player) uint8 { for _, v := range r.Players { if v != nil && v.GetUid() == o.Uid { return 0 } } for k, v := range r.Players { if v == nil { r.Players[k] = o o.Pos = uint8(k + 1) o.SetSitDown() return o.Pos } } return 0 } func (r *Room) removePlayer(o *Player) uint8 { for k, v := range r.Players { if v != nil && v.GetUid() == o.Uid { v.SetPos(0) r.Players[k] = nil return uint8(k + 1) } } return 0 } func (r *Room) addObserve(o *Player) uint8 { for _, v := range r.observes { if v != nil && v.Uid == o.Uid { return 0 } } o.SetObserve() r.observes = append(r.observes, o) return 0 } func (r *Room) removeObserve(o *Player) { for k, v := range r.observes { if v != nil && v.Uid == o.Uid { r.observes = append(r.observes[:k], r.observes[k+1:]...) return } } } func (r *Room) CloseRoomChannel() { once.Do(func() { close(r.Done) fmt.Println("通道被关闭") }) } // Each 从指定索引 `start` 开始遍历玩家列表,并对满足条件的玩家执行回调函数 `f` func (r *Room) Each(start uint8, f func(o *Player) bool) { volume := r.Cap() if volume == 0 { // 避免除零错误 return } end := (volume + start - 1) % volume i := start for { player := r.Players[i] if player != nil && player.IsGaming() { if !f(player) { // 如果回调返回 false,则终止循环 return } } if i == end { // 处理最后一个元素,防止额外调用 break } i = (i + 1) % volume } } func (r *Room) Cap() uint8 { return r.Max } func (r *Room) Len() uint8 { var num uint8 for _, v := range r.Players { if v != nil { num++ } } return num } func (r *Room) GetNumber() string { return r.RoomNumber } func (r *Room) SetNumber(value string) { r.RoomNumber = value } func (r *Room) Data() interface{} { return r.Room } func (r *Room) FindRoom() bool { r.Mutex.Lock() if r.Len() < r.Cap() { return true } r.Mutex.Unlock() return false }