2025-12-05 20:15:55 +08:00
|
|
|
|
package model
|
|
|
|
|
|
|
2025-12-09 17:49:29 +08:00
|
|
|
|
import (
|
|
|
|
|
|
"fmt"
|
|
|
|
|
|
"math/rand"
|
|
|
|
|
|
"roserver/serverproto"
|
2026-01-08 12:45:09 +08:00
|
|
|
|
"strconv"
|
2025-12-09 17:49:29 +08:00
|
|
|
|
"strings"
|
|
|
|
|
|
)
|
|
|
|
|
|
|
2025-12-05 20:15:55 +08:00
|
|
|
|
const (
|
|
|
|
|
|
LIVEROOM_STAGE_NULL = 0
|
|
|
|
|
|
LIVEROOM_STAGE_IDLE = 1
|
|
|
|
|
|
LIVEROOM_STAGE_READY = 2
|
|
|
|
|
|
LIVEROOM_STAGE_SHOW = 3
|
|
|
|
|
|
LIVEROOM_STAGE_RESULT = 4
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
const (
|
|
|
|
|
|
LIVEROOM_STAGE_IDLE_TIMER = 0
|
|
|
|
|
|
LIVEROOM_STAGE_SHOW_READY_TIMER = 15
|
|
|
|
|
|
LIVEROOM_STAGE_SHOWING_TIMER = 30
|
|
|
|
|
|
LIVEROOM_STAGE_SHOW_END_TIMER = 5
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
const (
|
2025-12-12 11:48:39 +08:00
|
|
|
|
LIVEROOM_TYPE_CARD = 1
|
|
|
|
|
|
LIVEROOM_TYPE_SELL = 2
|
|
|
|
|
|
LIVEROOM_TYPE_ANSWER = 3
|
|
|
|
|
|
LIVEROOM_TYPE_GAME = 4
|
2025-12-12 19:20:10 +08:00
|
|
|
|
LIVEROOM_TYPE_BID = 5
|
|
|
|
|
|
LIVEROOM_TYPE_SEND_GIFT = 6
|
2025-12-05 20:15:55 +08:00
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
const (
|
|
|
|
|
|
LIVEROOM_CARD_MAX_READY_NUM = 5
|
|
|
|
|
|
LIVEROOM_SELL_MAX_READY_NUM = 100
|
|
|
|
|
|
LIVEROOM_ANSWER_MAX_READY_NUM = 100
|
|
|
|
|
|
LIVEROOM_GAME_MAX_READY_NUM = 100
|
2025-12-12 19:20:10 +08:00
|
|
|
|
LIVEROOM_BID_MAX_READY_NUM = 100
|
2025-12-05 20:15:55 +08:00
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
const (
|
|
|
|
|
|
LIVEROOM_MAX_PLAYER_NUM = 1000
|
|
|
|
|
|
LIVEROOM_TICK_INTERVAL = 1000
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
const (
|
|
|
|
|
|
LIVEROOM_CMD_TALK = 1
|
|
|
|
|
|
LIVEROOM_CMD_PLAY = 2
|
|
|
|
|
|
)
|
|
|
|
|
|
|
2025-12-12 19:20:10 +08:00
|
|
|
|
const (
|
|
|
|
|
|
LIVEROOM_CMD_PLAY_PARAM_BidPreview = "BidPreview"
|
|
|
|
|
|
LIVEROOM_CMD_PLAY_PARAM_Bid = "Bid"
|
|
|
|
|
|
)
|
|
|
|
|
|
|
2025-12-05 20:15:55 +08:00
|
|
|
|
// 泛型按值删除
|
|
|
|
|
|
func SliceRemoveByValue[T comparable](slice []T, value T) []T {
|
|
|
|
|
|
result := slice[:0]
|
|
|
|
|
|
for _, v := range slice {
|
|
|
|
|
|
if v != value {
|
|
|
|
|
|
result = append(result, v)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
return truncateSlice(result)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 截断切片容量,避免内存泄漏
|
|
|
|
|
|
func truncateSlice[T any](slice []T) []T {
|
|
|
|
|
|
return slice[:len(slice):len(slice)]
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
func SliceIsExist[T comparable](slice []T, value T) bool {
|
|
|
|
|
|
for _, v := range slice {
|
|
|
|
|
|
if v == value {
|
|
|
|
|
|
return true
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
return false
|
|
|
|
|
|
}
|
2025-12-09 17:49:29 +08:00
|
|
|
|
|
2025-12-13 17:06:18 +08:00
|
|
|
|
func WeightedRandomChoice(weights map[string]int) string {
|
|
|
|
|
|
total := 0
|
|
|
|
|
|
for _, w := range weights {
|
|
|
|
|
|
total += w
|
|
|
|
|
|
}
|
|
|
|
|
|
r := rand.Intn(total)
|
|
|
|
|
|
current := 0
|
|
|
|
|
|
for item, weight := range weights {
|
|
|
|
|
|
current += weight
|
|
|
|
|
|
if r < current {
|
|
|
|
|
|
return item
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
var lastItem string
|
|
|
|
|
|
for item := range weights {
|
|
|
|
|
|
lastItem = item
|
|
|
|
|
|
}
|
|
|
|
|
|
return lastItem
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-12-12 19:20:10 +08:00
|
|
|
|
func SliceContains[T any](slice []T, predicate func(T) bool) bool {
|
|
|
|
|
|
for _, v := range slice {
|
|
|
|
|
|
if predicate(v) {
|
|
|
|
|
|
return true
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
return false
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-01-08 12:45:09 +08:00
|
|
|
|
func SliceFind[T any](slice []T, predicate func(T) bool) (T, bool) {
|
|
|
|
|
|
for _, v := range slice {
|
|
|
|
|
|
if predicate(v) {
|
|
|
|
|
|
return v, true
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
var zero T
|
|
|
|
|
|
return zero, false
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-12-09 17:49:29 +08:00
|
|
|
|
// 从 map 中随机选择 N 个键
|
|
|
|
|
|
func RandomKeys[K comparable, V any](m map[K]V, n int) []K {
|
|
|
|
|
|
if n <= 0 || len(m) == 0 {
|
|
|
|
|
|
return []K{}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 如果请求的数量大于 map 大小,返回所有键
|
|
|
|
|
|
if n >= len(m) {
|
|
|
|
|
|
keys := make([]K, 0, len(m))
|
|
|
|
|
|
for k := range m {
|
|
|
|
|
|
keys = append(keys, k)
|
|
|
|
|
|
}
|
|
|
|
|
|
return keys
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 获取所有键
|
|
|
|
|
|
allKeys := make([]K, 0, len(m))
|
|
|
|
|
|
for k := range m {
|
|
|
|
|
|
allKeys = append(allKeys, k)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Fisher-Yates 洗牌算法
|
|
|
|
|
|
rand.Shuffle(len(allKeys), func(i, j int) {
|
|
|
|
|
|
allKeys[i], allKeys[j] = allKeys[j], allKeys[i]
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
// 返回前 N 个
|
|
|
|
|
|
return allKeys[:n]
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
func ShuffleInPlace[T any](slice []T) {
|
|
|
|
|
|
rand.Shuffle(len(slice), func(i, j int) {
|
|
|
|
|
|
slice[i], slice[j] = slice[j], slice[i]
|
|
|
|
|
|
})
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
func JoinSlice[T any](slice []T, sep string) string {
|
|
|
|
|
|
if len(slice) == 0 {
|
|
|
|
|
|
return ""
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
var sb strings.Builder
|
|
|
|
|
|
for i, item := range slice {
|
|
|
|
|
|
if i > 0 {
|
|
|
|
|
|
sb.WriteString(sep)
|
|
|
|
|
|
}
|
|
|
|
|
|
sb.WriteString(fmt.Sprint(item))
|
|
|
|
|
|
}
|
|
|
|
|
|
return sb.String()
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
func MakeLiveRoomPlayerInfo(uid uint64) *serverproto.LiveRoomPlayerInfo {
|
|
|
|
|
|
currPlayer := RoomMgr.GetPlayer(uid)
|
|
|
|
|
|
return &serverproto.LiveRoomPlayerInfo{
|
2025-12-12 11:48:39 +08:00
|
|
|
|
Uid: uid,
|
2025-12-09 17:49:29 +08:00
|
|
|
|
Nickname: currPlayer.Name,
|
|
|
|
|
|
Level: currPlayer.Level,
|
|
|
|
|
|
HeadId: currPlayer.HeadId,
|
|
|
|
|
|
HeadTitle: currPlayer.HeadTitle,
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 获取前N个元素,自动处理边界情况
|
|
|
|
|
|
func GetFirstN[T any](slice []T, n int) []T {
|
|
|
|
|
|
if n <= 0 {
|
|
|
|
|
|
return []T{} // 返回空切片
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 如果n大于切片长度,返回整个切片的副本
|
|
|
|
|
|
if n >= len(slice) {
|
|
|
|
|
|
result := make([]T, len(slice))
|
|
|
|
|
|
copy(result, slice)
|
|
|
|
|
|
return result
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 正常情况:返回前n个元素的副本
|
|
|
|
|
|
result := make([]T, n)
|
|
|
|
|
|
copy(result, slice[:n])
|
|
|
|
|
|
return result
|
|
|
|
|
|
}
|
2026-01-08 12:45:09 +08:00
|
|
|
|
|
|
|
|
|
|
type ProbabilityItem struct {
|
|
|
|
|
|
ID int
|
|
|
|
|
|
Probability int
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
func parseProbabilityStr(str string) ([]ProbabilityItem, error) {
|
|
|
|
|
|
if str == "" {
|
|
|
|
|
|
return nil, fmt.Errorf("概率字符串为空")
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
items := make([]ProbabilityItem, 0)
|
|
|
|
|
|
pairs := strings.Split(str, ";")
|
|
|
|
|
|
|
|
|
|
|
|
for _, pair := range pairs {
|
|
|
|
|
|
if pair == "" {
|
|
|
|
|
|
continue
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
parts := strings.Split(pair, ":")
|
|
|
|
|
|
if len(parts) != 2 {
|
|
|
|
|
|
return nil, fmt.Errorf("格式错误: %s", pair)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
id, err := strconv.Atoi(strings.TrimSpace(parts[0]))
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
return nil, fmt.Errorf("ID转换错误: %s", parts[0])
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
prob, err := strconv.Atoi(strings.TrimSpace(parts[1]))
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
return nil, fmt.Errorf("概率转换错误: %s", parts[1])
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if prob < 0 {
|
|
|
|
|
|
return nil, fmt.Errorf("概率不能为负数: %d", prob)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
items = append(items, ProbabilityItem{
|
|
|
|
|
|
ID: id,
|
|
|
|
|
|
Probability: prob,
|
|
|
|
|
|
})
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return items, nil
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// SelectByProbability 根据概率随机选择一项
|
|
|
|
|
|
func selectByProbability(items []ProbabilityItem) (int, error) {
|
|
|
|
|
|
if len(items) == 0 {
|
|
|
|
|
|
return 0, fmt.Errorf("概率项列表为空")
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 计算总概率
|
|
|
|
|
|
totalProb := 0
|
|
|
|
|
|
for _, item := range items {
|
|
|
|
|
|
totalProb += item.Probability
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if totalProb <= 0 {
|
|
|
|
|
|
// 如果所有概率都为0,则从所有ID中随机选择一个
|
|
|
|
|
|
randIdx := rand.Intn(len(items))
|
|
|
|
|
|
return items[randIdx].ID, nil
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 生成随机数
|
|
|
|
|
|
randomValue := rand.Intn(totalProb)
|
|
|
|
|
|
|
|
|
|
|
|
// 根据概率选择
|
|
|
|
|
|
currentSum := 0
|
|
|
|
|
|
for _, item := range items {
|
|
|
|
|
|
currentSum += item.Probability
|
|
|
|
|
|
if randomValue < currentSum {
|
|
|
|
|
|
return item.ID, nil
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 理论上不会执行到这里
|
|
|
|
|
|
return items[len(items)-1].ID, nil
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// -1:50;601:50;602:30;603:10;604:10;605:5;606:5;607:0;608:0;609:0
|
|
|
|
|
|
func RollForRateCfg(str string) (int, error) {
|
|
|
|
|
|
items, _ := parseProbabilityStr(str)
|
|
|
|
|
|
return selectByProbability(items)
|
|
|
|
|
|
}
|
2026-01-12 19:27:32 +08:00
|
|
|
|
|
|
|
|
|
|
func MapToString(m map[string]string) string {
|
|
|
|
|
|
if len(m) == 0 {
|
|
|
|
|
|
return ""
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
var builder strings.Builder
|
|
|
|
|
|
|
|
|
|
|
|
// 有序输出(按key排序)
|
|
|
|
|
|
keys := make([]string, 0, len(m))
|
|
|
|
|
|
for k := range m {
|
|
|
|
|
|
keys = append(keys, k)
|
|
|
|
|
|
}
|
|
|
|
|
|
// 排序(可选)
|
|
|
|
|
|
//sort.Strings(keys)
|
|
|
|
|
|
|
|
|
|
|
|
for i, k := range keys {
|
|
|
|
|
|
builder.WriteString(k)
|
|
|
|
|
|
builder.WriteString(":")
|
|
|
|
|
|
builder.WriteString(m[k])
|
|
|
|
|
|
if i < len(keys)-1 {
|
|
|
|
|
|
builder.WriteString(";")
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return builder.String()
|
|
|
|
|
|
}
|