diff --git a/common/baseroom/baseRoom.go b/common/baseroom/baseRoom.go index 57289c3..3fe0b63 100644 --- a/common/baseroom/baseRoom.go +++ b/common/baseroom/baseRoom.go @@ -121,6 +121,24 @@ func (r *BaseRoom[Seat]) RemovePlayer(player IPlayer) { } } +func (r *BaseRoom[Seat]) RangePlayer(proc func(IPlayer) bool) { + for _, seat := range r.Seats { + if !seat.Empty() && !proc(seat.Player()) { + return + } + } +} + +func (r *BaseRoom[Seat]) FilterPlayer(proc func(IPlayer) bool) []IPlayer { + players := make([]IPlayer, 0) + for _, seat := range r.Seats { + if !seat.Empty() && proc(seat.Player()) { + players = append(players, seat.Player()) + } + } + return players +} + func (r *BaseRoom[Seat]) DebugSendMsg(user IPlayer, msgId pb.MsgId, msg proto.Message) { log.Debug(r.UserLog(user.Id(), "send msg:%v %v", msgId, msg.String())) } diff --git a/server/colorgame/model/db.go b/server/colorgame/model/db.go index 56e501b..721c91b 100644 --- a/server/colorgame/model/db.go +++ b/server/colorgame/model/db.go @@ -11,6 +11,7 @@ import ( var ( UserBindServiceRedis *redis.Client + UserRedis *redis.Client ) func InitRedis() { @@ -23,4 +24,11 @@ func InitRedis() { return } utils.AutoSetRedisPool(UserBindServiceRedis) + + UserRedis, err = db.InitRedis(cfg.Password, cfg.Host, cfg.Port, constant.Redis1User) + if err != nil { + log.Fatal(err.Error()) + return + } + utils.AutoSetRedisPool(UserRedis) } diff --git a/server/colorgame/model/trend.go b/server/colorgame/model/trend.go new file mode 100644 index 0000000..bc544b0 --- /dev/null +++ b/server/colorgame/model/trend.go @@ -0,0 +1,25 @@ +package model + +import ( + "context" + "encoding/json" + "game/common/proto/pb" +) + +const ( + trendKey = "ColorTrend" +) + +// 获取历史路途数据 +func GetTrend() [][]pb.ColorType { + v := UserRedis.Get(context.Background(), trendKey).Val() + trend := make([][]pb.ColorType, 0) + _ = json.Unmarshal([]byte(v), &trend) + return trend +} + +// 保存路途数据 +func SetTrend(trend [][]pb.ColorType) { + v, _ := json.Marshal(trend) + _ = UserRedis.Set(context.Background(), trendKey, string(v), 0).Err() +} diff --git a/server/colorgame/model/userAccount.go b/server/colorgame/model/userAccount.go deleted file mode 100644 index 0030e2a..0000000 --- a/server/colorgame/model/userAccount.go +++ /dev/null @@ -1,152 +0,0 @@ -package model - -//import ( -// "errors" -// "github.com/fox/fox/log" -// "golang.org/x/crypto/bcrypt" -// "gorm.io/gorm" -// "time" -//) -// -//const ( -// AccountNormal = 1 // 正常 -// AccountFrozen = 2 // 冻结 -// AccountBanned = 3 // 封禁 -//) -// -//// 玩家账户表 -//type UserAccount struct { -// gorm.Model -// Username string `gorm:"type:varchar(32);uniqueIndex;not null"` // 用户名 -// Password string `gorm:"type:varchar(255);not null"` // 密码哈希 -// Email string `gorm:"type:varchar(100)"` // 邮箱(可选) -// Phone string `gorm:"type:varchar(20)"` // 手机号(可选) -// DeviceID string `gorm:"type:varchar(64);index"` // 设备ID -// LastLoginIP string `gorm:"type:varchar(45)"` // 最后登录IP(支持IPv6) -// LastLoginTime time.Time // 最后登录时间 -// Status int `gorm:"type:tinyint;default:1"` // 账号状态 1-正常 2-冻结 3-封禁 -// RegisterIP string `gorm:"type:varchar(45)"` // 注册IP -// RegisterTime time.Time `gorm:"type:TIMESTAMP;default:CURRENT_TIMESTAMP"` // 注册时间 -//} -// -//// 玩家登录记录表 -//type UserLoginLog struct { -// gorm.Model -// PlayerID uint `gorm:"index"` // 关联玩家ID -// LoginIP string `gorm:"type:varchar(45);not null"` // 登录IP -// LoginTime time.Time `gorm:"type:TIMESTAMP;default:CURRENT_TIMESTAMP"` // 登录时间 -// DeviceInfo string `gorm:"type:varchar(255)"` // 设备信息(JSON格式) -// LoginResult bool // 登录结果 true-成功 false-失败 -// FailReason string `gorm:"type:varchar(100)"` // 失败原因 -//} -// -//type UserLoginOp struct { -// db *gorm.DB -// logDb *gorm.DB -//} -// -//func NewUserLoginOp() *UserLoginOp { -// return &UserLoginOp{db: UserDB, logDb: LogDB} -//} -// -//var ( -// ErrUserOrPassword = errors.New("user or password was error") -// ErrAccountFrozen = errors.New("account frozen") -// ErrAccountBanned = errors.New("account banned") -//) -// -//func (s *UserLoginOp) Login(username, password, ip, deviceID string) (*UserAccount, error) { -// var user UserAccount -// err := s.db.Where("username = ?", username).First(&user).Error -// if err != nil { -// return nil, err -// } -// // 验证密码 -// if err = bcrypt.CompareHashAndPassword([]byte(user.Password), []byte(password)); err != nil { -// s.recordLoginLog(user.ID, ip, deviceID, false, ErrUserOrPassword.Error()) -// return nil, ErrUserOrPassword -// } -// // 检查账号状态 -// switch user.Status { -// case AccountNormal: -// -// case AccountFrozen: -// s.recordLoginLog(user.ID, ip, deviceID, false, ErrAccountFrozen.Error()) -// return nil, ErrAccountFrozen -// case AccountBanned: -// s.recordLoginLog(user.ID, ip, deviceID, false, ErrAccountBanned.Error()) -// return nil, ErrAccountBanned -// } -// // 更新最后登录信息 -// user.LastLoginIP = ip -// user.LastLoginTime = time.Now() -// _ = s.db.Save(&user).Error -// -// // 记录成功登录日志 -// s.recordLoginLog(user.ID, ip, deviceID, true, "") -// -// // 6. 生成访问令牌 -// token, err := generateToken(user.ID, user.Username) -// if err != nil { -// return nil, err -// } -// user.Password = token -// return &user, err -//} -// -//// 注册新用户 -//func (s *UserLoginOp) RegisterNewUser(username, password, ip, deviceID string) (*UserAccount, error) { -// // 密码加密 -// hashedPassword, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost) -// if err != nil { -// return nil, err -// } -// user := UserAccount{ -// Username: username, -// Password: string(hashedPassword), -// DeviceID: deviceID, -// RegisterIP: ip, -// Status: 1, -// LastLoginIP: ip, -// LastLoginTime: time.Now(), -// } -// -// if err := s.db.Create(&user).Error; err != nil { -// return nil, err -// } -// -// s.recordLoginLog(user.ID, ip, deviceID, true, "") -// -// // 生成访问令牌 -// token, err := generateToken(user.ID, user.Username) -// if err != nil { -// return nil, err -// } -// user.Password = token -// -// return &user, nil -//} -// -//// 记录登录日志 -//func (s *UserLoginOp) recordLoginLog(userID uint, ip, deviceID string, success bool, failReason string) { -// logEntry := UserLoginLog{ -// PlayerID: userID, -// LoginIP: ip, -// DeviceInfo: deviceID, -// LoginResult: success, -// FailReason: failReason, -// } -// -// if err := s.logDb.Create(&logEntry).Error; err != nil { -// log.ErrorF("记录登录日志失败: %v", err) -// } -//} -// -//// 生成JWT令牌(简化版) -//func generateToken(userID uint, username string) (string, error) { -// _ = userID -// _ = username -// // 这里应该使用JWT库生成实际令牌 -// // 简化实现,实际项目中请使用安全的JWT实现 -// return "generated-token-placeholder", nil -//} diff --git a/server/colorgame/room/colorRoom.go b/server/colorgame/room/colorRoom.go index a1de2eb..76bd526 100644 --- a/server/colorgame/room/colorRoom.go +++ b/server/colorgame/room/colorRoom.go @@ -17,8 +17,7 @@ type ColorRoom struct { roomCfg *game.ColorRoomConfig timingCfg *game.ColorGameTiming status pb.ColorGameStatus - statusTime int64 // 毫秒时间戳 - users map[int64]*ColorPlayer // 所有玩家 + statusTime int64 // 毫秒时间戳 endBetAreaMul []*pb.ColorBetAreaMul // 下注结束后,每个区域更新是否爆奖以及实际赔率 ntfOpenThreeDice *pb.NtfColorOpenThreeDice // 开骰子的消息 @@ -26,40 +25,8 @@ type ColorRoom struct { jackpotMgr *jackpot.JackPotMgr // jackpotValue jackpotValue int64 // jackpot值 jackpotUser map[int64]int64 // 本局中jackpot的玩家,用于游戏内广播 - - // --------------------------------- - // Status pb.ColorPinoyLiveGameStatus // 房间状态1 表示 - // StatusTime int64 // 切换状态时的时间戳 - // - // NormalDices []byte // 普通骰子 3个 - // StartDices []byte // 初始位置摆放的骰子 3个 - // - // // DefaultLuckyDice byte // 幸运骰子 1个 每局开始 放在拉杆上的骰子 - // // DefaultNormalDices []byte // 普通骰子 3个 每局开始 放在拉杆上的骰子 - // - // totalBets [BET_TYPE_NUM]int64 // 各区域的下注统计 - // totalBet int64 // 下注统计 - // //SceneInfo model.SceneInfo // 下注的玩家列表 - // - // GameTrend *pb.ColorPinoyLiveTrend // 走势图 - // //OnlineUserList []*model.User // 所有的玩家列表 用于排序 - // PokerMsg *pb.ColorPinoyLivePokerMsg // 扑克消息 - // RoomCfg game.ColorRoomConfig // 房间配置 - // GameTiming game.ColorGameTiming // 时间配置 - // startAt int64 - // endAt int64 - // - // ServerStatus int32 - // wd *sync.WaitGroup - // TrendRedisKey string - // _aniLuckyDiceRouteIndex int32 // 幸运骰子动画路径 - // _aniThreeDiceRouteIndex int32 // 3个骰子动画路径 - // BigWinner []*pb.ColorPinoyLiveBigWinner - // LiveMgr *LiveMgr - // dealerName []string // 主播名字 - // betEndBetAreasOdds []*pb.ColorPinoyLiveGameBetAreaInfo // 下注结束后,每个区域更新是否爆奖 - // afterBetAreaOdds []*pb.ColorPinoyLiveBetAreaOdd // 开奖后,更新每个区域的赔率(主要是单色投注区开奖后,三个赔率变为1个赔率) - + bigUsers []*pb.ColorBigUser // 前6个大客户 + trend [][]pb.ColorType // 路途 } func newColorRoom(id, roomType int, srv service.IService) (baseroom.IRoom, pb.ErrCode) { @@ -75,6 +42,7 @@ func newColorRoom(id, roomType int, srv service.IService) (baseroom.IRoom, pb.Er winBetAreaMul: nil, jackpotMgr: jackpot.NewJackpotMgr(playType, model.UserRedis), } + rm.trend = rm.initGameTrend() code := pb.ErrCode_OK rm.BaseRoom, code = baseroom.NewBaseRoom[ColorSeat](id, roomType, playType, srv) @@ -82,6 +50,7 @@ func newColorRoom(id, roomType int, srv service.IService) (baseroom.IRoom, pb.Er log.ErrorF("new color room err code:%v", code) return nil, code } + // 初始化数据在OnInit之前完成 rm.OnInit() return rm, code } @@ -108,10 +77,13 @@ func (rm *ColorRoom) resetGameData() { rm.initEndBetAreaMul() rm.ntfOpenThreeDice = &pb.NtfColorOpenThreeDice{} rm.jackpotValue = rm.jackpotMgr.GetJackpot() + rm.bigUsers = rm.bigUsers[0:0] // 清理玩家身上数据 - for _, user := range rm.users { - user.resetData() + for _, st := range rm.Seats { + if st.Player() != nil { + GetPlayer(st.Player()).resetData() + } } } diff --git a/server/colorgame/room/helper.go b/server/colorgame/room/helper.go index 70c5c13..02ba1fb 100644 --- a/server/colorgame/room/helper.go +++ b/server/colorgame/room/helper.go @@ -1,13 +1,16 @@ package room import ( + "game/common/baseroom" "game/common/config/game" "game/common/proto/pb" "game/server/colorgame/config" + "game/server/colorgame/model" "github.com/fox/fox/ksync" "github.com/fox/fox/log" "github.com/fox/fox/xrand" "github.com/fox/fox/xtime" + "sort" "sync" ) @@ -246,12 +249,13 @@ func (rm *ColorRoom) CalculateJackpotScore() (pb.ColorBetArea, map[int64]int64) if winArea.PrizeType != pb.ColorPrizeType_CPT_Jackpot { continue } - for _, user := range rm.users { - if user.totalBets[winArea.Area] < 1 { - continue + rm.RangePlayer(func(u baseroom.IPlayer) bool { + user := GetPlayer(u) + if user.totalBets[winArea.Area] > 0 { + rm.jackpotMgr.AddJpUser(user.ID, user.totalBets[winArea.Area]) } - rm.jackpotMgr.AddJpUser(user.ID, user.totalBets[winArea.Area]) - } + return true + }) } rm.jackpotUser, rm.jackpotValue = rm.jackpotMgr.WinJackpot() log.Debug(rm.Log("本局是否中jackpot奖:%v, 玩家一起分走jackpot:%v, 当前jackpot值:%v", rm.jackpotValue > 0, rm.jackpotValue, rm.jackpotMgr.GetJackpot())) @@ -263,7 +267,8 @@ func (rm *ColorRoom) CalculateAllUserScore() { // 赢钱会清空jackpot池 jpArea, userJackPot := rm.CalculateJackpotScore() var jackpotUserName []string - for _, user := range rm.users { + rm.RangePlayer(func(u baseroom.IPlayer) bool { + user := GetPlayer(u) if user.totalBet > 0 { // 算分 jpScore := userJackPot[user.ID] @@ -273,7 +278,8 @@ func (rm *ColorRoom) CalculateAllUserScore() { //rm.BroadHitJackpot(user, jpScore) } } - } + return true + }) } // 计算 下注区域中奖得分 @@ -334,8 +340,9 @@ func (rm *ColorRoom) settle() { //var allWinner []*pb.ColorPinoyLiveBigWinner rm.CalculateAllUserScore() wg := new(sync.WaitGroup) - for _, user := range rm.users { + rm.RangePlayer(func(u baseroom.IPlayer) bool { wg.Add(1) + user := GetPlayer(u) ksync.GoSafe(func() { defer wg.Done() if user.totalBet > 0 { @@ -346,7 +353,8 @@ func (rm *ColorRoom) settle() { //} } }, nil) - } + return true + }) wg.Wait() //// 异步加钱完成后再执行后续的结算操作 //rm.Traverse(func(u *model.User) bool { @@ -370,6 +378,81 @@ func (rm *ColorRoom) settle() { //rm.SetGameTrend() } +// 更新大客户 +func (rm *ColorRoom) updateBigUsers() { + if rm.status > pb.ColorGameStatus_CGS_Betting && rm.status < pb.ColorGameStatus_CGS_Settle { + rm.RangePlayer(func(u baseroom.IPlayer) bool { + user := GetPlayer(u) + rm.bigUsers = append(rm.bigUsers, &pb.ColorBigUser{ + NickName: user.Nickname, + Avatar: user.AvatarUrl, + WinChips: user.totalBet, + AreaId: user.totalBets, + }) + return true + }) + + } else if rm.status >= pb.ColorGameStatus_CGS_Settle { + rm.RangePlayer(func(u baseroom.IPlayer) bool { + user := GetPlayer(u) + bigUser := &pb.ColorBigUser{ + NickName: user.Nickname, + Avatar: user.AvatarUrl, + WinChips: user.settleMsg.TotalWin, + } + for _, area := range user.settleMsg.UserAreaWin { + bigUser.AreaId = append(bigUser.AreaId, area.Win) + } + return true + }) + } + sort.Slice(rm.bigUsers, func(i, j int) bool { + return rm.bigUsers[i].WinChips > rm.bigUsers[j].WinChips + }) + if len(rm.bigUsers) > 6 { + rm.bigUsers = rm.bigUsers[0:6] + } +} + +func (rm *ColorRoom) initGameTrend() [][]pb.ColorType { + return model.GetTrend() +} + +// 更新路途 最多保存100局 +func (rm *ColorRoom) updateGameTrend() { + rm.trend = append(rm.trend, []pb.ColorType{rm.ntfOpenThreeDice.Color[0], rm.ntfOpenThreeDice.Color[1], rm.ntfOpenThreeDice.Color[2]}) + if len(rm.trend) > 100 { + rm.trend = rm.trend[len(rm.trend)-100 : len(rm.trend)] + } + model.SetTrend(rm.trend) +} + +// 更新路途 最多保存100局 +func (rm *ColorRoom) getNotifyTrend() *pb.NtfColorTrend { + ntf := &pb.NtfColorTrend{} + // 只计算30局的概率 + maxCount := 30 + partTrend := rm.trend + if len(rm.trend) > maxCount { + partTrend = rm.trend[len(rm.trend)-maxCount : len(rm.trend)] + } + for c := pb.ColorType_CT_Yellow; c <= pb.ColorType_CT_Green; c++ { + cnt := int32(0) + for _, colors := range partTrend { + for _, color := range colors { + if color == c { + cnt++ + } + } + } + ntf.ColorRate = append(ntf.ColorRate, &pb.NtfColorTrend_ColorRate{ + Color: c, + Rate: cnt * 10000 / 100, + }) + } + return ntf +} + // // import ( // "encoding/json" diff --git a/server/colorgame/room/process.go b/server/colorgame/room/process.go index f6e8908..4e8bfb6 100644 --- a/server/colorgame/room/process.go +++ b/server/colorgame/room/process.go @@ -36,6 +36,8 @@ func (rm *ColorRoom) gameSettle() { rm.setStatus(pb.ColorGameStatus_CGS_Settle) rm.settle() rm.notifySettle() + rm.updateGameTrend() + rm.notifyTrend() rm.NewTimer(TtGameStart, time.Duration(rm.timingCfg.Settle)*time.Millisecond) } diff --git a/server/colorgame/room/s2c.go b/server/colorgame/room/s2c.go index 6666543..c544669 100644 --- a/server/colorgame/room/s2c.go +++ b/server/colorgame/room/s2c.go @@ -1,6 +1,9 @@ package room -import "game/common/proto/pb" +import ( + "game/common/baseroom" + "game/common/proto/pb" +) func (rm *ColorRoom) notifyGameStart() { ntf := &pb.NtfColorGameStart{ @@ -22,22 +25,28 @@ func (rm *ColorRoom) notifyEndBetting() { Jackpot: rm.jackpotValue, } rm.Broadcast(pb.MsgId_NtfColorEndBettingId, ntf) + rm.notifyBigUsers() } func (rm *ColorRoom) notifyOpenThreeDice() { - ntf := &pb.NtfColorEndBetting{ - // EndTime: rm.statusTime + rm.timingCfg.Start, - // Jackpot: 0, - } - rm.Broadcast(pb.MsgId_NtfColorOpenThreeDiceId, ntf) + rm.Broadcast(pb.MsgId_NtfColorOpenThreeDiceId, rm.ntfOpenThreeDice) } func (rm *ColorRoom) notifySettle() { - ntf := &pb.NtfColorEndBetting{ - // EndTime: rm.statusTime + rm.timingCfg.Start, - // Jackpot: 0, - } - rm.Broadcast(pb.MsgId_NtfColorOpenThreeDiceId, ntf) + rm.RangePlayer(func(u baseroom.IPlayer) bool { + user := GetPlayer(u) + rm.SendMsg(user, pb.MsgId_NtfColorSettleId, user.settleMsg) + return true + }) + rm.notifyBigUsers() +} + +func (rm *ColorRoom) notifyBigUsers() { + rm.Broadcast(pb.MsgId_NtfColorBigUserId, &pb.NtfColorBigUser{BigUser: rm.bigUsers}) +} + +func (rm *ColorRoom) notifyTrend() { + rm.Broadcast(pb.MsgId_NtfColorTrendId, rm.getNotifyTrend()) } //