房间创建功能
(一)功能描述
玩家启动游戏后,可通过界面点击"创建房间"按钮,触发房间创建流程。系统自动生成房间号,并将房间相关信息存储于服务器,同时将房间号推送给房间创建者,便于其邀请其他玩家加入。
(二)实现细节
-
房间号生成:服务器采用特定算法生成唯一房间号,确保不同房间间无冲突。
-
房间信息存储:房间创建时,服务器记录房间号、创建者用户ID、房间初始状态(如空闲、游戏进行中等)等关键信息。
-
房间号推送:房间创建成功后,服务器通过已建立的网络连接,将房间号及相关基本信息以数据包形式推送给创建者客户端,客户端收到后显示房间号,供创建者使用。
进入房间通知功能
(一)功能描述
当玩家创建房间或加入房间时,系统需通知该玩家进入房间,同时告知其房间内当前的游戏类型等关键信息,以便玩家知晓并进行后续操作,如准备游戏等。
(二)实现细节
-
通知触发:玩家完成房间创建或加入操作后,系统自动触发进入房间通知流程。
-
信息推送:系统从服务器获取房间当前游戏类型等信息,将其封装为特定格式的数据包,通过网络推送给对应玩家客户端。
-
客户端处理:客户端收到通知后,解析数据包,提取游戏类型等信息,在界面上显示相关提示,如"您已进入房间,当前游戏类型为[具体类型]",并根据游戏类型加载相应资源,为进入游戏做好准备。
踢出房间机制
(一)功能描述
为避免玩家长时间停留在房间内不进行准备操作,影响其他玩家游戏体验,系统设置踢出房间机制。若玩家在规定时间内(如30秒)未准备,系统自动将其踢出房间,并通知房间内其他玩家该玩家被踢出,同时清理房间内该玩家相关数据。
(二)实现细节
-
定时任务设置:玩家进入房间后,系统为其设置定时任务,开始计时。
-
准备状态检测:定时任务运行期间,系统持续检测玩家是否完成准备操作。若玩家准备,取消定时任务;若定时任务完成时玩家仍未准备,执行踢出操作。
-
踢出操作执行:系统将被踢出玩家的房间ID置为空,通知房间内其他玩家该玩家被踢出,清理房间内该玩家数据,如座位信息等。
-
房间解散判断:踢出玩家后,系统检查房间内剩余玩家数量。若无玩家,自动解散房间,释放房间资源。
用户准备功能
(一)功能描述
玩家进入房间后,需点击"准备"按钮表明已准备好开始游戏。系统记录玩家准备状态,并通知房间内其他玩家该玩家已准备,当所有玩家均准备完毕后,可触发游戏开始逻辑。
(二)实现细节
-
准备状态记录:玩家点击"准备"按钮后,客户端向服务器发送准备请求,服务器接收后更新该玩家在房间内的准备状态为"已准备"。
-
状态通知:服务器将该玩家准备状态更新信息推送给房间内其他玩家,其他玩家客户端收到后更新界面显示,如显示该玩家头像旁的"已准备"标识。
-
游戏开始判断:系统持续检测房间内所有玩家准备状态,当所有玩家均准备完毕时,满足游戏开始条件,可触发发牌等游戏开始相关操作。
加入房间功能
(一)功能描述
已有房间存在时,其他玩家可通过输入房间号申请加入该房间。系统验证房间号有效性、房间是否已满等条件,若满足则允许玩家加入,并通知房间内所有玩家有新玩家加入,同步新玩家相关信息,如用户名、头像等。
(二)实现细节
-
房间查找:玩家输入房间号后,客户端将房间号发送至服务器,服务器根据房间号查找对应房间。
-
条件判断:服务器检查房间是否存在、是否已满等条件。若房间不存在或已满,返回错误信息给申请加入的玩家;若条件满足,允许加入。
-
玩家加入处理:新玩家加入后,服务器更新房间内玩家列表,将新玩家信息推送给房间内所有玩家,同时将房间内已有游戏相关信息(如当前游戏阶段、其他玩家准备状态等)推送给新加入玩家,实现信息同步。
func (r *Room) userReady(uid string, session *remote.Session) { if r.gameStarted { return } //1. push用户的座次,修改用户的状态,取消定时任务 user, ok := r.users[uid] if !ok { return } //首局判断玩家积分,如果积分不够则直接踢出游戏 if r.hasStartedOneBureau && user.UserInfo.Score < r.GameRule.ScoreLowLimit { r.sendPopDialogContent(biz.LeaveRoomGoldExceedLimit, []string{user.UserInfo.Uid}, session) r.kickUser(user, session) return } if user.UserStatus&enums.Ready == 0 { user.UserStatus |= enums.Ready user.UserStatus |= enums.Dismiss } else { return } r.sendData(proto.UserReadyPushData(user.ChairID), session.GetMsg()) efficacyStartRoom := r.efficacyStartRoom() if efficacyStartRoom { r.startGame(session, user) } else { if r.hasStartedOneBureau { return } if r.gameStarted { return } if r.startSchedulerID != nil { return } start := r.isShouldSchedulerStart() if start { tick := 10 r.startSchedulerID = tasks.NewTask("startSchedulerID", 1*time.Second, func() { if r.isDismissing() { return } tick-- if tick >= 0 { return } if !r.isShouldSchedulerStart() { return } //开始游戏 if r.gameStarted { return } //没准备的玩家转为旁观 for _, v := range r.users { if v.ChairID >= r.chairCount { continue } if v.UserStatus&enums.Ready == 0 { r.userChangeSeat(session, v.ChairID, r.getEmptyChairID("", true)) } } r.startGame(session, user) r.stopStartSchedulerID <- struct{}{} }) } } } func (r *Room) JoinRoom(session *remote.Session, data *entity.User) *msError.Error { return r.UserEntryRoom(session, data) } func (r *Room) OtherUserEntryRoomPush(session *remote.Session, uid string) { others := make([]string, 0) for _, v := range r.users { if v.UserInfo.Uid != uid { others = append(others, v.UserInfo.Uid) } } user, ok := r.users[uid] if ok { r.SendData(session.GetMsg(), others, proto.OtherUserEntryRoomPushData(user)) } }