diff --git a/README.md b/README.md index 4b2d07d..7d6fa25 100644 --- a/README.md +++ b/README.md @@ -13,12 +13,12 @@ mindmap root((Minotaur)) /component 通用组件接口定义 /components 通用组件内置实现 - /config 针对配置导表的配置加载 + /configuration 配置管理功能 /game 游戏通用功能接口定义 /builtin 游戏通用功能内置实现 /notify 通知功能接口定义 /planner 策划相关工具目录 - /configexport 配置导表功能实现 + /pce 配置导表功能实现 /report 数据埋点及上报功能 /server 网络服务器支持 /cross 内置跨服功能实现 @@ -113,7 +113,6 @@ func main() { ``` 其他的一些支持事件的结构体(非所有): - `game.Room` 游戏房间实现 - - `synchronization.Map` 并发安全的`Map`实现 - ... ### 可选项 大部分的 `New` 函数均可使用可选项进行创建,具体函数前缀通常为 `With`。 diff --git a/component/components/lockstep.go b/component/components/lockstep.go index eb21c6f..8b40d01 100644 --- a/component/components/lockstep.go +++ b/component/components/lockstep.go @@ -3,7 +3,7 @@ package components import ( "encoding/json" "github.com/kercylan98/minotaur/component" - "github.com/kercylan98/minotaur/utils/synchronization" + "github.com/kercylan98/minotaur/utils/concurrent" "github.com/kercylan98/minotaur/utils/timer" "sync" "sync/atomic" @@ -13,8 +13,8 @@ import ( // NewLockstep 创建一个锁步(帧)同步默认实现的组件(Lockstep)进行返回 func NewLockstep[ClientID comparable, Command any](options ...LockstepOption[ClientID, Command]) *Lockstep[ClientID, Command] { lockstep := &Lockstep[ClientID, Command]{ - clients: synchronization.NewMap[ClientID, component.LockstepClient[ClientID]](), - frames: synchronization.NewMap[int, []Command](), + clients: concurrent.NewBalanceMap[ClientID, component.LockstepClient[ClientID]](), + frames: concurrent.NewBalanceMap[int, []Command](), ticker: timer.GetTicker(10), frameRate: 15, serialization: func(frame int, commands []Command) []byte { @@ -25,7 +25,7 @@ func NewLockstep[ClientID comparable, Command any](options ...LockstepOption[Cli data, _ := json.Marshal(frameStruct) return data }, - clientCurrentFrame: synchronization.NewMap[ClientID, int](), + clientCurrentFrame: concurrent.NewBalanceMap[ClientID, int](), } for _, option := range options { option(lockstep) @@ -40,12 +40,12 @@ func NewLockstep[ClientID comparable, Command any](options ...LockstepOption[Cli // - 从特定帧开始追帧 // - 兼容各种基于TCP/UDP/Unix的网络类型,可通过客户端实现其他网络类型同步 type Lockstep[ClientID comparable, Command any] struct { - clients *synchronization.Map[ClientID, component.LockstepClient[ClientID]] // 接受广播的客户端 - frames *synchronization.Map[int, []Command] // 所有帧指令 - ticker *timer.Ticker // 定时器 - frameMutex sync.Mutex // 帧锁 - currentFrame int // 当前帧 - clientCurrentFrame *synchronization.Map[ClientID, int] // 客户端当前帧数 + clients *concurrent.BalanceMap[ClientID, component.LockstepClient[ClientID]] // 接受广播的客户端 + frames *concurrent.BalanceMap[int, []Command] // 所有帧指令 + ticker *timer.Ticker // 定时器 + frameMutex sync.Mutex // 帧锁 + currentFrame int // 当前帧 + clientCurrentFrame *concurrent.BalanceMap[ClientID, int] // 客户端当前帧数 running atomic.Bool frameRate int // 帧率(每秒N帧) @@ -123,8 +123,8 @@ func (slf *Lockstep[ClientID, Command]) StopBroadcast() { // AddCommand 添加命令到当前帧 func (slf *Lockstep[ClientID, Command]) AddCommand(command Command) { - slf.frames.AtomGetSet(slf.currentFrame, func(value []Command, exist bool) (newValue []Command, isSet bool) { - return append(value, command), true + slf.frames.Atom(func(m map[int][]Command) { + m[slf.currentFrame] = append(m[slf.currentFrame], command) }) } diff --git a/game/builtin/attrs.go b/game/builtin/attrs.go index 04b2fcd..8dc2683 100644 --- a/game/builtin/attrs.go +++ b/game/builtin/attrs.go @@ -2,18 +2,18 @@ package builtin import ( "github.com/kercylan98/minotaur/game" + "github.com/kercylan98/minotaur/utils/concurrent" "github.com/kercylan98/minotaur/utils/huge" - "github.com/kercylan98/minotaur/utils/synchronization" ) func NewAttrs() *Attrs { return &Attrs{ - attrs: synchronization.NewMap[int, any](), + attrs: concurrent.NewBalanceMap[int, any](), } } type Attrs struct { - attrs *synchronization.Map[int, any] + attrs *concurrent.BalanceMap[int, any] attrChangeEventHandles []game.AttrChangeEventHandle attrIdChangeEventHandles map[int][]game.AttrChangeEventHandle diff --git a/game/builtin/ranking_list.go b/game/builtin/ranking_list.go index 9d144e0..e366883 100644 --- a/game/builtin/ranking_list.go +++ b/game/builtin/ranking_list.go @@ -3,15 +3,15 @@ package builtin import ( "encoding/json" "github.com/kercylan98/minotaur/game" + "github.com/kercylan98/minotaur/utils/concurrent" "github.com/kercylan98/minotaur/utils/generic" - "github.com/kercylan98/minotaur/utils/synchronization" ) // NewRankingList 创建一个排名从0开始的排行榜 func NewRankingList[CompetitorID comparable, Score generic.Ordered](options ...RankingListOption[CompetitorID, Score]) *RankingList[CompetitorID, Score] { rankingList := &RankingList[CompetitorID, Score]{ rankCount: 100, - competitors: synchronization.NewMap[CompetitorID, Score](), + competitors: concurrent.NewBalanceMap[CompetitorID, Score](), } for _, option := range options { option(rankingList) @@ -22,7 +22,7 @@ func NewRankingList[CompetitorID comparable, Score generic.Ordered](options ...R type RankingList[CompetitorID comparable, Score generic.Ordered] struct { asc bool rankCount int - competitors *synchronization.Map[CompetitorID, Score] + competitors *concurrent.BalanceMap[CompetitorID, Score] scores []*scoreItem[CompetitorID, Score] // CompetitorID, Score rankChangeEventHandles []game.RankChangeEventHandle[CompetitorID, Score] @@ -244,11 +244,11 @@ func (slf *RankingList[CompetitorID, Score]) competitor(competitorId CompetitorI func (slf *RankingList[CompetitorID, Score]) UnmarshalJSON(bytes []byte) error { var t struct { - Competitors *synchronization.Map[CompetitorID, Score] `json:"competitors,omitempty"` - Scores []*scoreItem[CompetitorID, Score] `json:"scores,omitempty"` - Asc bool `json:"asc,omitempty"` + Competitors *concurrent.BalanceMap[CompetitorID, Score] `json:"competitors,omitempty"` + Scores []*scoreItem[CompetitorID, Score] `json:"scores,omitempty"` + Asc bool `json:"asc,omitempty"` } - t.Competitors = synchronization.NewMap[CompetitorID, Score]() + t.Competitors = concurrent.NewBalanceMap[CompetitorID, Score]() if err := json.Unmarshal(bytes, &t); err != nil { return err } @@ -260,9 +260,9 @@ func (slf *RankingList[CompetitorID, Score]) UnmarshalJSON(bytes []byte) error { func (slf *RankingList[CompetitorID, Score]) MarshalJSON() ([]byte, error) { var t struct { - Competitors *synchronization.Map[CompetitorID, Score] `json:"competitors,omitempty"` - Scores []*scoreItem[CompetitorID, Score] `json:"scores,omitempty"` - Asc bool `json:"asc,omitempty"` + Competitors *concurrent.BalanceMap[CompetitorID, Score] `json:"competitors,omitempty"` + Scores []*scoreItem[CompetitorID, Score] `json:"scores,omitempty"` + Asc bool `json:"asc,omitempty"` } t.Competitors = slf.competitors t.Scores = slf.scores diff --git a/game/builtin/room.go b/game/builtin/room.go index 457d5b0..86aabee 100644 --- a/game/builtin/room.go +++ b/game/builtin/room.go @@ -2,8 +2,7 @@ package builtin import ( "github.com/kercylan98/minotaur/game" - "github.com/kercylan98/minotaur/utils/asynchronous" - "github.com/kercylan98/minotaur/utils/hash" + "github.com/kercylan98/minotaur/utils/concurrent" "github.com/kercylan98/minotaur/utils/log" ) @@ -11,7 +10,7 @@ import ( func NewRoom[PlayerID comparable, Player game.Player[PlayerID]](guid int64, options ...RoomOption[PlayerID, Player]) *Room[PlayerID, Player] { room := &Room[PlayerID, Player]{ guid: guid, - players: asynchronous.NewMap[PlayerID, Player](), + players: concurrent.NewBalanceMap[PlayerID, Player](), } for _, option := range options { option(room) @@ -27,7 +26,7 @@ type Room[PlayerID comparable, Player game.Player[PlayerID]] struct { owner PlayerID noMaster bool playerLimit int - players hash.Map[PlayerID, Player] + players *concurrent.BalanceMap[PlayerID, Player] kickCheckHandle func(room *Room[PlayerID, Player], id, target PlayerID) error playerJoinRoomEventHandles []game.PlayerJoinRoomEventHandle[PlayerID, Player] @@ -51,8 +50,8 @@ func (slf *Room[PlayerID, Player]) GetPlayer(id PlayerID) Player { } // GetPlayers 获取所有玩家 -func (slf *Room[PlayerID, Player]) GetPlayers() hash.MapReadonly[PlayerID, Player] { - return slf.players.(hash.MapReadonly[PlayerID, Player]) +func (slf *Room[PlayerID, Player]) GetPlayers() map[PlayerID]Player { + return slf.players.Map() } // GetPlayerCount 获取玩家数量 diff --git a/game/builtin/room_manager.go b/game/builtin/room_manager.go index bd13d08..86f17c3 100644 --- a/game/builtin/room_manager.go +++ b/game/builtin/room_manager.go @@ -2,20 +2,20 @@ package builtin import ( "github.com/kercylan98/minotaur/game" - "github.com/kercylan98/minotaur/utils/synchronization" + "github.com/kercylan98/minotaur/utils/concurrent" "sync/atomic" ) func NewRoomManager[PlayerID comparable, Room game.Room[PlayerID, game.Player[PlayerID]]]() *RoomManager[PlayerID, Room] { return &RoomManager[PlayerID, Room]{ - rooms: synchronization.NewMap[int64, Room](), + rooms: concurrent.NewBalanceMap[int64, Room](), } } // RoomManager 房间管理器 type RoomManager[PlayerID comparable, Room game.Room[PlayerID, game.Player[PlayerID]]] struct { guid atomic.Int64 - rooms *synchronization.Map[int64, Room] + rooms *concurrent.BalanceMap[int64, Room] } // GenGuid 生成一个新的房间guid diff --git a/game/builtin/room_options.go b/game/builtin/room_options.go index 0b9801d..b67a6ec 100644 --- a/game/builtin/room_options.go +++ b/game/builtin/room_options.go @@ -2,7 +2,7 @@ package builtin import ( "github.com/kercylan98/minotaur/game" - "github.com/kercylan98/minotaur/utils/synchronization" + "github.com/kercylan98/minotaur/utils/concurrent" ) // RoomOption 房间构建可选项 @@ -11,7 +11,7 @@ type RoomOption[PlayerID comparable, Player game.Player[PlayerID]] func(room *Ro // WithRoomSync 通过线程安全的方式创建房间 func WithRoomSync[PlayerID comparable, Player game.Player[PlayerID]]() RoomOption[PlayerID, Player] { return func(room *Room[PlayerID, Player]) { - room.players = synchronization.NewMap[PlayerID, Player]() + room.players = concurrent.NewBalanceMap[PlayerID, Player]() } } diff --git a/game/builtin/room_seat.go b/game/builtin/room_seat.go index 2fc31ea..55ad7f7 100644 --- a/game/builtin/room_seat.go +++ b/game/builtin/room_seat.go @@ -2,7 +2,7 @@ package builtin import ( "github.com/kercylan98/minotaur/game" - "github.com/kercylan98/minotaur/utils/asynchronous" + "github.com/kercylan98/minotaur/utils/concurrent" "github.com/kercylan98/minotaur/utils/hash" "github.com/kercylan98/minotaur/utils/slice" "sync" @@ -12,7 +12,7 @@ import ( func NewRoomSeat[PlayerID comparable, Player game.Player[PlayerID]](room game.Room[PlayerID, Player], options ...RoomSeatOption[PlayerID, Player]) *RoomSeat[PlayerID, Player] { roomSeat := &RoomSeat[PlayerID, Player]{ Room: room, - seatPS: asynchronous.NewMap[PlayerID, int](), + seatPS: concurrent.NewBalanceMap[PlayerID, int](), } for _, option := range options { option(roomSeat) @@ -26,7 +26,7 @@ type RoomSeat[PlayerID comparable, Player game.Player[PlayerID]] struct { game.Room[PlayerID, Player] mutex sync.RWMutex vacancy []int - seatPS hash.Map[PlayerID, int] + seatPS *concurrent.BalanceMap[PlayerID, int] seatSP []*PlayerID duplicateLock bool fillIn bool @@ -240,6 +240,41 @@ func (slf *RoomSeat[PlayerID, Player]) GetNextSeatVacancy(seat int) int { return seat } +// GetPrevSeat 获取特定座位号上一个未缺席的座位号 +func (slf *RoomSeat[PlayerID, Player]) GetPrevSeat(seat int) int { + l := len(slf.seatSP) + if l == 0 || seat >= l || seat < 0 { + return -1 + } + var target = seat + for { + target-- + if target < 0 { + target = l - 1 + } + if target == seat { + return seat + } + if slf.seatSP[target] != nil { + return target + } + } +} + +// GetPrevSeatVacancy 获取特定座位号上一个座位号 +// - 缺席将不会被忽略 +func (slf *RoomSeat[PlayerID, Player]) GetPrevSeatVacancy(seat int) int { + l := len(slf.seatSP) + if l == 0 || seat >= l || seat < 0 { + return -1 + } + seat-- + if seat < 0 { + seat = l - 1 + } + return seat +} + func (slf *RoomSeat[PlayerID, Player]) onJoinRoom(room game.Room[PlayerID, Player], player Player) { slf.AddSeat(player.GetID()) } diff --git a/game/builtin/world.go b/game/builtin/world.go index 463b76d..f86e794 100644 --- a/game/builtin/world.go +++ b/game/builtin/world.go @@ -2,9 +2,8 @@ package builtin import ( "github.com/kercylan98/minotaur/game" - "github.com/kercylan98/minotaur/utils/hash" + "github.com/kercylan98/minotaur/utils/concurrent" "github.com/kercylan98/minotaur/utils/log" - "github.com/kercylan98/minotaur/utils/synchronization" "sync/atomic" ) @@ -12,10 +11,10 @@ import ( func NewWorld[PlayerID comparable, Player game.Player[PlayerID]](guid int64, options ...WorldOption[PlayerID, Player]) *World[PlayerID, Player] { world := &World[PlayerID, Player]{ guid: guid, - players: synchronization.NewMap[PlayerID, Player](), - playerActors: synchronization.NewMap[PlayerID, hash.Map[int64, game.Actor]](), - owners: synchronization.NewMap[int64, PlayerID](), - actors: synchronization.NewMap[int64, game.Actor](), + players: concurrent.NewBalanceMap[PlayerID, Player](), + playerActors: concurrent.NewBalanceMap[PlayerID, *concurrent.BalanceMap[int64, game.Actor]](), + owners: concurrent.NewBalanceMap[int64, PlayerID](), + actors: concurrent.NewBalanceMap[int64, game.Actor](), } for _, option := range options { option(world) @@ -28,10 +27,10 @@ type World[PlayerID comparable, Player game.Player[PlayerID]] struct { guid int64 actorGuid atomic.Int64 playerLimit int - players hash.Map[PlayerID, Player] - playerActors hash.Map[PlayerID, hash.Map[int64, game.Actor]] - owners hash.Map[int64, PlayerID] - actors hash.Map[int64, game.Actor] + players *concurrent.BalanceMap[PlayerID, Player] + playerActors *concurrent.BalanceMap[PlayerID, *concurrent.BalanceMap[int64, game.Actor]] + owners *concurrent.BalanceMap[int64, PlayerID] + actors *concurrent.BalanceMap[int64, game.Actor] playerJoinWorldEventHandles []game.PlayerJoinWorldEventHandle[PlayerID, Player] playerLeaveWorldEventHandles []game.PlayerLeaveWorldEventHandle[PlayerID, Player] @@ -56,16 +55,16 @@ func (slf *World[PlayerID, Player]) GetPlayer(id PlayerID) Player { return slf.players.Get(id) } -func (slf *World[PlayerID, Player]) GetPlayers() hash.MapReadonly[PlayerID, Player] { - return slf.players.(hash.MapReadonly[PlayerID, Player]) +func (slf *World[PlayerID, Player]) GetPlayers() map[PlayerID]Player { + return slf.players.Map() } func (slf *World[PlayerID, Player]) GetActor(guid int64) game.Actor { return slf.actors.Get(guid) } -func (slf *World[PlayerID, Player]) GetActors() hash.MapReadonly[int64, game.Actor] { - return slf.actors.(hash.MapReadonly[int64, game.Actor]) +func (slf *World[PlayerID, Player]) GetActors() map[int64]game.Actor { + return slf.actors.Map() } func (slf *World[PlayerID, Player]) GetPlayerActor(id PlayerID, guid int64) game.Actor { @@ -75,8 +74,8 @@ func (slf *World[PlayerID, Player]) GetPlayerActor(id PlayerID, guid int64) game return nil } -func (slf *World[PlayerID, Player]) GetPlayerActors(id PlayerID) hash.MapReadonly[int64, game.Actor] { - return slf.playerActors.Get(id).(hash.MapReadonly[int64, game.Actor]) +func (slf *World[PlayerID, Player]) GetPlayerActors(id PlayerID) map[int64]game.Actor { + return slf.playerActors.Get(id).Map() } func (slf *World[PlayerID, Player]) IsExistPlayer(id PlayerID) bool { @@ -105,7 +104,7 @@ func (slf *World[PlayerID, Player]) Join(player Player) error { log.Debug("World.Join", log.Int64("guid", slf.GetGuid()), log.Any("player", player.GetID())) slf.players.Set(player.GetID(), player) if actors := slf.playerActors.Get(player.GetID()); actors == nil { - actors = synchronization.NewMap[int64, game.Actor]() + actors = concurrent.NewBalanceMap[int64, game.Actor]() slf.playerActors.Set(player.GetID(), actors) } slf.OnPlayerJoinWorldEvent(player) @@ -119,9 +118,10 @@ func (slf *World[PlayerID, Player]) Leave(id PlayerID) { } log.Debug("World.Leave", log.Int64("guid", slf.GetGuid()), log.Any("player", player.GetID())) slf.OnPlayerLeaveWorldEvent(player) - slf.playerActors.Get(player.GetID()).Range(func(guid int64, actor game.Actor) { + slf.playerActors.Get(player.GetID()).Range(func(guid int64, actor game.Actor) bool { slf.OnActorAnnihilationEvent(actor) slf.owners.Delete(guid) + return false }) slf.playerActors.Delete(player.GetID()) slf.players.Delete(player.GetID()) @@ -171,8 +171,9 @@ func (slf *World[PlayerID, Player]) RemoveActorOwner(guid int64) { func (slf *World[PlayerID, Player]) Reset() { log.Debug("World", log.Int64("Reset", slf.guid)) slf.players.Clear() - slf.playerActors.Range(func(id PlayerID, actors hash.Map[int64, game.Actor]) { + slf.playerActors.Range(func(id PlayerID, actors *concurrent.BalanceMap[int64, game.Actor]) bool { actors.Clear() + return false }) slf.playerActors.Clear() slf.owners.Clear() diff --git a/game/poker/card.go b/game/poker/card.go index 4aa0536..cafb8dd 100644 --- a/game/poker/card.go +++ b/game/poker/card.go @@ -1,159 +1,13 @@ package poker -import ( - "fmt" - "math" -) - -// NewCard 创建一张扑克牌 -// - 当 point 为 PointBlackJoker 或 PointRedJoker 时,color 将没有效果 -func NewCard(point Point, color Color) Card { - if point == PointRedJoker || point == PointBlackJoker { - color = ColorNone - } - card := Card{ - point: point, - color: color, - } - return card -} +import "github.com/kercylan98/minotaur/utils/generic" // Card 扑克牌 -type Card struct { - point Point - color Color -} - -// GetPoint 返回扑克牌的点数 -func (slf Card) GetPoint() Point { - return slf.point -} - -// GetColor 返回扑克牌的花色 -func (slf Card) GetColor() Color { - if slf.point == PointRedJoker || slf.point == PointBlackJoker { - return ColorNone - } - return slf.color -} - -// GetPointAndColor 返回扑克牌的点数和花色 -func (slf Card) GetPointAndColor() (Point, Color) { - return slf.GetPoint(), slf.GetColor() -} - -// EqualPoint 比较与另一张扑克牌的点数是否相同 -func (slf Card) EqualPoint(card Card) bool { - return slf.GetPoint() == card.GetPoint() -} - -// EqualColor 比较与另一张扑克牌的花色是否相同 -func (slf Card) EqualColor(card Card) bool { - return slf.GetColor() == card.GetColor() -} - -// Equal 比较与另一张扑克牌的点数和花色是否相同 -func (slf Card) Equal(card Card) bool { - return slf.GetPoint() == card.GetPoint() && slf.GetColor() == card.GetColor() -} - -// MaxPoint 返回两张扑克牌中点数较大的一张 -func (slf Card) MaxPoint(card Card) Card { - if slf.GetPoint() > card.GetPoint() { - return slf - } - return card -} - -// MinPoint 返回两张扑克牌中点数较小的一张 -func (slf Card) MinPoint(card Card) Card { - if slf.GetPoint() < card.GetPoint() { - return slf - } - return card -} - -// MaxColor 返回两张扑克牌中花色较大的一张 -func (slf Card) MaxColor(card Card) Card { - if slf.GetColor() > card.GetColor() { - return slf - } - return card -} - -// MinColor 返回两张扑克牌中花色较小的一张 -func (slf Card) MinColor(card Card) Card { - if slf.GetColor() < card.GetColor() { - return slf - } - return card -} - -// Max 返回两张扑克牌中点数和花色较大的一张 -func (slf Card) Max(card Card) Card { - if slf.GetPoint() > card.GetPoint() { - return slf - } else if slf.GetPoint() < card.GetPoint() { - return card - } else { - if slf.GetColor() > card.GetColor() { - return slf - } - return card - } -} - -// Min 返回两张扑克牌中点数和花色较小的一张 -func (slf Card) Min(card Card) Card { - if slf.GetPoint() < card.GetPoint() { - return slf - } else if slf.GetPoint() > card.GetPoint() { - return card - } else { - if slf.GetColor() < card.GetColor() { - return slf - } - return card - } -} - -// IsJoker 判断是否为大小王 -func (slf Card) IsJoker() bool { - point := slf.GetPoint() - return point == PointRedJoker || point == PointBlackJoker -} - -// CalcPointDifference 计算两张扑克牌的点数差 -func (slf Card) CalcPointDifference(card Card) int { - return int(slf.GetPoint()) - int(card.GetPoint()) -} - -// CalcPointDifferenceAbs 计算两张扑克牌的点数差的绝对值 -func (slf Card) CalcPointDifferenceAbs(card Card) int { - return int(math.Abs(float64(slf.CalcPointDifference(card)))) -} - -// CalcColorDifference 计算两张扑克牌的花色差 -func (slf Card) CalcColorDifference(card Card) int { - return int(slf.GetColor()) - int(card.GetColor()) -} - -// CalcColorDifferenceAbs 计算两张扑克牌的花色差的绝对值 -func (slf Card) CalcColorDifferenceAbs(card Card) int { - return int(math.Abs(float64(slf.CalcColorDifference(card)))) -} - -// IsNeighborPoint 判断两张扑克牌是否为相邻的点数 -func (slf Card) IsNeighborPoint(card Card) bool { - return slf.CalcPointDifferenceAbs(card) == 1 -} - -// IsNeighborColor 判断两张扑克牌是否为相邻的花色 -func (slf Card) IsNeighborColor(card Card) bool { - return slf.CalcColorDifferenceAbs(card) == 1 -} - -// String 将扑克牌转换为字符串形式 -func (slf Card) String() string { - return fmt.Sprintf("(%s %s)", slf.point, slf.color) +type Card[P, C generic.Number] interface { + // GetGuid 获取扑克牌的唯一标识 + GetGuid() int64 + // GetPoint 获取扑克牌的点数 + GetPoint() P + // GetColor 获取扑克牌的花色 + GetColor() C } diff --git a/game/poker/card_example_test.go b/game/poker/card_example_test.go deleted file mode 100644 index 946f25b..0000000 --- a/game/poker/card_example_test.go +++ /dev/null @@ -1,189 +0,0 @@ -package poker_test - -import ( - "fmt" - "github.com/kercylan98/minotaur/game/poker" -) - -func ExampleNewCard() { - card := poker.NewCard(poker.PointA, poker.ColorSpade) - fmt.Println(card) - - // Output: - // (A Spade) -} - -func ExampleCard_Equal() { - card1 := poker.NewCard(poker.PointA, poker.ColorSpade) - card2 := poker.NewCard(poker.PointA, poker.ColorSpade) - fmt.Println(card1.Equal(card2)) - - // Output: - // true -} - -func ExampleCard_EqualColor() { - card1 := poker.NewCard(poker.PointA, poker.ColorSpade) - card2 := poker.NewCard(poker.PointA, poker.ColorHeart) - fmt.Println(card1.EqualColor(card2)) - - // Output: - // false -} - -func ExampleCard_EqualPoint() { - card1 := poker.NewCard(poker.PointA, poker.ColorSpade) - card2 := poker.NewCard(poker.PointA, poker.ColorHeart) - fmt.Println(card1.EqualPoint(card2)) - - // Output: - // true -} - -func ExampleCard_GetColor() { - card := poker.NewCard(poker.PointA, poker.ColorSpade) - fmt.Println(card.GetColor()) - - // Output: - // Spade -} - -func ExampleCard_GetPoint() { - card := poker.NewCard(poker.PointA, poker.ColorSpade) - fmt.Println(card.GetPoint()) - - // Output: - // A -} - -func ExampleCard_GetPointAndColor() { - card := poker.NewCard(poker.PointA, poker.ColorSpade) - fmt.Println(card.GetPointAndColor()) - - // Output: - // A Spade -} - -func ExampleCard_MaxPoint() { - card1 := poker.NewCard(poker.PointA, poker.ColorSpade) - card2 := poker.NewCard(poker.Point2, poker.ColorSpade) - fmt.Println(card1.MaxPoint(card2)) - - // Output: - // (2 Spade) -} - -func ExampleCard_MinPoint() { - card1 := poker.NewCard(poker.PointA, poker.ColorSpade) - card2 := poker.NewCard(poker.Point2, poker.ColorSpade) - fmt.Println(card1.MinPoint(card2)) - - // Output: - // (A Spade) -} - -func ExampleCard_String() { - card := poker.NewCard(poker.PointA, poker.ColorSpade) - fmt.Println(card.String()) - - // Output: - // (A Spade) -} - -func ExampleCard_MaxColor() { - card1 := poker.NewCard(poker.PointA, poker.ColorSpade) - card2 := poker.NewCard(poker.PointA, poker.ColorHeart) - fmt.Println(card1.MaxColor(card2)) - - // Output: - // (A Spade) -} - -func ExampleCard_MinColor() { - card1 := poker.NewCard(poker.PointA, poker.ColorSpade) - card2 := poker.NewCard(poker.PointA, poker.ColorHeart) - fmt.Println(card1.MinColor(card2)) - - // Output: - // (A Heart) -} - -func ExampleCard_Max() { - card1 := poker.NewCard(poker.PointA, poker.ColorSpade) - card2 := poker.NewCard(poker.PointA, poker.ColorHeart) - fmt.Println(card1.Max(card2)) - - // Output: - // (A Spade) -} - -func ExampleCard_Min() { - card1 := poker.NewCard(poker.PointA, poker.ColorSpade) - card2 := poker.NewCard(poker.PointA, poker.ColorHeart) - fmt.Println(card1.Min(card2)) - - // Output: - // (A Heart) -} - -func ExampleCard_IsJoker() { - card := poker.NewCard(poker.PointA, poker.ColorSpade) - fmt.Println(card.IsJoker()) - - // Output: - // false -} - -func ExampleCard_CalcPointDifference() { - card1 := poker.NewCard(poker.PointA, poker.ColorSpade) - card2 := poker.NewCard(poker.Point2, poker.ColorSpade) - fmt.Println(card1.CalcPointDifference(card2)) - - // Output: - // -1 -} - -func ExampleCard_CalcColorDifference() { - card1 := poker.NewCard(poker.PointA, poker.ColorSpade) - card2 := poker.NewCard(poker.PointA, poker.ColorHeart) - fmt.Println(card1.CalcColorDifference(card2)) - - // Output: - // 1 -} - -func ExampleCard_CalcPointDifferenceAbs() { - card1 := poker.NewCard(poker.PointA, poker.ColorSpade) - card2 := poker.NewCard(poker.Point2, poker.ColorSpade) - fmt.Println(card1.CalcPointDifferenceAbs(card2)) - - // Output: - // 1 -} - -func ExampleCard_CalcColorDifferenceAbs() { - card1 := poker.NewCard(poker.PointA, poker.ColorSpade) - card2 := poker.NewCard(poker.PointA, poker.ColorHeart) - fmt.Println(card1.CalcColorDifferenceAbs(card2)) - - // Output: - // 1 -} - -func ExampleCard_IsNeighborPoint() { - card1 := poker.NewCard(poker.PointA, poker.ColorSpade) - card2 := poker.NewCard(poker.Point2, poker.ColorSpade) - fmt.Println(card1.IsNeighborPoint(card2)) - - // Output: - // true -} - -func ExampleCard_IsNeighborColor() { - card1 := poker.NewCard(poker.PointA, poker.ColorSpade) - card2 := poker.NewCard(poker.PointA, poker.ColorHeart) - fmt.Println(card1.IsNeighborColor(card2)) - - // Output: - // true -} diff --git a/game/poker/card_pile.go b/game/poker/card_pile.go index ee37abb..5388bfc 100644 --- a/game/poker/card_pile.go +++ b/game/poker/card_pile.go @@ -2,6 +2,7 @@ package poker import ( "fmt" + "github.com/kercylan98/minotaur/utils/generic" "github.com/kercylan98/minotaur/utils/hash" "github.com/kercylan98/minotaur/utils/random" "github.com/kercylan98/minotaur/utils/slice" @@ -10,12 +11,17 @@ import ( // NewCardPile 返回一个新的牌堆,其中 size 表示了该牌堆由多少副牌组成 // - 在不洗牌的情况下,默认牌堆顶部到底部为从大到小排列 -func NewCardPile(size int, options ...CardPileOption) *CardPile { - pile := &CardPile{ - size: size, - pile: make([]Card, 0, size*54), +func NewCardPile[P, C generic.Number, T Card[P, C]](size int, jokers [2]P, points [13]P, colors [4]C, generateCard func(guid int64, point P, color C) T, options ...CardPileOption[P, C, T]) *CardPile[P, C, T] { + pile := &CardPile[P, C, T]{ + size: size, + pile: make([]T, 0, size*54), + generateCard: generateCard, + cards: map[int64]T{}, + jokers: jokers, + points: points, + colors: colors, } - pile.shuffleHandle = func(cards []Card) []Card { + pile.shuffleHandle = func(cards []T) []T { sort.Slice(cards, func(i, j int) bool { return random.Float64() >= 0.5 }) @@ -29,28 +35,47 @@ func NewCardPile(size int, options ...CardPileOption) *CardPile { } // CardPile 扑克牌堆 -type CardPile struct { - pile []Card +type CardPile[P, C generic.Number, T Card[P, C]] struct { + pile []T size int - shuffleHandle func(cards []Card) []Card - excludeColor map[Color]struct{} - excludePoint map[Point]struct{} - excludeCard map[Point]map[Color]struct{} + shuffleHandle func(cards []T) []T + excludeColor map[C]struct{} + excludePoint map[P]struct{} + excludeCard map[P]map[C]struct{} + generateCard func(guid int64, point P, color C) T + guid int64 + cards map[int64]T + jokers [2]P + points [13]P + colors [4]C +} + +// GetCard 通过 guid 获取一张牌 +func (slf *CardPile[P, C, T]) GetCard(guid int64) T { + return slf.cards[guid] } // Reset 重置牌堆的扑克牌数量及顺序 -func (slf *CardPile) Reset() { - var cards = make([]Card, 0, 54) - if !slf.IsExclude(PointRedJoker, ColorNone) { - cards = append(cards, NewCard(PointRedJoker, ColorNone)) - } - if !slf.IsExclude(PointBlackJoker, ColorNone) { - cards = append(cards, NewCard(PointBlackJoker, ColorNone)) - } - for point := PointK; point >= PointA; point-- { - for color := ColorDiamond; color <= ColorSpade; color++ { - if !slf.IsExclude(point, color) { - cards = append(cards, NewCard(point, color)) +func (slf *CardPile[P, C, T]) Reset() { + var cards = make([]T, 0, 54*slf.size) + for i := 0; i < slf.size; i++ { + for _, joker := range slf.jokers { + if !slf.IsExclude(joker, C(0)) { + slf.guid++ + card := slf.generateCard(slf.guid, joker, C(0)) + slf.cards[slf.guid] = card + cards = append(cards, card) + } + } + + for p := 0; p < len(slf.points); p++ { + for c := 0; c < len(slf.colors); c++ { + point, color := slf.points[p], slf.colors[c] + if !slf.IsExclude(point, color) { + slf.guid++ + card := slf.generateCard(slf.guid, point, color) + slf.cards[slf.guid] = card + } } } } @@ -61,21 +86,18 @@ func (slf *CardPile) Reset() { } // IsExclude 检查特定点数和花色是否被排除在外 -func (slf *CardPile) IsExclude(point Point, color Color) bool { - if point == PointRedJoker || point == PointBlackJoker { - color = ColorNone - } +func (slf *CardPile[P, C, T]) IsExclude(point P, color C) bool { return hash.Exist(slf.excludePoint, point) || hash.Exist(slf.excludeColor, color) || hash.Exist(slf.excludeCard[point], color) } // IsExcludeWithCard 检查特定扑克牌是否被排除在外 -func (slf *CardPile) IsExcludeWithCard(card Card) bool { - point, color := card.GetPointAndColor() +func (slf *CardPile[P, C, T]) IsExcludeWithCard(card T) bool { + point, color := GetPointAndColor[P, C, T](card) return hash.Exist(slf.excludePoint, point) || hash.Exist(slf.excludeColor, color) || hash.Exist(slf.excludeCard[point], color) } // Shuffle 洗牌 -func (slf *CardPile) Shuffle() { +func (slf *CardPile[P, C, T]) Shuffle() { before := slf.Count() cards := slf.shuffleHandle(slf.Cards()) if len(cards) != before { @@ -85,22 +107,22 @@ func (slf *CardPile) Shuffle() { } // Cards 获取当前牌堆的所有扑克牌 -func (slf *CardPile) Cards() []Card { +func (slf *CardPile[P, C, T]) Cards() []T { return slf.pile } // IsFree 返回牌堆是否没有扑克牌了 -func (slf *CardPile) IsFree() bool { +func (slf *CardPile[P, C, T]) IsFree() bool { return len(slf.pile) == 0 } // Count 获取牌堆剩余牌量 -func (slf *CardPile) Count() int { +func (slf *CardPile[P, C, T]) Count() int { return len(slf.pile) } // Pull 从牌堆特定位置抽出一张牌 -func (slf *CardPile) Pull(index int) Card { +func (slf *CardPile[P, C, T]) Pull(index int) T { if index >= slf.Count() || index < 0 { panic(fmt.Errorf("failed to pull a poker card from the pile, the index is less than 0 or exceeds the remaining number of cards in the pile. count: %d, index: %d", slf.Count(), index)) } @@ -110,7 +132,7 @@ func (slf *CardPile) Pull(index int) Card { } // PullTop 从牌堆顶部抽出一张牌 -func (slf *CardPile) PullTop() Card { +func (slf *CardPile[P, C, T]) PullTop() T { if slf.IsFree() { panic("empty poker cards pile") } @@ -120,7 +142,7 @@ func (slf *CardPile) PullTop() Card { } // PullBottom 从牌堆底部抽出一张牌 -func (slf *CardPile) PullBottom() Card { +func (slf *CardPile[P, C, T]) PullBottom() T { if slf.IsFree() { panic("empty poker cards pile") } @@ -131,17 +153,17 @@ func (slf *CardPile) PullBottom() Card { } // Push 将扑克牌插入到牌堆特定位置 -func (slf *CardPile) Push(index int, card Card) { +func (slf *CardPile[P, C, T]) Push(index int, card T) { slice.Insert(&slf.pile, index, card) return } // PushTop 将扑克牌插入到牌堆顶部 -func (slf *CardPile) PushTop(card Card) { - slf.pile = append([]Card{card}, slf.pile...) +func (slf *CardPile[P, C, T]) PushTop(card T) { + slf.pile = append([]T{card}, slf.pile...) } // PushBottom 将扑克牌插入到牌堆底部 -func (slf *CardPile) PushBottom(card Card) { +func (slf *CardPile[P, C, T]) PushBottom(card T) { slf.pile = append(slf.pile, card) } diff --git a/game/poker/card_pile_example_test.go b/game/poker/card_pile_example_test.go deleted file mode 100644 index 5714397..0000000 --- a/game/poker/card_pile_example_test.go +++ /dev/null @@ -1,17 +0,0 @@ -package poker_test - -import ( - "fmt" - "github.com/kercylan98/minotaur/game/poker" -) - -func ExampleNewCardPile() { - var pile = poker.NewCardPile(1, - poker.WithCardPileExcludeCard(poker.NewCard(poker.PointBlackJoker, poker.ColorNone)), - ) - - fmt.Println(pile.Cards()) - - // Output: - // [(R None) (K Diamond) (K Club) (K Heart) (K Spade) (Q Diamond) (Q Club) (Q Heart) (Q Spade) (J Diamond) (J Club) (J Heart) (J Spade) (10 Diamond) (10 Club) (10 Heart) (10 Spade) (9 Diamond) (9 Club) (9 Heart) (9 Spade) (8 Diamond) (8 Club) (8 Heart) (8 Spade) (7 Diamond) (7 Club) (7 Heart) (7 Spade) (6 Diamond) (6 Club) (6 Heart) (6 Spade) (5 Diamond) (5 Club) (5 Heart) (5 Spade) (4 Diamond) (4 Club) (4 Heart) (4 Spade) (3 Diamond) (3 Club) (3 Heart) (3 Spade) (2 Diamond) (2 Club) (2 Heart) (2 Spade) (A Diamond) (A Club) (A Heart) (A Spade)] -} diff --git a/game/poker/card_pile_options.go b/game/poker/card_pile_options.go index 5b66428..19c94a2 100644 --- a/game/poker/card_pile_options.go +++ b/game/poker/card_pile_options.go @@ -1,11 +1,13 @@ package poker -type CardPileOption func(pile *CardPile) +import "github.com/kercylan98/minotaur/utils/generic" + +type CardPileOption[P, C generic.Number, T Card[P, C]] func(pile *CardPile[P, C, T]) // WithCardPileShuffle 通过特定的洗牌算法创建牌堆 // - 需要保证洗牌后的牌堆剩余扑克数量与之前相同,否则将会引发 panic -func WithCardPileShuffle(shuffleHandle func(pile []Card) []Card) CardPileOption { - return func(pile *CardPile) { +func WithCardPileShuffle[P, C generic.Number, T Card[P, C]](shuffleHandle func(pile []T) []T) CardPileOption[P, C, T] { + return func(pile *CardPile[P, C, T]) { if shuffleHandle == nil { return } @@ -14,10 +16,10 @@ func WithCardPileShuffle(shuffleHandle func(pile []Card) []Card) CardPileOption } // WithCardPileExcludeColor 通过排除特定花色的方式创建牌堆 -func WithCardPileExcludeColor(colors ...Color) CardPileOption { - return func(pile *CardPile) { +func WithCardPileExcludeColor[P, C generic.Number, T Card[P, C]](colors ...C) CardPileOption[P, C, T] { + return func(pile *CardPile[P, C, T]) { if pile.excludeColor == nil { - pile.excludeColor = map[Color]struct{}{} + pile.excludeColor = map[C]struct{}{} } for _, color := range colors { pile.excludeColor[color] = struct{}{} @@ -26,10 +28,10 @@ func WithCardPileExcludeColor(colors ...Color) CardPileOption { } // WithCardPileExcludePoint 通过排除特定点数的方式创建牌堆 -func WithCardPileExcludePoint(points ...Point) CardPileOption { - return func(pile *CardPile) { +func WithCardPileExcludePoint[P, C generic.Number, T Card[P, C]](points ...P) CardPileOption[P, C, T] { + return func(pile *CardPile[P, C, T]) { if pile.excludePoint == nil { - pile.excludePoint = map[Point]struct{}{} + pile.excludePoint = map[P]struct{}{} } for _, point := range points { pile.excludePoint[point] = struct{}{} @@ -38,22 +40,22 @@ func WithCardPileExcludePoint(points ...Point) CardPileOption { } // WithCardPileExcludeCard 通过排除特定扑克牌的方式创建牌堆 -func WithCardPileExcludeCard(cards ...Card) CardPileOption { - return func(pile *CardPile) { +func WithCardPileExcludeCard[P, C generic.Number, T Card[P, C]](cards ...Card[P, C]) CardPileOption[P, C, T] { + return func(pile *CardPile[P, C, T]) { if pile.excludeCard == nil { - pile.excludeCard = map[Point]map[Color]struct{}{} + pile.excludeCard = map[P]map[C]struct{}{} } for _, card := range cards { point := card.GetPoint() cs, exist := pile.excludeCard[point] if !exist { - cs = map[Color]struct{}{} + cs = map[C]struct{}{} pile.excludeCard[point] = cs } - if point == PointRedJoker || point == PointBlackJoker { - cs[ColorNone] = struct{}{} - } else { - cs[card.GetColor()] = struct{}{} + for _, joker := range pile.jokers { + if point != joker { + cs[card.GetColor()] = struct{}{} + } } } } diff --git a/game/poker/card_test.go b/game/poker/card_test.go deleted file mode 100644 index 124c7e5..0000000 --- a/game/poker/card_test.go +++ /dev/null @@ -1,60 +0,0 @@ -package poker_test - -import ( - "github.com/kercylan98/minotaur/game/poker" - . "github.com/smartystreets/goconvey/convey" - "testing" -) - -func TestCard_GetPoint(t *testing.T) { - Convey("TestCard_GetPoint", t, func() { - card := poker.NewCard(poker.PointA, poker.ColorSpade) - So(card.GetPoint(), ShouldEqual, poker.PointA) - }) -} - -func TestCard_GetColor(t *testing.T) { - Convey("TestCard_GetColor", t, func() { - card := poker.NewCard(poker.PointA, poker.ColorSpade) - So(card.GetColor(), ShouldEqual, poker.ColorSpade) - }) -} - -func TestCard_GetPointAndColor(t *testing.T) { - Convey("TestCard_GetPointAndColor", t, func() { - card := poker.NewCard(poker.PointA, poker.ColorSpade) - point, color := card.GetPointAndColor() - So(point, ShouldEqual, poker.PointA) - So(color, ShouldEqual, poker.ColorSpade) - }) -} - -func TestCard_EqualPoint(t *testing.T) { - Convey("TestCard_EqualPoint", t, func() { - card1 := poker.NewCard(poker.PointA, poker.ColorSpade) - card2 := poker.NewCard(poker.PointA, poker.ColorSpade) - card3 := poker.NewCard(poker.Point2, poker.ColorSpade) - So(card1.EqualPoint(card2), ShouldEqual, true) - So(card2.EqualPoint(card3), ShouldEqual, false) - }) -} - -func TestCard_EqualColor(t *testing.T) { - Convey("TestCard_EqualColor", t, func() { - card1 := poker.NewCard(poker.PointA, poker.ColorSpade) - card2 := poker.NewCard(poker.PointA, poker.ColorSpade) - card3 := poker.NewCard(poker.PointA, poker.ColorHeart) - So(card1.EqualColor(card2), ShouldEqual, true) - So(card2.EqualColor(card3), ShouldEqual, false) - }) -} - -func TestCard_Equal(t *testing.T) { - Convey("TestCard_Equal", t, func() { - card1 := poker.NewCard(poker.PointA, poker.ColorSpade) - card2 := poker.NewCard(poker.PointA, poker.ColorSpade) - card3 := poker.NewCard(poker.Point2, poker.ColorHeart) - So(card1.Equal(card2), ShouldEqual, true) - So(card2.Equal(card3), ShouldEqual, false) - }) -} diff --git a/game/poker/color.go b/game/poker/color.go deleted file mode 100644 index 6cbc652..0000000 --- a/game/poker/color.go +++ /dev/null @@ -1,43 +0,0 @@ -package poker - -const ( - ColorNone Color = 0 // 无花色,通常为大小王 - ColorSpade Color = 4 // 黑桃 - ColorHeart Color = 3 // 红桃 - ColorClub Color = 2 // 梅花 - ColorDiamond Color = 1 // 方片 -) - -var defaultColorSort = map[Color]int{ - ColorSpade: int(ColorSpade), - ColorHeart: int(ColorHeart), - ColorClub: int(ColorClub), - ColorDiamond: int(ColorDiamond), - ColorNone: int(ColorDiamond + 1), -} - -// Color 扑克牌花色 -type Color int - -// InBounds 扑克牌花色是否在界限内 -// - 将检查花色是否在黑桃、红桃、梅花、方片之间 -func (slf Color) InBounds() bool { - return slf <= ColorSpade && slf >= ColorDiamond -} - -func (slf Color) String() string { - var str string - switch slf { - case ColorSpade: - str = "Spade" - case ColorHeart: - str = "Heart" - case ColorClub: - str = "Club" - case ColorDiamond: - str = "Diamond" - default: - str = "None" - } - return str -} diff --git a/game/poker/hand.go b/game/poker/hand.go index 4fe1ab4..2a16d1a 100644 --- a/game/poker/hand.go +++ b/game/poker/hand.go @@ -1,37 +1,39 @@ package poker +import "github.com/kercylan98/minotaur/utils/generic" + const ( HandNone = "None" // 无牌型 ) // HandHandle 扑克牌型验证函数 -type HandHandle func(rule *Rule, cards []Card) bool +type HandHandle[P, C generic.Number, T Card[P, C]] func(rule *Rule[P, C, T], cards []T) bool // HandSingle 单牌 -func HandSingle() HandHandle { - return func(rule *Rule, cards []Card) bool { +func HandSingle[P, C generic.Number, T Card[P, C]]() HandHandle[P, C, T] { + return func(rule *Rule[P, C, T], cards []T) bool { return len(cards) == 1 } } // HandPairs 对子 -func HandPairs() HandHandle { - return func(rule *Rule, cards []Card) bool { +func HandPairs[P, C generic.Number, T Card[P, C]]() HandHandle[P, C, T] { + return func(rule *Rule[P, C, T], cards []T) bool { return len(cards) == 2 && rule.IsPointContinuity(2, cards...) } } // HandThreeOfKind 三张 -func HandThreeOfKind() HandHandle { - return func(rule *Rule, cards []Card) bool { +func HandThreeOfKind[P, C generic.Number, T Card[P, C]]() HandHandle[P, C, T] { + return func(rule *Rule[P, C, T], cards []T) bool { return len(cards) == 3 && rule.IsPointContinuity(3, cards...) } } // HandThreeOfKindWithOne 三带一 -func HandThreeOfKindWithOne() HandHandle { - return func(rule *Rule, cards []Card) bool { - group := GroupByPoint(cards...) +func HandThreeOfKindWithOne[P, C generic.Number, T Card[P, C]]() HandHandle[P, C, T] { + return func(rule *Rule[P, C, T], cards []T) bool { + group := GroupByPoint[P, C, T](cards...) if len(group) != 2 { return false } @@ -49,9 +51,9 @@ func HandThreeOfKindWithOne() HandHandle { } // HandThreeOfKindWithTwo 三带二 -func HandThreeOfKindWithTwo() HandHandle { - return func(rule *Rule, cards []Card) bool { - group := GroupByPoint(cards...) +func HandThreeOfKindWithTwo[P, C generic.Number, T Card[P, C]]() HandHandle[P, C, T] { + return func(rule *Rule[P, C, T], cards []T) bool { + group := GroupByPoint[P, C, T](cards...) if len(group) != 2 { return false } @@ -69,15 +71,15 @@ func HandThreeOfKindWithTwo() HandHandle { } // HandOrderSingle 顺子 -func HandOrderSingle(count int) HandHandle { - return func(rule *Rule, cards []Card) bool { +func HandOrderSingle[P, C generic.Number, T Card[P, C]](count int) HandHandle[P, C, T] { + return func(rule *Rule[P, C, T], cards []T) bool { return len(cards) >= count && rule.IsPointContinuity(1, cards...) } } // HandOrderPairs 对子顺子 -func HandOrderPairs(count int) HandHandle { - return func(rule *Rule, cards []Card) bool { +func HandOrderPairs[P, C generic.Number, T Card[P, C]](count int) HandHandle[P, C, T] { + return func(rule *Rule[P, C, T], cards []T) bool { if len(cards) < count*2 || len(cards)%2 != 0 { return false } @@ -86,8 +88,8 @@ func HandOrderPairs(count int) HandHandle { } // HandOrderSingleThree 三张顺子 -func HandOrderSingleThree(count int) HandHandle { - return func(rule *Rule, cards []Card) bool { +func HandOrderSingleThree[P, C generic.Number, T Card[P, C]](count int) HandHandle[P, C, T] { + return func(rule *Rule[P, C, T], cards []T) bool { if len(cards) < count*3 || len(cards)%3 != 0 { return false } @@ -96,8 +98,8 @@ func HandOrderSingleThree(count int) HandHandle { } // HandOrderSingleFour 四张顺子 -func HandOrderSingleFour(count int) HandHandle { - return func(rule *Rule, cards []Card) bool { +func HandOrderSingleFour[P, C generic.Number, T Card[P, C]](count int) HandHandle[P, C, T] { + return func(rule *Rule[P, C, T], cards []T) bool { if len(cards) < count*4 || len(cards)%4 != 0 { return false } @@ -106,10 +108,10 @@ func HandOrderSingleFour(count int) HandHandle { } // HandOrderThreeWithOne 三带一顺子 -func HandOrderThreeWithOne(count int) HandHandle { - return func(rule *Rule, cards []Card) bool { - group := GroupByPoint(cards...) - var continuous []Card +func HandOrderThreeWithOne[P, C generic.Number, T Card[P, C]](count int) HandHandle[P, C, T] { + return func(rule *Rule[P, C, T], cards []T) bool { + group := GroupByPoint[P, C, T](cards...) + var continuous []T var other int for _, cards := range group { if len(cards) == 3 { @@ -126,10 +128,10 @@ func HandOrderThreeWithOne(count int) HandHandle { } // HandOrderThreeWithTwo 三带二顺子 -func HandOrderThreeWithTwo(count int) HandHandle { - return func(rule *Rule, cards []Card) bool { - group := GroupByPoint(cards...) - var continuous []Card +func HandOrderThreeWithTwo[P, C generic.Number, T Card[P, C]](count int) HandHandle[P, C, T] { + return func(rule *Rule[P, C, T], cards []T) bool { + group := GroupByPoint[P, C, T](cards...) + var continuous []T var other int for _, cards := range group { if len(cards) == 3 { @@ -148,10 +150,10 @@ func HandOrderThreeWithTwo(count int) HandHandle { } // HandOrderFourWithOne 四带一顺子 -func HandOrderFourWithOne(count int) HandHandle { - return func(rule *Rule, cards []Card) bool { - group := GroupByPoint(cards...) - var continuous []Card +func HandOrderFourWithOne[P, C generic.Number, T Card[P, C]](count int) HandHandle[P, C, T] { + return func(rule *Rule[P, C, T], cards []T) bool { + group := GroupByPoint[P, C, T](cards...) + var continuous []T var other int for _, cards := range group { if len(cards) == 4 { @@ -168,10 +170,10 @@ func HandOrderFourWithOne(count int) HandHandle { } // HandOrderFourWithTwo 四带二顺子 -func HandOrderFourWithTwo(count int) HandHandle { - return func(rule *Rule, cards []Card) bool { - group := GroupByPoint(cards...) - var continuous []Card +func HandOrderFourWithTwo[P, C generic.Number, T Card[P, C]](count int) HandHandle[P, C, T] { + return func(rule *Rule[P, C, T], cards []T) bool { + group := GroupByPoint[P, C, T](cards...) + var continuous []T var other int for _, cards := range group { if len(cards) == 4 { @@ -190,10 +192,10 @@ func HandOrderFourWithTwo(count int) HandHandle { } // HandOrderFourWithThree 四带三顺子 -func HandOrderFourWithThree(count int) HandHandle { - return func(rule *Rule, cards []Card) bool { - group := GroupByPoint(cards...) - var continuous []Card +func HandOrderFourWithThree[P, C generic.Number, T Card[P, C]](count int) HandHandle[P, C, T] { + return func(rule *Rule[P, C, T], cards []T) bool { + group := GroupByPoint[P, C, T](cards...) + var continuous []T var other int for _, cards := range group { if len(cards) == 4 { @@ -212,9 +214,9 @@ func HandOrderFourWithThree(count int) HandHandle { } // HandFourWithOne 四带一 -func HandFourWithOne() HandHandle { - return func(rule *Rule, cards []Card) bool { - group := GroupByPoint(cards...) +func HandFourWithOne[P, C generic.Number, T Card[P, C]]() HandHandle[P, C, T] { + return func(rule *Rule[P, C, T], cards []T) bool { + group := GroupByPoint[P, C, T](cards...) var hasFour bool var count int for _, cards := range group { @@ -229,9 +231,9 @@ func HandFourWithOne() HandHandle { } // HandFourWithTwo 四带二 -func HandFourWithTwo() HandHandle { - return func(rule *Rule, cards []Card) bool { - group := GroupByPoint(cards...) +func HandFourWithTwo[P, C generic.Number, T Card[P, C]]() HandHandle[P, C, T] { + return func(rule *Rule[P, C, T], cards []T) bool { + group := GroupByPoint[P, C, T](cards...) var hasFour bool var count int for _, cards := range group { @@ -246,9 +248,9 @@ func HandFourWithTwo() HandHandle { } // HandFourWithThree 四带三 -func HandFourWithThree() HandHandle { - return func(rule *Rule, cards []Card) bool { - group := GroupByPoint(cards...) +func HandFourWithThree[P, C generic.Number, T Card[P, C]]() HandHandle[P, C, T] { + return func(rule *Rule[P, C, T], cards []T) bool { + group := GroupByPoint[P, C, T](cards...) var hasFour bool var count int for _, cards := range group { @@ -263,9 +265,9 @@ func HandFourWithThree() HandHandle { } // HandFourWithTwoPairs 四带两对 -func HandFourWithTwoPairs() HandHandle { - return func(rule *Rule, cards []Card) bool { - group := GroupByPoint(cards...) +func HandFourWithTwoPairs[P, C generic.Number, T Card[P, C]]() HandHandle[P, C, T] { + return func(rule *Rule[P, C, T], cards []T) bool { + group := GroupByPoint[P, C, T](cards...) var hasFour bool var count int for _, cards := range group { @@ -286,15 +288,15 @@ func HandFourWithTwoPairs() HandHandle { } // HandBomb 炸弹 -func HandBomb() HandHandle { - return func(rule *Rule, cards []Card) bool { +func HandBomb[P, C generic.Number, T Card[P, C]]() HandHandle[P, C, T] { + return func(rule *Rule[P, C, T], cards []T) bool { return len(cards) == 4 && rule.IsPointContinuity(4, cards...) } } // HandStraightPairs 连对 -func HandStraightPairs() HandHandle { - return func(rule *Rule, cards []Card) bool { +func HandStraightPairs[P, C generic.Number, T Card[P, C]]() HandHandle[P, C, T] { + return func(rule *Rule[P, C, T], cards []T) bool { if len(cards) < 6 || len(cards)%2 != 0 { return false } @@ -304,8 +306,8 @@ func HandStraightPairs() HandHandle { // HandPlane 飞机 // - 表示三张点数相同的牌组成的连续的牌 -func HandPlane() HandHandle { - return func(rule *Rule, cards []Card) bool { +func HandPlane[P, C generic.Number, T Card[P, C]]() HandHandle[P, C, T] { + return func(rule *Rule[P, C, T], cards []T) bool { if len(cards) < 6 || len(cards)%3 != 0 { return false } @@ -314,9 +316,9 @@ func HandPlane() HandHandle { } // HandPlaneWithOne 飞机带单 -func HandPlaneWithOne() HandHandle { - return func(rule *Rule, cards []Card) bool { - group := GroupByPoint(cards...) +func HandPlaneWithOne[P, C generic.Number, T Card[P, C]]() HandHandle[P, C, T] { + return func(rule *Rule[P, C, T], cards []T) bool { + group := GroupByPoint[P, C, T](cards...) if len(group) < 2 { return false } @@ -335,20 +337,20 @@ func HandPlaneWithOne() HandHandle { // HandRocket 王炸 // - 表示一对王牌,即大王和小王 -func HandRocket() HandHandle { - return func(rule *Rule, cards []Card) bool { +func HandRocket[P, C generic.Number, T Card[P, C]](pile *CardPile[P, C, T]) HandHandle[P, C, T] { + return func(rule *Rule[P, C, T], cards []T) bool { if len(cards) != 2 { return false } - return IsRocket(cards[0], cards[1]) + return IsRocket[P, C, T](pile, cards[0], cards[1]) } } // HandFlush 同花 // - 表示所有牌的花色都相同 -func HandFlush() HandHandle { - return func(rule *Rule, cards []Card) bool { - return IsFlush(cards...) +func HandFlush[P, C generic.Number, T Card[P, C]]() HandHandle[P, C, T] { + return func(rule *Rule[P, C, T], cards []T) bool { + return IsFlush[P, C, T](cards...) } } @@ -356,12 +358,12 @@ func HandFlush() HandHandle { // - count: 顺子的对子数量,例如当 count = 2 时,可以是 334455、445566、556677、667788、778899 // - lower: 顺子的最小连续数量 // - limit: 顺子的最大连续数量 -func HandFlushStraight(count, lower, limit int) HandHandle { - return func(rule *Rule, cards []Card) bool { +func HandFlushStraight[P, C generic.Number, T Card[P, C]](count, lower, limit int) HandHandle[P, C, T] { + return func(rule *Rule[P, C, T], cards []T) bool { if len(cards) < lower*count || len(cards) > limit*count || len(cards)%count != 0 { return false } - if !IsFlush(cards...) { + if !IsFlush[P, C, T](cards...) { return false } return rule.IsPointContinuity(count, cards...) @@ -372,8 +374,8 @@ func HandFlushStraight(count, lower, limit int) HandHandle { // - 表示三张点数相同的牌 // - 例如:333、444、555、666、777、888、999、JJJ、QQQ、KKK、AAA // - 大小王不能用于豹子,因为他们没有点数 -func HandLeopard() HandHandle { - return func(rule *Rule, cards []Card) bool { +func HandLeopard[P, C generic.Number, T Card[P, C]]() HandHandle[P, C, T] { + return func(rule *Rule[P, C, T], cards []T) bool { if len(cards) == 0 { return false } @@ -382,7 +384,7 @@ func HandLeopard() HandHandle { } var card = cards[0] for i := 1; i < len(cards); i++ { - if !card.Equal(cards[1]) { + if !Equal[P, C, T](card, cards[1]) { return false } } @@ -395,9 +397,9 @@ func HandLeopard() HandHandle { // - 例如:334、445、556、667、778、889、99J、TTQ、JJK、QQA、AA2 // - 大小王不能用于二带一,因为他们没有点数 // - 通常用于炸金花玩法中检查对子 -func HandTwoWithOne() HandHandle { - return func(rule *Rule, cards []Card) bool { - group := GroupByPoint(cards...) +func HandTwoWithOne[P, C generic.Number, T Card[P, C]]() HandHandle[P, C, T] { + return func(rule *Rule[P, C, T], cards []T) bool { + group := GroupByPoint[P, C, T](cards...) var hasTwo bool var count int for _, cards := range group { diff --git a/game/poker/matcher.go b/game/poker/matcher.go new file mode 100644 index 0000000..e76d2c6 --- /dev/null +++ b/game/poker/matcher.go @@ -0,0 +1,52 @@ +package poker + +import ( + "github.com/kercylan98/minotaur/utils/generic" + "github.com/kercylan98/minotaur/utils/hash" +) + +// NewMatcher 创建一个新的匹配器 +// - evaluate: 用于评估一组扑克牌的分值,分值最高的组合将被选中 +func NewMatcher[P, C generic.Number, T Card[P, C]]() *Matcher[P, C, T] { + matcher := &Matcher[P, C, T]{ + filter: map[string]*MatcherFilter[P, C, T]{}, + } + return matcher +} + +// Matcher 匹配器 +// - 用于匹配扑克牌型,筛选分组等 +type Matcher[P, C generic.Number, T Card[P, C]] struct { + filter map[string]*MatcherFilter[P, C, T] + sort []string +} + +// RegType 注册一个新的牌型 +// - name: 牌型名称 +// - evaluate: 用于评估一组扑克牌的分值,分值最高的组合将被选中 +// - options: 牌型选项 +func (slf *Matcher[P, C, T]) RegType(name string, evaluate func([]T) int64, options ...MatcherOption[P, C, T]) *Matcher[P, C, T] { + if hash.Exist(slf.filter, name) { + panic("exist of the same type") + } + filter := &MatcherFilter[P, C, T]{ + evaluate: evaluate, + } + for _, option := range options { + option(filter) + } + slf.filter[name] = filter + slf.sort = append(slf.sort, name) + return slf +} + +// Group 将一组扑克牌按照匹配器的规则分组,并返回最佳组合及其牌型名称 +func (slf *Matcher[P, C, T]) Group(cards []T) (name string, result []T) { + for _, n := range slf.sort { + result = slf.filter[n].group(cards) + if len(result) > 0 { + return n, result + } + } + return +} diff --git a/game/poker/matcher_filter.go b/game/poker/matcher_filter.go new file mode 100644 index 0000000..2047a89 --- /dev/null +++ b/game/poker/matcher_filter.go @@ -0,0 +1,42 @@ +package poker + +import "github.com/kercylan98/minotaur/utils/generic" + +type MatcherFilter[P, C generic.Number, T Card[P, C]] struct { + evaluate func([]T) int64 + handles []func(cards []T) [][]T + asc bool +} + +func (slf *MatcherFilter[P, C, T]) AddHandle(handle func(cards []T) [][]T) { + slf.handles = append(slf.handles, handle) +} + +func (slf *MatcherFilter[P, C, T]) group(cards []T) []T { + var bestCombination = cards + + for _, handle := range slf.handles { + var bestScore int64 + filteredCombinations := handle(bestCombination) + if len(filteredCombinations) == 0 { + return []T{} + } + for _, combination := range filteredCombinations { + score := slf.evaluate(combination) + if slf.asc { + if score < bestScore || bestCombination == nil { + bestCombination = combination + bestScore = score + } + } else { + if score > bestScore || bestCombination == nil { + bestCombination = combination + bestScore = score + } + } + + } + } + + return bestCombination +} diff --git a/game/poker/matcher_options.go b/game/poker/matcher_options.go new file mode 100644 index 0000000..0045329 --- /dev/null +++ b/game/poker/matcher_options.go @@ -0,0 +1,271 @@ +package poker + +import ( + "fmt" + "github.com/kercylan98/minotaur/utils/generic" + "github.com/kercylan98/minotaur/utils/slice" + "reflect" + "sort" +) + +// MatcherOption 匹配器选项 +type MatcherOption[P, C generic.Number, T Card[P, C]] func(matcher *MatcherFilter[P, C, T]) + +// WithMatcherScoreAsc 通过升序评估分数创建匹配器 +// - 用于评估一组扑克牌的分值,分值最低的组合将被选中 +// - 默认为分数最高的组合将被选中 +func WithMatcherScoreAsc[P, C generic.Number, T Card[P, C]]() MatcherOption[P, C, T] { + return func(matcher *MatcherFilter[P, C, T]) { + matcher.asc = true + } +} + +// WithMatcherLeastLength 通过匹配最小长度的扑克牌创建匹配器 +// - length: 牌型的长度,表示需要匹配的扑克牌最小数量 +func WithMatcherLeastLength[P, C generic.Number, T Card[P, C]](length int) MatcherOption[P, C, T] { + return func(matcher *MatcherFilter[P, C, T]) { + matcher.AddHandle(func(cards []T) [][]T { + var combinations [][]T + combinations = slice.LimitedCombinations(cards, length, len(cards)) + return combinations + }) + } +} + +// WithMatcherLength 通过匹配长度的扑克牌创建匹配器 +// - length: 牌型的长度,表示需要匹配的扑克牌数量 +func WithMatcherLength[P, C generic.Number, T Card[P, C]](length int) MatcherOption[P, C, T] { + return func(matcher *MatcherFilter[P, C, T]) { + matcher.AddHandle(func(cards []T) [][]T { + var combinations [][]T + combinations = slice.LimitedCombinations(cards, length, length) + return combinations + }) + } +} + +// WithMatcherContinuity 通过匹配连续的扑克牌创建匹配器 +func WithMatcherContinuity[P, C generic.Number, T Card[P, C]]() MatcherOption[P, C, T] { + return func(matcher *MatcherFilter[P, C, T]) { + matcher.AddHandle(func(cards []T) [][]T { + var combinations [][]T + n := len(cards) + + if n <= 0 { + return combinations + } + + // 对扑克牌按点数进行排序 + sort.Slice(cards, func(i, j int) bool { + return cards[i].GetPoint() < cards[j].GetPoint() + }) + + // 查找连续的牌型组合 + for i := 0; i < n; i++ { + combination := []T{cards[i]} + for j := i + 1; j < n; j++ { + if cards[j].GetPoint()-combination[len(combination)-1].GetPoint() == 1 { + combination = append(combination, cards[j]) + } else { + break + } + } + if len(combination) >= 2 { + combinations = append(combinations, combination) + } + } + + return combinations + }) + } +} + +// WithMatcherContinuityPointOrder 通过匹配连续的扑克牌创建匹配器,与 WithMatcherContinuity 不同的是,该选项将按照自定义的点数顺序进行匹配 +func WithMatcherContinuityPointOrder[P, C generic.Number, T Card[P, C]](order map[P]int) MatcherOption[P, C, T] { + return func(matcher *MatcherFilter[P, C, T]) { + var getOrder = func(card T) P { + if v, ok := order[card.GetPoint()]; ok { + return P(v) + } + return card.GetPoint() + } + matcher.AddHandle(func(cards []T) [][]T { + var combinations [][]T + n := len(cards) + + if n <= 0 { + return combinations + } + + // 对扑克牌按点数进行排序 + sort.Slice(cards, func(i, j int) bool { + return getOrder(cards[i]) < getOrder(cards[j]) + }) + + // 查找连续的牌型组合 + for i := 0; i < n; i++ { + combination := []T{cards[i]} + for j := i + 1; j < n; j++ { + if getOrder(cards[j])-getOrder(combination[len(combination)-1]) == 1 { + combination = append(combination, cards[j]) + } else { + break + } + } + if len(combination) >= 2 { + combinations = append(combinations, combination) + } + } + + return combinations + }) + } +} + +// WithMatcherFlush 通过匹配同花的扑克牌创建匹配器 +func WithMatcherFlush[P, C generic.Number, T Card[P, C]]() MatcherOption[P, C, T] { + return func(matcher *MatcherFilter[P, C, T]) { + matcher.AddHandle(func(cards []T) [][]T { + var combinations [][]T + + groups := GroupByColor[P, C, T](cards...) + for _, group := range groups { + combinations = append(combinations, slice.Combinations(group)...) + } + + return combinations + }) + } +} + +// WithMatcherTie 通过匹配相同点数的扑克牌创建匹配器 +func WithMatcherTie[P, C generic.Number, T Card[P, C]]() MatcherOption[P, C, T] { + return func(matcher *MatcherFilter[P, C, T]) { + matcher.AddHandle(func(cards []T) [][]T { + var combinations [][]T + groups := GroupByPoint[P, C, T](cards...) + for _, group := range groups { + for _, ts := range slice.Combinations(group) { + combinations = append(combinations, ts) + } + } + return combinations + }) + } +} + +// WithMatcherTieCount 通过匹配相同点数的特定数量的扑克牌创建匹配器 +// - count: 牌型中相同点数的牌的数量 +func WithMatcherTieCount[P, C generic.Number, T Card[P, C]](count int) MatcherOption[P, C, T] { + return func(matcher *MatcherFilter[P, C, T]) { + matcher.AddHandle(func(cards []T) [][]T { + var combinations [][]T + groups := GroupByPoint[P, C, T](cards...) + for _, group := range groups { + if len(group) < count { + continue + } + for _, ts := range slice.Combinations(group) { + if len(ts) == count { + combinations = append(combinations, ts) + } + } + } + return combinations + }) + } +} + +// WithMatcherTieCountNum 通过匹配相同点数的特定数量的扑克牌创建匹配器 +// - count: 牌型中相同点数的牌的数量 +// - num: 牌型中相同点数的牌的数量 +func WithMatcherTieCountNum[P, C generic.Number, T Card[P, C]](count, num int) MatcherOption[P, C, T] { + return func(matcher *MatcherFilter[P, C, T]) { + matcher.AddHandle(func(cards []T) [][]T { + var combinations [][]T + cs := slice.LimitedCombinations(cards, count*num, count*num) + var pointCount = make(map[P]int) + for _, group := range cs { + var ok = false + for _, t := range group { + pointCount[t.GetPoint()]++ + if len(pointCount) == 2 { + var matchCount = true + for _, n := range pointCount { + if n != num { + matchCount = false + break + } + } + if matchCount { + ok = true + break + } + } + } + if ok { + combinations = append(combinations, group) + } + for point := range pointCount { + delete(pointCount, point) + } + } + return combinations + }) + } +} + +// WithMatcherNCarryM 通过匹配N带相同点数M的扑克牌创建匹配器 +// - n: 需要匹配的主牌数量 +// - m: 需要匹配的附加牌数量 +func WithMatcherNCarryM[P, C generic.Number, T Card[P, C]](n, m int) MatcherOption[P, C, T] { + return func(matcher *MatcherFilter[P, C, T]) { + matcher.AddHandle(func(cards []T) [][]T { + var combinations [][]T + groups := GroupByPoint[P, C, T](cards...) + for _, group := range groups { + if len(group) != n { + continue + } + ms := slice.Combinations(slice.SubWithCheck(cards, group, func(a, b T) bool { return reflect.DeepEqual(a, b) })) + for i := 0; i < len(ms); i++ { + ts := GroupByPoint[P, C, T](ms[i]...) + for _, cs := range ts { + if len(cs) == m { + combinations = append(combinations, slice.Merge(group, cs)) + } + } + } + } + return combinations + }) + } +} + +// WithMatcherNCarryMSingle 通过匹配N带M的扑克牌创建匹配器 +// - n: 需要匹配的主牌数量 +// - m: 需要匹配的附加牌数量 +func WithMatcherNCarryMSingle[P, C generic.Number, T Card[P, C]](n, m int) MatcherOption[P, C, T] { + return func(matcher *MatcherFilter[P, C, T]) { + matcher.AddHandle(func(cards []T) [][]T { + var combinations [][]T + groups := GroupByPoint[P, C, T](cards...) + for _, group := range groups { + if len(group) != n { + continue + } + ms := slice.Combinations(slice.SubWithCheck(cards, group, func(a, b T) bool { return reflect.DeepEqual(a, b) })) + for i := 0; i < len(ms); i++ { + ts := ms[i] + if len(ts) == m { + combinations = append(combinations, slice.Merge(group, ts)) + } + } + } + if len(combinations) > 0 { + fmt.Println(len(combinations)) + } + return combinations + }) + } +} diff --git a/game/poker/matcher_test.go b/game/poker/matcher_test.go new file mode 100644 index 0000000..346fd88 --- /dev/null +++ b/game/poker/matcher_test.go @@ -0,0 +1,117 @@ +package poker_test + +import ( + "fmt" + "github.com/kercylan98/minotaur/game/poker" + "github.com/kercylan98/minotaur/utils/generic" + "github.com/kercylan98/minotaur/utils/slice" + "testing" + "time" +) + +type Card[P, C generic.Number] struct { + guid int64 + point P + color C +} + +func (slf *Card[P, C]) GetGuid() int64 { + return slf.guid +} + +func (slf *Card[P, C]) GetPoint() P { + return slf.point +} + +func (slf *Card[P, C]) GetColor() C { + return slf.color +} + +func TestMatcher_Group(t *testing.T) { + + evaluate := func(cards []*Card[int, int]) int64 { + score := int64(0) + for _, card := range cards { + if card.point == 1 { + score += 14 + continue + } + score += int64(card.GetPoint()) + } + return score + } + + matcher := poker.NewMatcher[int, int, *Card[int, int]]() + //matcher.RegType("三条", evaluate, + // poker.WithMatcherNCarryMSingle[*Card](3, 2)) + matcher.RegType("皇家同花顺", evaluate, + poker.WithMatcherFlush[int, int, *Card[int, int]](), + poker.WithMatcherContinuityPointOrder[int, int, *Card[int, int]](map[int]int{1: 14}), + poker.WithMatcherLength[int, int, *Card[int, int]](5), + ).RegType("同花顺", evaluate, + poker.WithMatcherFlush[int, int, *Card[int, int]](), + poker.WithMatcherContinuityPointOrder[int, int, *Card[int, int]](map[int]int{1: 14}), + poker.WithMatcherLeastLength[int, int, *Card[int, int]](3), + ).RegType("四条", evaluate, + poker.WithMatcherTieCount[int, int, *Card[int, int]](4), + ).RegType("葫芦", evaluate, + poker.WithMatcherNCarryM[int, int, *Card[int, int]](3, 2), + ).RegType("顺子", evaluate, + poker.WithMatcherContinuityPointOrder[int, int, *Card[int, int]](map[int]int{1: 14}), + poker.WithMatcherLength[int, int, *Card[int, int]](5), + ).RegType("三条", evaluate, + poker.WithMatcherNCarryMSingle[int, int, *Card[int, int]](3, 2), + ).RegType("两对", evaluate, + poker.WithMatcherTieCountNum[int, int, *Card[int, int]](2, 2), + ).RegType("一对", evaluate, + poker.WithMatcherTieCount[int, int, *Card[int, int]](2), + ).RegType("高牌", evaluate, + poker.WithMatcherTieCount[int, int, *Card[int, int]](1), + ) + + var pub = []*Card[int, int]{ + {point: 4, color: 3}, + {point: 5, color: 2}, + {point: 6, color: 1}, + {point: 6, color: 2}, + {point: 13, color: 2}, + } + + var pri = []*Card[int, int]{ + {point: 1, color: 1}, + {point: 1, color: 2}, + {point: 4, color: 3}, + {point: 5, color: 4}, + } + + var start = time.Now() + var usePub, usePri = slice.LimitedCombinations(pub, 3, 3), slice.LimitedCombinations(pri, 2, 2) + + var topResult []*Card[int, int] + var topScore int64 + var topName string + var source []*Card[int, int] + for _, handCards := range usePri { + for _, pubCards := range usePub { + cards := append(handCards, pubCards...) + name, result := matcher.Group(cards) + score := evaluate(result) + if score > topScore || topResult == nil { + topScore = score + topResult = result + topName = name + source = cards + } + } + } + + fmt.Println("time:", time.Since(start)) + fmt.Println("result:", topName) + for _, card := range topResult { + fmt.Println(fmt.Sprintf("Point: %d Color: %d", card.GetPoint(), card.GetColor())) + } + fmt.Println("source:", topScore) + for _, card := range source { + fmt.Println(fmt.Sprintf("Point: %d Color: %d", card.GetPoint(), card.GetColor())) + } +} diff --git a/game/poker/options.go b/game/poker/options.go index ed6ade5..502e819 100644 --- a/game/poker/options.go +++ b/game/poker/options.go @@ -1,13 +1,16 @@ package poker -import "fmt" +import ( + "fmt" + "github.com/kercylan98/minotaur/utils/generic" +) -type Option func(rule *Rule) +type Option[P, C generic.Number, T Card[P, C]] func(rule *Rule[P, C, T]) // WithHand 通过绑定特定牌型的方式创建扑克玩法 // - 牌型顺序决定了牌型的优先级 -func WithHand(pokerHand string, value int, handle HandHandle) Option { - return func(rule *Rule) { +func WithHand[P, C generic.Number, T Card[P, C]](pokerHand string, value int, handle HandHandle[P, C, T]) Option[P, C, T] { + return func(rule *Rule[P, C, T]) { if _, exist := rule.pokerHand[pokerHand]; exist { panic(fmt.Errorf("same poker hand name: %s", pokerHand)) } @@ -24,8 +27,8 @@ func WithHand(pokerHand string, value int, handle HandHandle) Option { } // WithHandRestraint 通过绑定特定克制牌型的方式创建扑克玩法 -func WithHandRestraint(pokerHand, restraint string) Option { - return func(rule *Rule) { +func WithHandRestraint[P, C generic.Number, T Card[P, C]](pokerHand, restraint string) Option[P, C, T] { + return func(rule *Rule[P, C, T]) { r, exist := rule.restraint[pokerHand] if !exist { r = map[string]struct{}{} @@ -37,8 +40,8 @@ func WithHandRestraint(pokerHand, restraint string) Option { // WithHandRestraintFull 通过绑定所有克制牌型的方式创建扑克玩法 // - 需要确保在牌型声明之后调用 -func WithHandRestraintFull(pokerHand string) Option { - return func(rule *Rule) { +func WithHandRestraintFull[P, C generic.Number, T Card[P, C]](pokerHand string) Option[P, C, T] { + return func(rule *Rule[P, C, T]) { for hand := range rule.pokerHand { r, exist := rule.restraint[pokerHand] if !exist { @@ -51,22 +54,22 @@ func WithHandRestraintFull(pokerHand string) Option { } // WithPointValue 通过特定的扑克点数牌值创建扑克玩法 -func WithPointValue(pointValues map[Point]int) Option { - return func(rule *Rule) { +func WithPointValue[P, C generic.Number, T Card[P, C]](pointValues map[P]int) Option[P, C, T] { + return func(rule *Rule[P, C, T]) { rule.pointValue = pointValues } } // WithColorValue 通过特定的扑克花色牌值创建扑克玩法 -func WithColorValue(colorValues map[Color]int) Option { - return func(rule *Rule) { +func WithColorValue[P, C generic.Number, T Card[P, C]](colorValues map[C]int) Option[P, C, T] { + return func(rule *Rule[P, C, T]) { rule.colorValue = colorValues } } // WithPointSort 通过特定的扑克点数顺序创建扑克玩法,顺序必须为连续的 -func WithPointSort(pointSort map[Point]int) Option { - return func(rule *Rule) { +func WithPointSort[P, C generic.Number, T Card[P, C]](pointSort map[P]int) Option[P, C, T] { + return func(rule *Rule[P, C, T]) { for k, v := range pointSort { rule.pointSort[k] = v } @@ -74,8 +77,8 @@ func WithPointSort(pointSort map[Point]int) Option { } // WithColorSort 通过特定的扑克花色顺序创建扑克玩法,顺序必须为连续的 -func WithColorSort(colorSort map[Color]int) Option { - return func(rule *Rule) { +func WithColorSort[P, C generic.Number, T Card[P, C]](colorSort map[C]int) Option[P, C, T] { + return func(rule *Rule[P, C, T]) { for k, v := range colorSort { rule.colorSort[k] = v } @@ -83,10 +86,10 @@ func WithColorSort(colorSort map[Color]int) Option { } // WithExcludeContinuityPoint 排除连续的点数 -func WithExcludeContinuityPoint(points ...Point) Option { - return func(rule *Rule) { +func WithExcludeContinuityPoint[P, C generic.Number, T Card[P, C]](points ...P) Option[P, C, T] { + return func(rule *Rule[P, C, T]) { if rule.excludeContinuityPoint == nil { - rule.excludeContinuityPoint = make(map[Point]struct{}) + rule.excludeContinuityPoint = make(map[P]struct{}) } for _, point := range points { rule.excludeContinuityPoint[point] = struct{}{} diff --git a/game/poker/point.go b/game/poker/point.go deleted file mode 100644 index dcaa2cf..0000000 --- a/game/poker/point.go +++ /dev/null @@ -1,63 +0,0 @@ -package poker - -import "strconv" - -const ( - PointA Point = 1 - Point2 Point = 2 - Point3 Point = 3 - Point4 Point = 4 - Point5 Point = 5 - Point6 Point = 6 - Point7 Point = 7 - Point8 Point = 8 - Point9 Point = 9 - Point10 Point = 10 - PointJ Point = 11 - PointQ Point = 12 - PointK Point = 13 - PointBlackJoker Point = 14 - PointRedJoker Point = 15 -) - -var defaultPointSort = map[Point]int{ - PointA: int(PointA), - Point2: int(Point2), - Point3: int(Point3), - Point4: int(Point4), - Point5: int(Point5), - Point6: int(Point6), - Point7: int(Point7), - Point8: int(Point8), - Point9: int(Point9), - Point10: int(Point10), - PointJ: int(PointJ), - PointQ: int(PointQ), - PointK: int(PointK), - PointBlackJoker: int(PointBlackJoker), - PointRedJoker: int(PointRedJoker), -} - -// Point 扑克点数 -type Point int - -func (slf Point) String() string { - var str string - switch slf { - case PointA: - str = "A" - case PointJ: - str = "J" - case PointQ: - str = "Q" - case PointK: - str = "K" - case PointBlackJoker: - str = "B" - case PointRedJoker: - str = "R" - default: - str = strconv.Itoa(int(slf)) - } - return str -} diff --git a/game/poker/poker.go b/game/poker/poker.go index c575bb2..d982e5d 100644 --- a/game/poker/poker.go +++ b/game/poker/poker.go @@ -1,9 +1,14 @@ package poker +import ( + "github.com/kercylan98/minotaur/utils/generic" + "math" +) + // IsContainJoker 检查扑克牌是否包含大小王 -func IsContainJoker(cards ...Card) bool { +func IsContainJoker[P, C generic.Number, T Card[P, C]](pile *CardPile[P, C, T], cards ...T) bool { for _, card := range cards { - if card.IsJoker() { + if IsJoker[P, C, T](pile, card) { return true } } @@ -11,8 +16,8 @@ func IsContainJoker(cards ...Card) bool { } // GroupByPoint 将扑克牌按照点数分组 -func GroupByPoint(cards ...Card) map[Point][]Card { - group := map[Point][]Card{} +func GroupByPoint[P, C generic.Number, T Card[P, C]](cards ...T) map[P][]T { + group := map[P][]T{} for _, card := range cards { group[card.GetPoint()] = append(group[card.GetPoint()], card) } @@ -20,8 +25,8 @@ func GroupByPoint(cards ...Card) map[Point][]Card { } // GroupByColor 将扑克牌按照花色分组 -func GroupByColor(cards ...Card) map[Color][]Card { - group := map[Color][]Card{} +func GroupByColor[P, C generic.Number, T Card[P, C]](cards ...T) map[C][]T { + group := map[C][]T{} for _, card := range cards { group[card.GetColor()] = append(group[card.GetColor()], card) } @@ -29,12 +34,18 @@ func GroupByColor(cards ...Card) map[Color][]Card { } // IsRocket 两张牌能否组成红黑 Joker -func IsRocket(cardA, cardB Card) bool { - return cardA.GetPoint() == PointRedJoker && cardB.GetPoint() == PointBlackJoker || cardA.GetPoint() == PointBlackJoker && cardB.GetPoint() == PointRedJoker +func IsRocket[P, C generic.Number, T Card[P, C]](pile *CardPile[P, C, T], cardA, cardB T) bool { + var num int + for _, joker := range pile.jokers { + if cardA.GetPoint() == joker || cardB.GetPoint() == joker { + num++ + } + } + return num == 2 } // IsFlush 判断是否是同花 -func IsFlush(cards ...Card) bool { +func IsFlush[P, C generic.Number, T Card[P, C]](cards ...T) bool { if len(cards) == 0 { return false } @@ -52,8 +63,8 @@ func IsFlush(cards ...Card) bool { } // GetCardsPoint 获取一组扑克牌的点数 -func GetCardsPoint(cards ...Card) []Point { - var points = make([]Point, len(cards)) +func GetCardsPoint[P, C generic.Number, T Card[P, C]](cards ...T) []P { + var points = make([]P, len(cards)) for i, card := range cards { points[i] = card.GetPoint() } @@ -61,10 +72,136 @@ func GetCardsPoint(cards ...Card) []Point { } // GetCardsColor 获取一组扑克牌的花色 -func GetCardsColor(cards ...Card) []Color { - var colors = make([]Color, len(cards)) +func GetCardsColor[P, C generic.Number, T Card[P, C]](cards ...T) []C { + var colors = make([]C, len(cards)) for i, card := range cards { colors[i] = card.GetColor() } return colors } + +// IsContain 一组扑克牌是否包含某张牌 +func IsContain[P, C generic.Number, T Card[P, C]](cards []T, card T) bool { + for _, c := range cards { + if Equal[P, C, T](c, card) { + return true + } + } + return false +} + +// IsContainAll 一组扑克牌是否包含另一组扑克牌 +func IsContainAll[P, C generic.Number, T Card[P, C]](cards []T, cards2 []T) bool { + for _, card := range cards2 { + if !IsContain[P, C, T](cards, card) { + return false + } + } + return true +} + +// GetPointAndColor 返回扑克牌的点数和花色 +func GetPointAndColor[P, C generic.Number, T Card[P, C]](card T) (P, C) { + return card.GetPoint(), card.GetColor() +} + +// EqualPoint 比较两张扑克牌的点数是否相同 +func EqualPoint[P, C generic.Number, T Card[P, C]](card1 T, card2 T) bool { + return card1.GetPoint() == card2.GetPoint() +} + +// EqualColor 比较两张扑克牌的花色是否相同 +func EqualColor[P, C generic.Number, T Card[P, C]](card1 T, card2 T) bool { + return card1.GetColor() == card2.GetColor() +} + +// Equal 比较两张扑克牌的点数和花色是否相同 +func Equal[P, C generic.Number, T Card[P, C]](card1 T, card2 T) bool { + return EqualPoint[P, C, T](card1, card2) && EqualColor[P, C, T](card1, card2) +} + +// MaxPoint 返回两张扑克牌中点数较大的一张 +func MaxPoint[P, C generic.Number, T Card[P, C]](card1 T, card2 T) T { + if card1.GetPoint() > card2.GetPoint() { + return card1 + } + return card2 +} + +// MinPoint 返回两张扑克牌中点数较小的一张 +func MinPoint[P, C generic.Number, T Card[P, C]](card1 T, card2 T) T { + if card1.GetPoint() < card2.GetPoint() { + return card1 + } + return card2 +} + +// MaxColor 返回两张扑克牌中花色较大的一张 +func MaxColor[P, C generic.Number, T Card[P, C]](card1 T, card2 T) T { + if card1.GetColor() > card2.GetColor() { + return card1 + } + return card2 +} + +// MinColor 返回两张扑克牌中花色较小的一张 +func MinColor[P, C generic.Number, T Card[P, C]](card1 T, card2 T) T { + if card1.GetColor() < card2.GetColor() { + return card1 + } + return card2 +} + +// Max 返回两张扑克牌中点数和花色较大的一张 +func Max[P, C generic.Number, T Card[P, C]](card1 T, card2 T) T { + if card1.GetPoint() > card2.GetPoint() { + return card1 + } else if card1.GetPoint() < card2.GetPoint() { + return card2 + } else if card1.GetColor() > card2.GetColor() { + return card1 + } + return card2 +} + +// Min 返回两张扑克牌中点数和花色较小的一张 +func Min[P, C generic.Number, T Card[P, C]](card1 T, card2 T) T { + if card1.GetPoint() < card2.GetPoint() { + return card1 + } else if card1.GetPoint() > card2.GetPoint() { + return card2 + } else if card1.GetColor() < card2.GetColor() { + return card1 + } + return card2 +} + +// PointDifference 计算两张扑克牌的点数差 +func PointDifference[P, C generic.Number, T Card[P, C]](card1 T, card2 T) int { + return int(math.Abs(float64(card1.GetPoint()) - float64(card2.GetPoint()))) +} + +// ColorDifference 计算两张扑克牌的花色差 +func ColorDifference[P, C generic.Number, T Card[P, C]](card1 T, card2 T) int { + return int(math.Abs(float64(card1.GetColor()) - float64(card2.GetColor()))) +} + +// IsNeighborColor 判断两张扑克牌是否为相邻的花色 +func IsNeighborColor[P, C generic.Number, T Card[P, C]](card1 T, card2 T) bool { + return ColorDifference[P, C, T](card1, card2) == 1 +} + +// IsNeighborPoint 判断两张扑克牌是否为相邻的点数 +func IsNeighborPoint[P, C generic.Number, T Card[P, C]](card1 T, card2 T) bool { + return PointDifference[P, C, T](card1, card2) == 1 +} + +// IsJoker 判断扑克牌是否为大小王 +func IsJoker[P, C generic.Number, T Card[P, C]](pile *CardPile[P, C, T], card T) bool { + for _, joker := range pile.jokers { + if card.GetPoint() == joker { + return true + } + } + return false +} diff --git a/game/poker/rule.go b/game/poker/rule.go index 3bcaad4..fde5495 100644 --- a/game/poker/rule.go +++ b/game/poker/rule.go @@ -1,16 +1,17 @@ package poker import ( + "github.com/kercylan98/minotaur/utils/generic" "github.com/kercylan98/minotaur/utils/hash" "github.com/kercylan98/minotaur/utils/maths" "sort" ) -func NewRule(options ...Option) *Rule { - poker := &Rule{ - pokerHand: map[string]HandHandle{}, - pointSort: hash.Copy(defaultPointSort), - colorSort: hash.Copy(defaultColorSort), +func NewRule[P, C generic.Number, T Card[P, C]](options ...Option[P, C, T]) *Rule[P, C, T] { + poker := &Rule[P, C, T]{ + pokerHand: map[string]HandHandle[P, C, T]{}, + //pointSort: hash.Copy(defaultPointSort), + //colorSort: hash.Copy(defaultColorSort), pokerHandValue: map[string]int{}, restraint: map[string]map[string]struct{}{}, } @@ -23,19 +24,19 @@ func NewRule(options ...Option) *Rule { return poker } -type Rule struct { - pokerHand map[string]HandHandle +type Rule[P, C generic.Number, T Card[P, C]] struct { + pokerHand map[string]HandHandle[P, C, T] pokerHandValue map[string]int - pointValue map[Point]int - colorValue map[Color]int - pointSort map[Point]int - colorSort map[Color]int - excludeContinuityPoint map[Point]struct{} + pointValue map[P]int + colorValue map[C]int + pointSort map[P]int + colorSort map[C]int + excludeContinuityPoint map[P]struct{} restraint map[string]map[string]struct{} } // GetCardCountWithPointMaximumNumber 获取指定点数的牌的数量 -func (slf *Rule) GetCardCountWithPointMaximumNumber(cards []Card, point Point, maximumNumber int) int { +func (slf *Rule[P, C, T]) GetCardCountWithPointMaximumNumber(cards []T, point P, maximumNumber int) int { count := 0 for _, card := range cards { if card.GetPoint() == point { @@ -49,7 +50,7 @@ func (slf *Rule) GetCardCountWithPointMaximumNumber(cards []Card, point Point, m } // GetCardCountWithColorMaximumNumber 获取指定花色的牌的数量 -func (slf *Rule) GetCardCountWithColorMaximumNumber(cards []Card, color Color, maximumNumber int) int { +func (slf *Rule[P, C, T]) GetCardCountWithColorMaximumNumber(cards []T, color C, maximumNumber int) int { count := 0 for _, card := range cards { if card.GetColor() == color { @@ -63,10 +64,10 @@ func (slf *Rule) GetCardCountWithColorMaximumNumber(cards []Card, color Color, m } // GetCardCountWithMaximumNumber 获取指定牌的数量 -func (slf *Rule) GetCardCountWithMaximumNumber(cards []Card, card Card, maximumNumber int) int { +func (slf *Rule[P, C, T]) GetCardCountWithMaximumNumber(cards []T, card T, maximumNumber int) int { count := 0 for _, c := range cards { - if c.Equal(card) { + if Equal[P, C, T](c, card) { count++ if count >= maximumNumber { return count @@ -77,7 +78,7 @@ func (slf *Rule) GetCardCountWithMaximumNumber(cards []Card, card Card, maximumN } // GetCardCountWithPoint 获取指定点数的牌的数量 -func (slf *Rule) GetCardCountWithPoint(cards []Card, point Point) int { +func (slf *Rule[P, C, T]) GetCardCountWithPoint(cards []T, point P) int { count := 0 for _, card := range cards { if card.GetPoint() == point { @@ -88,7 +89,7 @@ func (slf *Rule) GetCardCountWithPoint(cards []Card, point Point) int { } // GetCardCountWithColor 获取指定花色的牌的数量 -func (slf *Rule) GetCardCountWithColor(cards []Card, color Color) int { +func (slf *Rule[P, C, T]) GetCardCountWithColor(cards []T, color C) int { count := 0 for _, card := range cards { if card.GetColor() == color { @@ -99,10 +100,10 @@ func (slf *Rule) GetCardCountWithColor(cards []Card, color Color) int { } // GetCardCount 获取指定牌的数量 -func (slf *Rule) GetCardCount(cards []Card, card Card) int { +func (slf *Rule[P, C, T]) GetCardCount(cards []T, card T) int { count := 0 for _, c := range cards { - if c.Equal(card) { + if Equal[P, C, T](c, card) { count++ } } @@ -110,7 +111,7 @@ func (slf *Rule) GetCardCount(cards []Card, card Card) int { } // PokerHandIsMatch 检查两组扑克牌牌型是否匹配 -func (slf *Rule) PokerHandIsMatch(cardsA, cardsB []Card) bool { +func (slf *Rule[P, C, T]) PokerHandIsMatch(cardsA, cardsB []T) bool { handA, exist := slf.PokerHand(cardsA...) if !exist { return false @@ -126,7 +127,7 @@ func (slf *Rule) PokerHandIsMatch(cardsA, cardsB []Card) bool { } // PokerHand 获取一组扑克的牌型 -func (slf *Rule) PokerHand(cards ...Card) (pokerHand string, hit bool) { +func (slf *Rule[P, C, T]) PokerHand(cards ...T) (pokerHand string, hit bool) { for phn := range slf.pokerHandValue { if slf.pokerHand[phn](slf, cards) { return phn, true @@ -136,11 +137,11 @@ func (slf *Rule) PokerHand(cards ...Card) (pokerHand string, hit bool) { } // IsPointContinuity 检查一组扑克牌点数是否连续,count 表示了每个点数的数量 -func (slf *Rule) IsPointContinuity(count int, cards ...Card) bool { +func (slf *Rule[P, C, T]) IsPointContinuity(count int, cards ...T) bool { if len(cards) == 0 { return false } - group := GroupByPoint(cards...) + group := GroupByPoint[P, C, T](cards...) var values []int for point, cards := range group { if _, exist := slf.excludeContinuityPoint[point]; exist { @@ -155,7 +156,7 @@ func (slf *Rule) IsPointContinuity(count int, cards ...Card) bool { } // IsSameColor 检查一组扑克牌是否同花 -func (slf *Rule) IsSameColor(cards ...Card) bool { +func (slf *Rule[P, C, T]) IsSameColor(cards ...T) bool { length := len(cards) if length == 0 { return false @@ -173,7 +174,7 @@ func (slf *Rule) IsSameColor(cards ...Card) bool { } // IsSamePoint 检查一组扑克牌是否同点 -func (slf *Rule) IsSamePoint(cards ...Card) bool { +func (slf *Rule[P, C, T]) IsSamePoint(cards ...T) bool { length := len(cards) if length == 0 { return false @@ -191,7 +192,7 @@ func (slf *Rule) IsSamePoint(cards ...Card) bool { } // SortByPointDesc 将扑克牌按照点数从大到小排序 -func (slf *Rule) SortByPointDesc(cards []Card) []Card { +func (slf *Rule[P, C, T]) SortByPointDesc(cards []T) []T { sort.Slice(cards, func(i, j int) bool { return slf.pointSort[cards[i].GetPoint()] > slf.pointSort[cards[j].GetPoint()] }) @@ -199,7 +200,7 @@ func (slf *Rule) SortByPointDesc(cards []Card) []Card { } // SortByPointAsc 将扑克牌按照点数从小到大排序 -func (slf *Rule) SortByPointAsc(cards []Card) []Card { +func (slf *Rule[P, C, T]) SortByPointAsc(cards []T) []T { sort.Slice(cards, func(i, j int) bool { return slf.pointSort[cards[i].GetPoint()] < slf.pointSort[cards[j].GetPoint()] }) @@ -207,7 +208,7 @@ func (slf *Rule) SortByPointAsc(cards []Card) []Card { } // SortByColorDesc 将扑克牌按照花色从大到小排序 -func (slf *Rule) SortByColorDesc(cards []Card) []Card { +func (slf *Rule[P, C, T]) SortByColorDesc(cards []T) []T { sort.Slice(cards, func(i, j int) bool { return slf.colorSort[cards[i].GetColor()] > slf.colorSort[cards[j].GetColor()] }) @@ -215,7 +216,7 @@ func (slf *Rule) SortByColorDesc(cards []Card) []Card { } // SortByColorAsc 将扑克牌按照花色从小到大排序 -func (slf *Rule) SortByColorAsc(cards []Card) []Card { +func (slf *Rule[P, C, T]) SortByColorAsc(cards []T) []T { sort.Slice(cards, func(i, j int) bool { return slf.colorSort[cards[i].GetColor()] < slf.colorSort[cards[j].GetColor()] }) @@ -223,13 +224,13 @@ func (slf *Rule) SortByColorAsc(cards []Card) []Card { } // GetValueWithPokerHand 获取扑克牌的牌型牌值 -func (slf *Rule) GetValueWithPokerHand(hand string, cards ...Card) int { +func (slf *Rule[P, C, T]) GetValueWithPokerHand(hand string, cards ...T) int { hv := slf.pokerHandValue[hand] return hv * slf.GetValueWithCards(cards...) } // GetValueWithCards 获取扑克牌的牌值 -func (slf *Rule) GetValueWithCards(cards ...Card) int { +func (slf *Rule[P, C, T]) GetValueWithCards(cards ...T) int { var value int for _, card := range cards { value += slf.GetValueWithPoint(card.GetPoint()) @@ -239,16 +240,16 @@ func (slf *Rule) GetValueWithCards(cards ...Card) int { } // GetValueWithPoint 获取扑克牌的点数牌值 -func (slf *Rule) GetValueWithPoint(point Point) int { +func (slf *Rule[P, C, T]) GetValueWithPoint(point P) int { return slf.pointValue[point] } // GetValueWithColor 获取扑克牌的花色牌值 -func (slf *Rule) GetValueWithColor(color Color) int { +func (slf *Rule[P, C, T]) GetValueWithColor(color C) int { return slf.colorValue[color] } // CompareValueWithCards 根据特定的条件表达式比较两组扑克牌的牌值 -func (slf *Rule) CompareValueWithCards(cards1 []Card, expression maths.CompareExpression, cards2 []Card) bool { +func (slf *Rule[P, C, T]) CompareValueWithCards(cards1 []T, expression maths.CompareExpression, cards2 []T) bool { return maths.Compare(slf.GetValueWithCards(cards1...), expression, slf.GetValueWithCards(cards2...)) } diff --git a/game/room.go b/game/room.go index 275ad8b..ed3bb3d 100644 --- a/game/room.go +++ b/game/room.go @@ -1,9 +1,5 @@ package game -import ( - "github.com/kercylan98/minotaur/utils/hash" -) - // Room 房间类似于简版的游戏世界(World),不过没有游戏实体 type Room[PlayerID comparable, P Player[PlayerID]] interface { // GetGuid 获取房间的唯一标识符 @@ -13,7 +9,7 @@ type Room[PlayerID comparable, P Player[PlayerID]] interface { // GetPlayer 根据玩家id获取玩家 GetPlayer(id PlayerID) P // GetPlayers 获取房间中的所有玩家 - GetPlayers() hash.MapReadonly[PlayerID, P] + GetPlayers() map[PlayerID]P // GetPlayerCount 获取玩家数量 GetPlayerCount() int // IsExistPlayer 检查房间中是否存在特定玩家 diff --git a/game/room_seat.go b/game/room_seat.go index 05cb24c..8814417 100644 --- a/game/room_seat.go +++ b/game/room_seat.go @@ -36,4 +36,12 @@ type RoomSeat[PlayerID comparable, P Player[PlayerID]] interface { // - 超出范围将返回-1 // - 当没有下一个座位号时将始终返回本身 GetNextSeatVacancy(seat int) int + // GetPrevSeat 获取上一个座位号,空缺的位置将会被跳过 + // - 超出范围将返回-1 + // - 当没有上一个座位号时将始终返回本身 + GetPrevSeat(seat int) int + // GetPrevSeatVacancy 获取上一个座位号,空缺的位置将被保留 + // - 超出范围将返回-1 + // - 当没有上一个座位号时将始终返回本身 + GetPrevSeatVacancy(seat int) int } diff --git a/game/world.go b/game/world.go index ba2a811..82b1399 100644 --- a/game/world.go +++ b/game/world.go @@ -1,9 +1,5 @@ package game -import ( - "github.com/kercylan98/minotaur/utils/hash" -) - // World 游戏世界接口定义 type World[PlayerID comparable, P Player[PlayerID]] interface { // GetGuid 获取世界的唯一标识符 @@ -13,15 +9,15 @@ type World[PlayerID comparable, P Player[PlayerID]] interface { // GetPlayer 根据玩家id获取玩家 GetPlayer(id PlayerID) P // GetPlayers 获取世界中的所有玩家 - GetPlayers() hash.MapReadonly[PlayerID, P] + GetPlayers() map[PlayerID]P // GetActor 根据唯一标识符获取世界中的游戏对象 GetActor(guid int64) Actor // GetActors 获取世界中的所有游戏对象 - GetActors() hash.MapReadonly[int64, Actor] + GetActors() map[int64]Actor // GetPlayerActor 获取游戏世界中归属特定玩家的特定游戏对象 GetPlayerActor(id PlayerID, guid int64) Actor // GetPlayerActors 获取游戏世界中归属特定玩家的所有游戏对象 - GetPlayerActors(id PlayerID) hash.MapReadonly[int64, Actor] + GetPlayerActors(id PlayerID) map[int64]Actor // IsExistPlayer 检查游戏世界中是否存在特定玩家 IsExistPlayer(id PlayerID) bool // IsExistActor 检查游戏世界中是否存在特定游戏对象 diff --git a/go.mod b/go.mod index 83a3804..bde4afb 100644 --- a/go.mod +++ b/go.mod @@ -23,6 +23,7 @@ require ( ) require ( + github.com/alphadose/haxmap v1.2.0 // indirect github.com/bytedance/sonic v1.9.1 // indirect github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 // indirect github.com/gabriel-vasile/mimetype v1.4.2 // indirect @@ -61,6 +62,7 @@ require ( go.uber.org/multierr v1.11.0 // indirect golang.org/x/arch v0.3.0 // indirect golang.org/x/crypto v0.9.0 // indirect + golang.org/x/exp v0.0.0-20221031165847-c99f073a8326 // indirect golang.org/x/net v0.10.0 // indirect golang.org/x/sys v0.8.0 // indirect golang.org/x/text v0.9.0 // indirect diff --git a/go.sum b/go.sum index ec55401..6f9d86e 100644 --- a/go.sum +++ b/go.sum @@ -2,6 +2,8 @@ cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMT github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/RussellLuo/timingwheel v0.0.0-20220218152713-54845bda3108 h1:iPugyBI7oFtbDZXC4dnY093M1kZx6k/95sen92gafbY= github.com/RussellLuo/timingwheel v0.0.0-20220218152713-54845bda3108/go.mod h1:WAMLHwunr1hi3u7OjGV6/VWG9QbdMhGpEKjROiSFd10= +github.com/alphadose/haxmap v1.2.0 h1:noGrAmCE+gNheZ4KpW+sYj9W5uMcO1UAjbAq9XBOAfM= +github.com/alphadose/haxmap v1.2.0/go.mod h1:rjHw1IAqbxm0S3U5tD16GoKsiAd8FWx5BJ2IYqXwgmM= github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8= github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM= @@ -219,6 +221,8 @@ golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5y golang.org/x/crypto v0.9.0 h1:LF6fAI+IutBocDJ2OT0Q1g8plpYljMZ4+lty+dsqw3g= golang.org/x/crypto v0.9.0/go.mod h1:yrmDGqONDYtNj3tH8X9dzUun2m2lzPa9ngI6/RUPGR0= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20221031165847-c99f073a8326 h1:QfTh0HpN6hlw6D3vu8DAwC8pBIwikq0AI1evdm+FksE= +golang.org/x/exp v0.0.0-20221031165847-c99f073a8326/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= diff --git a/planner/pce/cs/xlsx_test.go b/planner/pce/cs/xlsx_test.go deleted file mode 100644 index 5ae90f3..0000000 --- a/planner/pce/cs/xlsx_test.go +++ /dev/null @@ -1,63 +0,0 @@ -package cs_test - -import ( - "github.com/kercylan98/minotaur/planner/pce/cs" - . "github.com/smartystreets/goconvey/convey" - "github.com/tealeg/xlsx" - "testing" -) - -func TestNewIndexXlsxConfig(t *testing.T) { - Convey("TestNewIndexXlsxConfig", t, func() { - f, err := xlsx.OpenFile("./xlsx_template.xlsx") - if err != nil { - panic(err) - } - config := cs.NewXlsx(f.Sheets[1]) - So(config, ShouldNotBeNil) - }) -} - -func TestXlsxIndexConfig_GetConfigName(t *testing.T) { - Convey("TestXlsxIndexConfig_GetConfigName", t, func() { - f, err := xlsx.OpenFile("./xlsx_template.xlsx") - if err != nil { - panic(err) - } - config := cs.NewXlsx(f.Sheets[1]) - So(config.GetConfigName(), ShouldEqual, "IndexConfig") - }) -} - -func TestXlsxIndexConfig_GetDisplayName(t *testing.T) { - Convey("TestXlsxIndexConfig_GetDisplayName", t, func() { - f, err := xlsx.OpenFile("./xlsx_template.xlsx") - if err != nil { - panic(err) - } - config := cs.NewXlsx(f.Sheets[1]) - So(config.GetDisplayName(), ShouldEqual, "有索引") - }) -} - -func TestXlsxIndexConfig_GetDescription(t *testing.T) { - Convey("TestXlsxIndexConfig_GetDescription", t, func() { - f, err := xlsx.OpenFile("./xlsx_template.xlsx") - if err != nil { - panic(err) - } - config := cs.NewXlsx(f.Sheets[1]) - So(config.GetDescription(), ShouldEqual, "暂无描述") - }) -} - -func TestXlsxIndexConfig_GetIndexCount(t *testing.T) { - Convey("TestXlsxIndexConfig_GetIndexCount", t, func() { - f, err := xlsx.OpenFile("./xlsx_template.xlsx") - if err != nil { - panic(err) - } - config := cs.NewXlsx(f.Sheets[1]) - So(config.GetIndexCount(), ShouldEqual, 2) - }) -} diff --git a/report/data_buried.go b/report/data_buried.go index b6d2daa..9ccfaf1 100644 --- a/report/data_buried.go +++ b/report/data_buried.go @@ -1,8 +1,7 @@ package report import ( - "github.com/kercylan98/minotaur/utils/asynchronous" - "github.com/kercylan98/minotaur/utils/hash" + "github.com/kercylan98/minotaur/utils/concurrent" "sync" ) @@ -10,7 +9,7 @@ import ( func NewDataBuried[DataID comparable, Data any](name string, hitLogic HitLogic[Data], options ...DataBuriedOption[DataID, Data]) *DataBuried[DataID, Data] { buried := &DataBuried[DataID, Data]{ name: name, - data: asynchronous.NewMap[DataID, Data](), + data: concurrent.NewBalanceMap[DataID, Data](), hitLogic: hitLogic, } buried.setData = func(id DataID, data Data) { @@ -29,7 +28,7 @@ func NewDataBuried[DataID comparable, Data any](name string, hitLogic HitLogic[D // - 数据埋点通常用于统计不同类型的数据,例如用户数据、商城数据等 type DataBuried[DataID comparable, Data any] struct { name string - data hash.Map[DataID, Data] + data *concurrent.BalanceMap[DataID, Data] hitLogic HitLogic[Data] getData func(DataID) Data setData func(id DataID, data Data) diff --git a/server/conn.go b/server/conn.go index 1d0fd69..0c7d347 100644 --- a/server/conn.go +++ b/server/conn.go @@ -2,7 +2,7 @@ package server import ( "github.com/gorilla/websocket" - "github.com/kercylan98/minotaur/utils/synchronization" + "github.com/kercylan98/minotaur/utils/concurrent" "github.com/panjf2000/gnet" "github.com/xtaci/kcp-go/v5" "net" @@ -75,7 +75,7 @@ type Conn struct { kcp *kcp.UDPSession data map[any]any mutex sync.Mutex - packetPool *synchronization.Pool[*connPacket] + packetPool *concurrent.Pool[*connPacket] packets []*connPacket } @@ -187,7 +187,7 @@ func (slf *Conn) WriteWithCallback(packet Packet, callback func(err error), mess // writeLoop 写循环 func (slf *Conn) writeLoop(wait *sync.WaitGroup) { - slf.packetPool = synchronization.NewPool[*connPacket](10*1024, + slf.packetPool = concurrent.NewPool[*connPacket](10*1024, func() *connPacket { return &connPacket{} }, func(data *connPacket) { diff --git a/server/cross/nats.go b/server/cross/nats.go index 8b13d21..d27c80c 100644 --- a/server/cross/nats.go +++ b/server/cross/nats.go @@ -4,8 +4,8 @@ import ( "encoding/json" "fmt" "github.com/kercylan98/minotaur/server" + "github.com/kercylan98/minotaur/utils/concurrent" "github.com/kercylan98/minotaur/utils/log" - "github.com/kercylan98/minotaur/utils/synchronization" "github.com/nats-io/nats.go" "time" ) @@ -18,7 +18,7 @@ func NewNats(url string, options ...NatsOption) *Nats { n := &Nats{ url: url, subject: "MINOTAUR_CROSS", - messagePool: synchronization.NewPool[*Message](1024*100, func() *Message { + messagePool: concurrent.NewPool[*Message](1024*100, func() *Message { return new(Message) }, func(data *Message) { data.ServerId = 0 @@ -36,7 +36,7 @@ type Nats struct { url string subject string options []nats.Option - messagePool *synchronization.Pool[*Message] + messagePool *concurrent.Pool[*Message] } func (slf *Nats) Init(server *server.Server, packetHandle func(serverId int64, packet []byte)) (err error) { diff --git a/server/server.go b/server/server.go index 210bc2e..e400f16 100644 --- a/server/server.go +++ b/server/server.go @@ -5,9 +5,9 @@ import ( "fmt" "github.com/gin-gonic/gin" "github.com/gorilla/websocket" + "github.com/kercylan98/minotaur/utils/concurrent" "github.com/kercylan98/minotaur/utils/log" "github.com/kercylan98/minotaur/utils/super" - "github.com/kercylan98/minotaur/utils/synchronization" "github.com/kercylan98/minotaur/utils/timer" "github.com/kercylan98/minotaur/utils/times" "github.com/panjf2000/ants/v2" @@ -33,7 +33,7 @@ func New(network Network, options ...Option) *Server { runtime: &runtime{messagePoolSize: DefaultMessageBufferSize, messageChannelSize: DefaultMessageChannelSize}, option: &option{}, network: network, - online: synchronization.NewMap[string, *Conn](), + online: concurrent.NewBalanceMap[string, *Conn](), closeChannel: make(chan struct{}, 1), systemSignal: make(chan os.Signal, 1), } @@ -72,26 +72,26 @@ func New(network Network, options ...Option) *Server { // Server 网络服务器 type Server struct { - *event // 事件 - *runtime // 运行时 - *option // 可选项 - network Network // 网络类型 - addr string // 侦听地址 - systemSignal chan os.Signal // 系统信号 - online *synchronization.Map[string, *Conn] // 在线连接 - ginServer *gin.Engine // HTTP模式下的路由器 - httpServer *http.Server // HTTP模式下的服务器 - grpcServer *grpc.Server // GRPC模式下的服务器 - gServer *gNet // TCP或UDP模式下的服务器 - isRunning bool // 是否正在运行 - isShutdown atomic.Bool // 是否已关闭 - closeChannel chan struct{} // 关闭信号 - ants *ants.Pool // 协程池 - messagePool *synchronization.Pool[*Message] // 消息池 - messageChannel chan *Message // 消息管道 - multiple *MultipleServer // 多服务器模式下的服务器 - multipleRuntimeErrorChan chan error // 多服务器模式下的运行时错误 - runMode RunMode // 运行模式 + *event // 事件 + *runtime // 运行时 + *option // 可选项 + network Network // 网络类型 + addr string // 侦听地址 + systemSignal chan os.Signal // 系统信号 + online *concurrent.BalanceMap[string, *Conn] // 在线连接 + ginServer *gin.Engine // HTTP模式下的路由器 + httpServer *http.Server // HTTP模式下的服务器 + grpcServer *grpc.Server // GRPC模式下的服务器 + gServer *gNet // TCP或UDP模式下的服务器 + isRunning bool // 是否正在运行 + isShutdown atomic.Bool // 是否已关闭 + closeChannel chan struct{} // 关闭信号 + ants *ants.Pool // 协程池 + messagePool *concurrent.Pool[*Message] // 消息池 + messageChannel chan *Message // 消息管道 + multiple *MultipleServer // 多服务器模式下的服务器 + multipleRuntimeErrorChan chan error // 多服务器模式下的运行时错误 + runMode RunMode // 运行模式 } // Run 使用特定地址运行服务器 @@ -115,7 +115,7 @@ func (slf *Server) Run(addr string) error { var protoAddr = fmt.Sprintf("%s://%s", slf.network, slf.addr) var messageInitFinish = make(chan struct{}, 1) var connectionInitHandle = func(callback func()) { - slf.messagePool = synchronization.NewPool[*Message](slf.messagePoolSize, + slf.messagePool = concurrent.NewPool[*Message](slf.messagePoolSize, func() *Message { return &Message{} }, diff --git a/utils/asynchronous/map.go b/utils/asynchronous/map.go deleted file mode 100644 index 36524a4..0000000 --- a/utils/asynchronous/map.go +++ /dev/null @@ -1,195 +0,0 @@ -package asynchronous - -import ( - "encoding/json" - "github.com/kercylan98/minotaur/utils/hash" -) - -func NewMap[Key comparable, Value any](options ...MapOption[Key, Value]) *Map[Key, Value] { - m := &Map[Key, Value]{ - data: make(map[Key]Value), - } - for _, option := range options { - option(m) - } - return m -} - -// Map 非并发安全的字典数据结构 -// - 可用于对 synchronization.Map 的替代 -type Map[Key comparable, Value any] struct { - data map[Key]Value -} - -func (slf *Map[Key, Value]) Set(key Key, value Value) { - slf.data[key] = value -} - -func (slf *Map[Key, Value]) Get(key Key) Value { - return slf.data[key] -} - -// AtomGetSet 原子方式获取一个值并在之后进行赋值 -func (slf *Map[Key, Value]) AtomGetSet(key Key, handle func(value Value, exist bool) (newValue Value, isSet bool)) { - value, exist := slf.data[key] - if newValue, isSet := handle(value, exist); isSet { - slf.data[key] = newValue - } -} - -// Atom 原子操作 -func (slf *Map[Key, Value]) Atom(handle func(m hash.Map[Key, Value])) { - handle(slf) -} - -func (slf *Map[Key, Value]) Exist(key Key) bool { - _, exist := slf.data[key] - return exist -} - -func (slf *Map[Key, Value]) GetExist(key Key) (Value, bool) { - value, exist := slf.data[key] - return value, exist -} - -func (slf *Map[Key, Value]) Delete(key Key) { - delete(slf.data, key) -} - -func (slf *Map[Key, Value]) DeleteGet(key Key) Value { - v := slf.data[key] - delete(slf.data, key) - return v -} - -func (slf *Map[Key, Value]) DeleteGetExist(key Key) (Value, bool) { - v, exist := slf.data[key] - delete(slf.data, key) - return v, exist -} - -func (slf *Map[Key, Value]) DeleteExist(key Key) bool { - if _, exist := slf.data[key]; !exist { - return exist - } - delete(slf.data, key) - return true -} - -func (slf *Map[Key, Value]) Clear() { - for k := range slf.data { - delete(slf.data, k) - } -} - -func (slf *Map[Key, Value]) ClearHandle(handle func(key Key, value Value)) { - for k, v := range slf.data { - handle(k, v) - delete(slf.data, k) - } -} - -func (slf *Map[Key, Value]) Range(handle func(key Key, value Value)) { - for k, v := range slf.data { - key, value := k, v - handle(key, value) - } -} - -func (slf *Map[Key, Value]) RangeSkip(handle func(key Key, value Value) bool) { - for k, v := range slf.data { - key, value := k, v - if !handle(key, value) { - continue - } - } -} - -func (slf *Map[Key, Value]) RangeBreakout(handle func(key Key, value Value) bool) { - slf.rangeBreakout(handle) -} - -func (slf *Map[Key, Value]) rangeBreakout(handle func(key Key, value Value) bool) bool { - for k, v := range slf.data { - key, value := k, v - if !handle(key, value) { - return true - } - } - return false -} - -func (slf *Map[Key, Value]) RangeFree(handle func(key Key, value Value, skip func(), breakout func())) { - slf.rangeFree(handle) -} - -func (slf *Map[Key, Value]) rangeFree(handle func(key Key, value Value, skip func(), breakout func())) bool { - var skipExec, breakoutExec bool - var skip = func() { - skipExec = true - } - var breakout = func() { - breakoutExec = true - } - for k, v := range slf.data { - key, value := k, v - handle(key, value, skip, breakout) - if skipExec { - continue - } - if breakoutExec { - break - } - } - return breakoutExec -} - -func (slf *Map[Key, Value]) Keys() []Key { - var s = make([]Key, 0, len(slf.data)) - for k := range slf.data { - s = append(s, k) - } - return s -} - -func (slf *Map[Key, Value]) Slice() []Value { - var s = make([]Value, 0, len(slf.data)) - for _, v := range slf.data { - s = append(s, v) - } - return s -} - -func (slf *Map[Key, Value]) Map() map[Key]Value { - var m = make(map[Key]Value) - for k, v := range slf.data { - m[k] = v - } - return m -} - -func (slf *Map[Key, Value]) Size() int { - return len(slf.data) -} - -// GetOne 获取一个 -func (slf *Map[Key, Value]) GetOne() (value Value) { - for _, v := range slf.data { - return v - } - return value -} - -func (slf *Map[Key, Value]) MarshalJSON() ([]byte, error) { - m := slf.Map() - return json.Marshal(m) -} - -func (slf *Map[Key, Value]) UnmarshalJSON(bytes []byte) error { - var m = make(map[Key]Value) - if err := json.Unmarshal(bytes, &m); err != nil { - return err - } - slf.data = m - return nil -} diff --git a/utils/asynchronous/map_options.go b/utils/asynchronous/map_options.go deleted file mode 100644 index 9e4284c..0000000 --- a/utils/asynchronous/map_options.go +++ /dev/null @@ -1,10 +0,0 @@ -package asynchronous - -type MapOption[Key comparable, Value any] func(m *Map[Key, Value]) - -// WithMapSource 通过传入的 map 初始化 -func WithMapSource[Key comparable, Value any](source map[Key]Value) MapOption[Key, Value] { - return func(m *Map[Key, Value]) { - m.data = source - } -} diff --git a/utils/concurrent/balance_map.go b/utils/concurrent/balance_map.go new file mode 100644 index 0000000..39f07ca --- /dev/null +++ b/utils/concurrent/balance_map.go @@ -0,0 +1,185 @@ +package concurrent + +import ( + "encoding/json" + "sync" +) + +// NewBalanceMap 创建一个并发安全且性能在普通读写和并发读写的情况下较为平衡的字典数据结构 +func NewBalanceMap[Key comparable, value any](options ...BalanceMapOption[Key, value]) *BalanceMap[Key, value] { + m := &BalanceMap[Key, value]{ + data: make(map[Key]value), + } + for _, option := range options { + option(m) + } + return m +} + +// BalanceMap 并发安全且性能在普通读写和并发读写的情况下较为平衡的字典数据结构 +// - 适用于要考虑并发读写但是并发读写的频率不高的情况 +type BalanceMap[Key comparable, Value any] struct { + lock sync.RWMutex + data map[Key]Value +} + +// Set 设置一个值 +func (slf *BalanceMap[Key, Value]) Set(key Key, value Value) { + slf.lock.Lock() + defer slf.lock.Unlock() + slf.data[key] = value +} + +// Get 获取一个值 +func (slf *BalanceMap[Key, Value]) Get(key Key) Value { + slf.lock.RLock() + defer slf.lock.RUnlock() + return slf.data[key] +} + +// Atom 原子操作 +func (slf *BalanceMap[Key, Value]) Atom(handle func(m map[Key]Value)) { + slf.lock.Lock() + handle(slf.data) + slf.lock.Unlock() +} + +// Exist 判断是否存在 +func (slf *BalanceMap[Key, Value]) Exist(key Key) bool { + slf.lock.RLock() + _, exist := slf.data[key] + slf.lock.RUnlock() + return exist +} + +// GetExist 获取一个值并判断是否存在 +func (slf *BalanceMap[Key, Value]) GetExist(key Key) (Value, bool) { + slf.lock.RLock() + value, exist := slf.data[key] + slf.lock.RUnlock() + return value, exist +} + +// Delete 删除一个值 +func (slf *BalanceMap[Key, Value]) Delete(key Key) { + slf.lock.Lock() + delete(slf.data, key) + defer slf.lock.Unlock() +} + +// DeleteGet 删除一个值并返回 +func (slf *BalanceMap[Key, Value]) DeleteGet(key Key) Value { + slf.lock.Lock() + v := slf.data[key] + delete(slf.data, key) + slf.lock.Unlock() + return v +} + +// DeleteGetExist 删除一个值并返回是否存在 +func (slf *BalanceMap[Key, Value]) DeleteGetExist(key Key) (Value, bool) { + slf.lock.Lock() + v, exist := slf.data[key] + delete(slf.data, key) + defer slf.lock.Unlock() + return v, exist +} + +// DeleteExist 删除一个值并返回是否存在 +func (slf *BalanceMap[Key, Value]) DeleteExist(key Key) bool { + slf.lock.Lock() + if _, exist := slf.data[key]; !exist { + slf.lock.Unlock() + return exist + } + delete(slf.data, key) + slf.lock.Unlock() + return true +} + +// Clear 清空 +func (slf *BalanceMap[Key, Value]) Clear() { + slf.lock.Lock() + for k := range slf.data { + delete(slf.data, k) + } + slf.lock.Unlock() +} + +// ClearHandle 清空并处理 +func (slf *BalanceMap[Key, Value]) ClearHandle(handle func(key Key, value Value)) { + slf.lock.Lock() + for k, v := range slf.data { + handle(k, v) + delete(slf.data, k) + } + slf.lock.Unlock() +} + +// Range 遍历所有值,如果 handle 返回 true 则停止遍历 +func (slf *BalanceMap[Key, Value]) Range(handle func(key Key, value Value) bool) { + slf.lock.Lock() + for k, v := range slf.data { + key, value := k, v + if handle(key, value) { + break + } + } + slf.lock.Unlock() +} + +// Keys 获取所有的键 +func (slf *BalanceMap[Key, Value]) Keys() []Key { + slf.lock.RLock() + var s = make([]Key, 0, len(slf.data)) + for k := range slf.data { + s = append(s, k) + } + slf.lock.RUnlock() + return s +} + +// Slice 获取所有的值 +func (slf *BalanceMap[Key, Value]) Slice() []Value { + slf.lock.RLock() + var s = make([]Value, 0, len(slf.data)) + for _, v := range slf.data { + s = append(s, v) + } + slf.lock.RUnlock() + return s +} + +// Map 转换为普通 map +func (slf *BalanceMap[Key, Value]) Map() map[Key]Value { + slf.lock.RLock() + var m = make(map[Key]Value) + for k, v := range slf.data { + m[k] = v + } + slf.lock.RUnlock() + return m +} + +// Size 获取数量 +func (slf *BalanceMap[Key, Value]) Size() int { + slf.lock.RLock() + defer slf.lock.RUnlock() + return len(slf.data) +} + +func (slf *BalanceMap[Key, Value]) MarshalJSON() ([]byte, error) { + m := slf.Map() + return json.Marshal(m) +} + +func (slf *BalanceMap[Key, Value]) UnmarshalJSON(bytes []byte) error { + var m = make(map[Key]Value) + if err := json.Unmarshal(bytes, &m); err != nil { + return err + } + slf.lock.Lock() + slf.data = m + slf.lock.Unlock() + return nil +} diff --git a/utils/concurrent/balance_map_options.go b/utils/concurrent/balance_map_options.go new file mode 100644 index 0000000..f49dc6b --- /dev/null +++ b/utils/concurrent/balance_map_options.go @@ -0,0 +1,11 @@ +package concurrent + +// BalanceMapOption BalanceMap 的选项 +type BalanceMapOption[Key comparable, Value any] func(m *BalanceMap[Key, Value]) + +// WithBalanceMapSource 通过传入的 map 初始化 +func WithBalanceMapSource[Key comparable, Value any](source map[Key]Value) BalanceMapOption[Key, Value] { + return func(m *BalanceMap[Key, Value]) { + m.data = source + } +} diff --git a/utils/synchronization/pool.go b/utils/concurrent/pool.go similarity index 98% rename from utils/synchronization/pool.go rename to utils/concurrent/pool.go index 9b2ed24..8f69150 100644 --- a/utils/synchronization/pool.go +++ b/utils/concurrent/pool.go @@ -1,4 +1,4 @@ -package synchronization +package concurrent import ( "github.com/kercylan98/minotaur/utils/log" diff --git a/utils/synchronization/slice.go b/utils/concurrent/slice.go similarity index 97% rename from utils/synchronization/slice.go rename to utils/concurrent/slice.go index 4362615..ac70601 100644 --- a/utils/synchronization/slice.go +++ b/utils/concurrent/slice.go @@ -1,4 +1,4 @@ -package synchronization +package concurrent import ( "github.com/kercylan98/minotaur/utils/slice" diff --git a/utils/synchronization/slice_option.go b/utils/concurrent/slice_option.go similarity index 94% rename from utils/synchronization/slice_option.go rename to utils/concurrent/slice_option.go index ce6fa13..e6b288a 100644 --- a/utils/synchronization/slice_option.go +++ b/utils/concurrent/slice_option.go @@ -1,4 +1,4 @@ -package synchronization +package concurrent type SliceOption[T any] func(slice *Slice[T]) diff --git a/utils/hash/map.go b/utils/hash/map.go deleted file mode 100644 index acb79c7..0000000 --- a/utils/hash/map.go +++ /dev/null @@ -1,29 +0,0 @@ -package hash - -// Map 提供了map集合接口 -type Map[Key comparable, Value any] interface { - Set(key Key, value Value) - Get(key Key) Value - // AtomGetSet 原子方式获取一个值并在之后进行赋值 - AtomGetSet(key Key, handle func(value Value, exist bool) (newValue Value, isSet bool)) - // Atom 原子操作 - Atom(handle func(m Map[Key, Value])) - Exist(key Key) bool - GetExist(key Key) (Value, bool) - Delete(key Key) - DeleteGet(key Key) Value - DeleteGetExist(key Key) (Value, bool) - DeleteExist(key Key) bool - Clear() - ClearHandle(handle func(key Key, value Value)) - Range(handle func(key Key, value Value)) - RangeSkip(handle func(key Key, value Value) bool) - RangeBreakout(handle func(key Key, value Value) bool) - RangeFree(handle func(key Key, value Value, skip func(), breakout func())) - Keys() []Key - Slice() []Value - Map() map[Key]Value - Size() int - // GetOne 获取一个 - GetOne() (value Value) -} diff --git a/utils/hash/map_readonly.go b/utils/hash/map_readonly.go deleted file mode 100644 index 67a442d..0000000 --- a/utils/hash/map_readonly.go +++ /dev/null @@ -1,17 +0,0 @@ -package hash - -// MapReadonly 只读字典接口 -type MapReadonly[Key comparable, Value any] interface { - Get(key Key) Value - Exist(key Key) bool - GetExist(key Key) (Value, bool) - Range(handle func(key Key, value Value)) - RangeSkip(handle func(key Key, value Value) bool) - RangeBreakout(handle func(key Key, value Value) bool) - RangeFree(handle func(key Key, value Value, skip func(), breakout func())) - Keys() []Key - Slice() []Value - Map() map[Key]Value - Size() int - GetOne() (value Value) -} diff --git a/utils/slice/slice.go b/utils/slice/slice.go index 4c34423..63b6aa8 100644 --- a/utils/slice/slice.go +++ b/utils/slice/slice.go @@ -197,3 +197,125 @@ func ContainsAny[V any](slice []V, values V) bool { } return false } + +// GetIndex 判断数组是否包含某个元素,如果包含则返回索引 +func GetIndex[V comparable](slice []V, value V) int { + for i, v := range slice { + if v == value { + return i + } + } + return -1 +} + +// GetIndexAny 判断数组是否包含某个元素,如果包含则返回索引 +func GetIndexAny[V any](slice []V, values V) int { + for i, v := range slice { + if reflect.DeepEqual(v, values) { + return i + } + } + return -1 +} + +// Combinations 获取给定数组的所有组合,包括重复元素的组合 +func Combinations[T any](a []T) [][]T { + n := len(a) + + // 去除重复元素,保留唯一元素 + uniqueSet := make(map[uintptr]bool) + uniqueSlice := make([]T, 0, n) + for _, val := range a { + ptr := reflect.ValueOf(val).Pointer() + if !uniqueSet[ptr] { + uniqueSet[ptr] = true + uniqueSlice = append(uniqueSlice, val) + } + } + + n = len(uniqueSlice) // 去重后的数组长度 + totalCombinations := 1 << n // 2的n次方 + var result [][]T + for i := 0; i < totalCombinations; i++ { + var currentCombination []T + for j := 0; j < n; j++ { + if (i & (1 << j)) != 0 { + currentCombination = append(currentCombination, uniqueSlice[j]) + } + } + result = append(result, currentCombination) + } + return result +} + +// LimitedCombinations 获取给定数组的所有组合,且每个组合的成员数量限制在指定范围内 +func LimitedCombinations[T any](a []T, minSize, maxSize int) [][]T { + n := len(a) + if n == 0 || minSize <= 0 || maxSize <= 0 || minSize > maxSize { + return nil + } + + var result [][]T + var currentCombination []T + + var backtrack func(startIndex int, currentSize int) + backtrack = func(startIndex int, currentSize int) { + if currentSize >= minSize && currentSize <= maxSize { + combination := make([]T, len(currentCombination)) + copy(combination, currentCombination) + result = append(result, combination) + } + + for i := startIndex; i < n; i++ { + currentCombination = append(currentCombination, a[i]) + backtrack(i+1, currentSize+1) + currentCombination = currentCombination[:len(currentCombination)-1] + } + } + + backtrack(0, 0) + return result +} + +// IsIntersectWithCheck 判断两个切片是否有交集 +func IsIntersectWithCheck[T any](a, b []T, checkHandle func(a, b T) bool) bool { + for _, a := range a { + for _, b := range b { + if checkHandle(a, b) { + return true + } + } + } + return false +} + +// IsIntersect 判断两个切片是否有交集 +func IsIntersect[T any](a, b []T) bool { + for _, a := range a { + for _, b := range b { + if reflect.DeepEqual(a, b) { + return true + } + } + } + return false +} + +// SubWithCheck 获取移除指定元素后的切片 +// - checkHandle 返回 true 表示需要移除 +func SubWithCheck[T any](a, b []T, checkHandle func(a, b T) bool) []T { + var result []T + for _, a := range a { + flag := false + for _, b := range b { + if checkHandle(a, b) { + flag = true + break + } + } + if !flag { + result = append(result, a) + } + } + return result +} diff --git a/utils/slice/slice_test.go b/utils/slice/slice_test.go new file mode 100644 index 0000000..b9b1f26 --- /dev/null +++ b/utils/slice/slice_test.go @@ -0,0 +1,14 @@ +package slice_test + +import ( + "fmt" + "github.com/kercylan98/minotaur/utils/slice" + "testing" +) + +func TestLimitedCombinations(t *testing.T) { + c := slice.LimitedCombinations([]int{1, 2, 3, 4, 5}, 3, 3) + for _, v := range c { + fmt.Println(v) + } +} diff --git a/utils/sole/guid.go b/utils/sole/guid.go index c5d84dd..f7199ff 100644 --- a/utils/sole/guid.go +++ b/utils/sole/guid.go @@ -1,37 +1,43 @@ package sole -import "sync" - -var ( - global int64 - namespace map[any]int64 - mutex sync.Mutex +import ( + "sync/atomic" ) -func init() { - namespace = map[any]int64{} +var ( + global atomic.Int64 // 全局唯一标识符 + namespace = map[any]*atomic.Int64{} // 唯一标识符命名空间 +) + +// RegNameSpace 注册特定命名空间的唯一标识符 +func RegNameSpace(name any) { + if namespace == nil { + namespace = map[any]*atomic.Int64{} + } + namespace[name] = new(atomic.Int64) } +// UnRegNameSpace 解除注销特定命名空间的唯一标识符 +func UnRegNameSpace(name any) { + delete(namespace, name) +} + +// Get 获取全局唯一标识符 func Get() int64 { - global++ - return global + return global.Add(1) } +// Reset 重置全局唯一标识符 +func Reset() { + global.Store(0) +} + +// GetWith 获取特定命名空间的唯一标识符 func GetWith(name any) int64 { - namespace[name]++ - return namespace[name] + return namespace[name].Add(1) } -func GetSync() int64 { - mutex.Lock() - defer mutex.Unlock() - global++ - return global -} - -func GetSyncWith(name any) int64 { - mutex.Lock() - defer mutex.Unlock() - namespace[name]++ - return namespace[name] +// ResetWith 重置特定命名空间的唯一标识符 +func ResetWith(name any) { + namespace[name].Store(0) } diff --git a/utils/storage/data.go b/utils/storage/data.go new file mode 100644 index 0000000..62bdf31 --- /dev/null +++ b/utils/storage/data.go @@ -0,0 +1,20 @@ +package storage + +// newData 创建一个数据存储 +func newData[Body any](body Body) *Data[Body] { + data := &Data[Body]{ + body: body, + } + return data +} + +// Data 数据存储 +// - 数据存储默认拥有一个 Body 字段 +type Data[Body any] struct { + body Body +} + +// Handle 处理数据 +func (slf *Data[Body]) Handle(handler func(data Body)) { + handler(slf.body) +} diff --git a/utils/storage/data_test.go b/utils/storage/data_test.go new file mode 100644 index 0000000..a5b3d11 --- /dev/null +++ b/utils/storage/data_test.go @@ -0,0 +1,45 @@ +package storage_test + +import ( + "encoding/json" + "fmt" + "github.com/kercylan98/minotaur/utils/storage" + "testing" +) + +type Player struct { + ID string `json:"id"` + Name string `json:"name"` + Power int64 `json:"power"` +} + +func TestData_Struct(t *testing.T) { + player := storage.NewSet[string, *Player](new(Player), + func(data *Player) string { + return data.ID + }, storage.WithIndex[string, string, *Player]("id", func(data *Player) string { + return data.ID + }), storage.WithIndex[string, string, *Player]("name", func(data *Player) string { + return data.Name + }), + ) + + p := player.New() + + p.Handle(func(data *Player) { + data.ID = "1" + data.Name = "kercylan" + data.Power = 100 + }) + + str, err := player.Struct(p) + if err != nil { + panic(err) + } + bytes, err := json.Marshal(str) + if err != nil { + panic(err) + } + + fmt.Println(string(bytes)) +} diff --git a/utils/storage/global_data.go b/utils/storage/global_data.go deleted file mode 100644 index 63eb32c..0000000 --- a/utils/storage/global_data.go +++ /dev/null @@ -1,51 +0,0 @@ -package storage - -// NewGlobalData 创建全局数据 -func NewGlobalData[T any](name string, storage GlobalDataStorage[T]) *GlobalData[T] { - data := &GlobalData[T]{ - storage: storage, - name: name, - data: storage.Load(name), - } - globalDataSaveHandles = append(globalDataSaveHandles, data.SaveData) - return data -} - -// GlobalData 全局数据 -type GlobalData[T any] struct { - storage GlobalDataStorage[T] // 全局数据存储器 - name string // 全局数据名称 - data T // 数据 -} - -// GetName 获取名称 -func (slf *GlobalData[T]) GetName() string { - return slf.name -} - -// GetData 获取数据 -func (slf *GlobalData[T]) GetData() T { - return slf.data -} - -// LoadData 加载数据 -func (slf *GlobalData[T]) LoadData() { - slf.data = slf.storage.Load(slf.GetName()) -} - -// SaveData 保存数据 -func (slf *GlobalData[T]) SaveData() error { - return slf.storage.Save(slf.GetName(), slf.GetData()) -} - -// Handle 处理数据 -func (slf *GlobalData[T]) Handle(handler func(name string, data T)) *GlobalData[T] { - handler(slf.GetName(), slf.GetData()) - return slf -} - -// HandleWithCallback 处理数据 -func (slf *GlobalData[T]) HandleWithCallback(handler func(name string, data T) error, callback func(err error)) *GlobalData[T] { - callback(handler(slf.GetName(), slf.GetData())) - return slf -} diff --git a/utils/storage/global_data_storage.go b/utils/storage/global_data_storage.go deleted file mode 100644 index 872c368..0000000 --- a/utils/storage/global_data_storage.go +++ /dev/null @@ -1,10 +0,0 @@ -package storage - -// GlobalDataStorage 全局数据存储器接口 -type GlobalDataStorage[T any] interface { - // Load 加载全局数据 - // - 当全局数据不存在时,应当返回新的全局数据实例 - Load(name string) T - // Save 保存全局数据 - Save(name string, data T) error -} diff --git a/utils/storage/index_data.go b/utils/storage/index_data.go deleted file mode 100644 index 71ac801..0000000 --- a/utils/storage/index_data.go +++ /dev/null @@ -1,92 +0,0 @@ -package storage - -import ( - "github.com/kercylan98/minotaur/utils/generic" - "time" -) - -// NewIndexData 创建索引数据 -func NewIndexData[I generic.Ordered, T IndexDataItem[I]](name string, storage IndexDataStorage[I, T]) *IndexData[I, T] { - data := &IndexData[I, T]{ - storage: storage, - name: name, - data: storage.LoadAll(name), - } - return data -} - -// IndexData 全局数据 -type IndexData[I generic.Ordered, T IndexDataItem[I]] struct { - storage IndexDataStorage[I, T] // 存储器 - name string // 数据组名称 - data map[I]T // 数据 -} - -// GetName 获取名称 -func (slf *IndexData[I, T]) GetName() string { - return slf.name -} - -// GetData 获取数据 -func (slf *IndexData[I, T]) GetData(index I) T { - data, exist := slf.data[index] - if !exist { - slf.LoadData(index) - data = slf.data[index] - } - return data -} - -// GetAllData 获取所有数据 -func (slf *IndexData[I, T]) GetAllData() map[I]T { - return slf.data -} - -// LoadData 加载数据 -func (slf *IndexData[I, T]) LoadData(index I) { - slf.data[index] = slf.storage.Load(slf.GetName(), index) -} - -// LoadAllData 加载所有数据 -func (slf *IndexData[I, T]) LoadAllData() { - slf.data = slf.storage.LoadAll(slf.GetName()) -} - -// SaveData 保存数据 -func (slf *IndexData[I, T]) SaveData(index I) error { - return slf.storage.Save(slf.GetName(), index, slf.GetData(index)) -} - -// SaveAllData 保存所有数据 -// - errHandle 错误处理中如果返回 false 将重试,否则跳过当前保存下一个 -func (slf *IndexData[I, T]) SaveAllData(errHandle func(err error) bool, retryInterval time.Duration) { - slf.storage.SaveAll(slf.GetName(), slf.GetAllData(), errHandle, retryInterval) -} - -// DeleteData 删除数据 -func (slf *IndexData[I, T]) DeleteData(index I) *IndexData[I, T] { - slf.storage.Delete(slf.GetName(), index) - delete(slf.data, index) - return slf -} - -// DeleteAllData 删除所有数据 -func (slf *IndexData[I, T]) DeleteAllData() *IndexData[I, T] { - slf.storage.DeleteAll(slf.GetName()) - for k := range slf.data { - delete(slf.data, k) - } - return slf -} - -// Handle 处理数据 -func (slf *IndexData[I, T]) Handle(index I, handler func(name string, index I, data T)) *IndexData[I, T] { - handler(slf.GetName(), index, slf.GetData(index)) - return slf -} - -// HandleWithCallback 处理数据 -func (slf *IndexData[I, T]) HandleWithCallback(index I, handler func(name string, index I, data T) error, callback func(err error)) *IndexData[I, T] { - callback(handler(slf.GetName(), index, slf.GetData(index))) - return slf -} diff --git a/utils/storage/index_data_item.go b/utils/storage/index_data_item.go deleted file mode 100644 index 86da5b6..0000000 --- a/utils/storage/index_data_item.go +++ /dev/null @@ -1,7 +0,0 @@ -package storage - -import "github.com/kercylan98/minotaur/utils/generic" - -type IndexDataItem[I generic.Ordered] interface { - GetIndex() I -} diff --git a/utils/storage/index_data_storage.go b/utils/storage/index_data_storage.go deleted file mode 100644 index 4ef0d74..0000000 --- a/utils/storage/index_data_storage.go +++ /dev/null @@ -1,23 +0,0 @@ -package storage - -import ( - "github.com/kercylan98/minotaur/utils/generic" - "time" -) - -// IndexDataStorage 全局数据存储器接口 -type IndexDataStorage[I generic.Ordered, T IndexDataItem[I]] interface { - // Load 加载特定索引数据 - // - 通常情况下当数据不存在时,应当返回空指针 - Load(name string, index I) T - // LoadAll 加载所有数据 - LoadAll(name string) map[I]T - // Save 保存特定索引数据 - Save(name string, index I, data T) error - // SaveAll 保存所有数据 - SaveAll(name string, data map[I]T, errHandle func(err error) bool, retryInterval time.Duration) - // Delete 删除特定索引数据 - Delete(name string, index I) - // DeleteAll 删除所有数据 - DeleteAll(name string) -} diff --git a/utils/storage/set.go b/utils/storage/set.go new file mode 100644 index 0000000..71f07fe --- /dev/null +++ b/utils/storage/set.go @@ -0,0 +1,97 @@ +package storage + +import ( + jsonIter "github.com/json-iterator/go" + "github.com/kercylan98/minotaur/utils/generic" + "github.com/kercylan98/minotaur/utils/str" + "reflect" +) + +var json = jsonIter.ConfigCompatibleWithStandardLibrary + +func NewSet[PrimaryKey generic.Ordered, Body any](zero Body, getIndex func(data Body) PrimaryKey, options ...SetOption[PrimaryKey, Body]) *Set[PrimaryKey, Body] { + set := &Set[PrimaryKey, Body]{ + zero: zero, + tf: reflect.Indirect(reflect.ValueOf(zero)).Type(), + getIndex: getIndex, + bodyField: reflect.StructField{ + Name: DefaultBodyFieldName, + Type: reflect.TypeOf(str.None), + }, + items: make(map[PrimaryKey]*Data[Body]), + } + for _, option := range options { + option(set) + } + return set +} + +// Set 数据集合 +type Set[PrimaryKey generic.Ordered, Body any] struct { + storage Storage[PrimaryKey, Body] + zero Body + tf reflect.Type + getIndex func(data Body) PrimaryKey + bodyField reflect.StructField + indexes []reflect.StructField + getIndexValue map[string]func(data Body) any + items map[PrimaryKey]*Data[Body] +} + +// New 创建一份新数据 +// - 这份数据不会被存储 +func (slf *Set[PrimaryKey, Body]) New() *Data[Body] { + var data = reflect.New(slf.tf).Interface().(Body) + return newData(data) +} + +// Get 通过主键获取数据 +// - 优先从内存中加载,如果数据不存在,则尝试从存储中加载 +func (slf *Set[PrimaryKey, Body]) Get(index PrimaryKey) (*Data[Body], error) { + if data, exist := slf.items[index]; exist { + return data, nil + } + body, err := slf.storage.Load(index) + if err != nil { + return nil, err + } + data := newData(body) + slf.items[index] = data + return data, nil +} + +// Set 设置数据 +// - 该方法会将数据存储到内存中 +func (slf *Set[PrimaryKey, Body]) Set(data *Data[Body]) { + slf.items[slf.getIndex(data.body)] = data +} + +// Save 保存数据 +// - 该方法会将数据存储到存储器中 +func (slf *Set[PrimaryKey, Body]) Save(data *Data[Body]) error { + return slf.storage.Save(slf, slf.getIndex(data.body), data.body) +} + +// Struct 将数据存储转换为结构体 +func (slf *Set[PrimaryKey, Body]) Struct(data *Data[Body]) (any, error) { + var fields = make([]reflect.StructField, 0, len(slf.indexes)+1) + for _, field := range append(slf.indexes, slf.bodyField) { + fields = append(fields, field) + } + instance := reflect.New(reflect.StructOf(fields)) + value := instance.Elem() + for _, field := range slf.indexes { + get, exist := slf.getIndexValue[field.Name] + if !exist { + continue + } + value.FieldByName(field.Name).Set(reflect.ValueOf(get(data.body))) + } + bytes, err := json.Marshal(data.body) + if err != nil { + return nil, err + } + + value.FieldByName(slf.bodyField.Name).Set(reflect.ValueOf(string(bytes))) + return value.Interface(), nil +} diff --git a/utils/storage/set_options.go b/utils/storage/set_options.go new file mode 100644 index 0000000..51166d8 --- /dev/null +++ b/utils/storage/set_options.go @@ -0,0 +1,65 @@ +package storage + +import ( + "github.com/kercylan98/minotaur/utils/generic" + "github.com/kercylan98/minotaur/utils/str" + "reflect" + "strings" +) + +const ( + DefaultBodyFieldName = "Data" +) + +type SetOption[PrimaryKey generic.Ordered, Body any] func(set *Set[PrimaryKey, Body]) + +// WithIndex 添加一个索引 +// - 索引将会在数据结构体中创建一个字段,这个字段必须可以在 Body 内部找到,用于对查找功能的拓展 +func WithIndex[PrimaryKey generic.Ordered, Index generic.Ordered, Body any](name string, getValue func(data Body) Index) SetOption[PrimaryKey, Body] { + return func(set *Set[PrimaryKey, Body]) { + WithTagIndex[PrimaryKey, Index, Body](name, nil, getValue)(set) + } +} + +// WithTagIndex 添加一个带 tag 的索引 +// - 同 WithIndex,但是可以自定义索引的 tag +func WithTagIndex[PrimaryKey generic.Ordered, Index generic.Ordered, Body any](name string, tags []string, getValue func(data Body) Index) SetOption[PrimaryKey, Body] { + return func(set *Set[PrimaryKey, Body]) { + value := getValue(set.zero) + upperName := str.FirstUpper(name) + if set.getIndexValue == nil { + set.getIndexValue = map[string]func(data Body) any{} + } + set.getIndexValue[upperName] = func(data Body) any { + return getValue(data) + } + var tag string + if len(tags) > 0 { + tag = strings.Join(tags, " ") + } + set.indexes = append(set.indexes, reflect.StructField{ + Name: upperName, + Type: reflect.TypeOf(value), + Tag: reflect.StructTag(tag), + }) + } +} + +// WithBodyName 设置 Body 字段名称 +// - 默认字段名称为 DefaultBodyFieldName +func WithBodyName[PrimaryKey generic.Ordered, Body any](name string) SetOption[PrimaryKey, Body] { + return func(set *Set[PrimaryKey, Body]) { + if len(name) == 0 { + return + } + set.bodyField.Name = str.FirstUpper(name) + } +} + +// WithBodyTag 设置 Body 字段标签 +// - 如果有多个标签,将会以空格分隔,例如:`json:"data" yaml:"data"` +func WithBodyTag[PrimaryKey generic.Ordered, Body any](tags ...string) SetOption[PrimaryKey, Body] { + return func(set *Set[PrimaryKey, Body]) { + set.bodyField.Tag = reflect.StructTag(strings.Join(tags, " ")) + } +} diff --git a/utils/storage/storage.go b/utils/storage/storage.go index 608b959..4491839 100644 --- a/utils/storage/storage.go +++ b/utils/storage/storage.go @@ -1,26 +1,11 @@ package storage -import "time" +import "github.com/kercylan98/minotaur/utils/generic" -var ( - // globalDataSaveHandles 全局数据保存句柄 - globalDataSaveHandles []func() error -) +type Storage[PrimaryKey generic.Ordered, Body any] interface { + // Load 加载数据 + Load(index PrimaryKey) (Body, error) -// SaveAll 保存所有数据 -// - errorHandle 错误处理中如果返回 false 将重试,否则跳过当前保存下一个 -func SaveAll(errorHandle func(err error) bool, retryInterval time.Duration) { - var err error - for _, handle := range globalDataSaveHandles { - for { - if err = handle(); err != nil { - if !errorHandle(err) { - time.Sleep(retryInterval) - continue - } - break - } - break - } - } + // Save 保存数据 + Save(set *Set[PrimaryKey, Body], index PrimaryKey, data any) error } diff --git a/utils/storage/storages/encode.go b/utils/storage/storages/encode.go deleted file mode 100644 index f34958d..0000000 --- a/utils/storage/storages/encode.go +++ /dev/null @@ -1,23 +0,0 @@ -package storages - -import "encoding/json" - -// FileStorageEncoder 全局数据文件存储编码器 -type FileStorageEncoder[T any] func(data T) ([]byte, error) - -// FileStorageDecoder 全局数据文件存储解码器 -type FileStorageDecoder[T any] func(bytes []byte, data T) error - -// FileStorageJSONEncoder JSON 编码器 -func FileStorageJSONEncoder[T any]() FileStorageEncoder[T] { - return func(data T) ([]byte, error) { - return json.Marshal(data) - } -} - -// FileStorageJSONDecoder JSON 解码器 -func FileStorageJSONDecoder[T any]() FileStorageDecoder[T] { - return func(bytes []byte, data T) error { - return json.Unmarshal(bytes, data) - } -} diff --git a/utils/storage/storages/example-data/global_data_file_test.stock b/utils/storage/storages/example-data/global_data_file_test.stock deleted file mode 100644 index 7359430..0000000 --- a/utils/storage/storages/example-data/global_data_file_test.stock +++ /dev/null @@ -1 +0,0 @@ -{"CreateAt":"2023-07-19T14:49:35.7235348+08:00","TotalCount":10} \ No newline at end of file diff --git a/utils/storage/storages/example-data/index_data_file_test.INDEX_001.stock b/utils/storage/storages/example-data/index_data_file_test.INDEX_001.stock deleted file mode 100644 index c730b47..0000000 --- a/utils/storage/storages/example-data/index_data_file_test.INDEX_001.stock +++ /dev/null @@ -1 +0,0 @@ -{"ID":"INDEX_001","Value":10} \ No newline at end of file diff --git a/utils/storage/storages/global_data_file.go b/utils/storage/storages/global_data_file.go deleted file mode 100644 index 8d1e713..0000000 --- a/utils/storage/storages/global_data_file.go +++ /dev/null @@ -1,63 +0,0 @@ -package storages - -import ( - "fmt" - "github.com/kercylan98/minotaur/utils/file" - "path/filepath" -) - -const ( - // GlobalDataFileDefaultSuffix 是 GlobalDataFileStorage 的文件默认后缀 - GlobalDataFileDefaultSuffix = "stock" -) - -// NewGlobalDataFileStorage 创建一个 GlobalDataFileStorage,dir 是文件存储目录,generate 是生成数据的函数,options 是可选参数 -// - 生成的文件名为 ${name}.${suffix},可以通过 WithGlobalDataFileStorageSuffix 修改后缀 -// - 默认使用 JSON 格式存储,可以通过 WithGlobalDataFileStorageEncoder 和 WithGlobalDataFileStorageDecoder 修改编码和解码函数 -// - 内置了 JSON 编码和解码函数,可以通过 FileStorageJSONEncoder 和 FileStorageJSONDecoder 获取 -func NewGlobalDataFileStorage[T any](dir string, generate func(name string) T, options ...GlobalDataFileStorageOption[T]) *GlobalDataFileStorage[T] { - abs, err := filepath.Abs(dir) - if err != nil { - panic(err) - } - storage := &GlobalDataFileStorage[T]{ - dir: abs, - suffix: GlobalDataFileDefaultSuffix, - generate: generate, - encoder: FileStorageJSONEncoder[T](), - decoder: FileStorageJSONDecoder[T](), - } - for _, option := range options { - option(storage) - } - return storage -} - -// GlobalDataFileStorage 用于存储全局数据的文件存储器 -type GlobalDataFileStorage[T any] struct { - dir string - suffix string - generate func(name string) T - encoder FileStorageEncoder[T] - decoder FileStorageDecoder[T] -} - -// Load 从文件中加载数据,如果文件不存在则使用 generate 生成数据 -func (slf *GlobalDataFileStorage[T]) Load(name string) T { - bytes, err := file.ReadOnce(filepath.Join(slf.dir, fmt.Sprintf("%s.%s", name, slf.suffix))) - if err != nil { - return slf.generate(name) - } - var data = slf.generate(name) - _ = slf.decoder(bytes, data) - return data -} - -// Save 将数据保存到文件中 -func (slf *GlobalDataFileStorage[T]) Save(name string, data T) error { - bytes, err := slf.encoder(data) - if err != nil { - return err - } - return file.WriterFile(filepath.Join(slf.dir, fmt.Sprintf("%s.%s", name, slf.suffix)), bytes) -} diff --git a/utils/storage/storages/global_data_file_example_test.go b/utils/storage/storages/global_data_file_example_test.go deleted file mode 100644 index a9f3186..0000000 --- a/utils/storage/storages/global_data_file_example_test.go +++ /dev/null @@ -1,19 +0,0 @@ -package storages_test - -import ( - "fmt" - "github.com/kercylan98/minotaur/utils/storage/storages" - "time" -) - -func ExampleNewGlobalDataFileStorage() { - storage := storages.NewGlobalDataFileStorage[*GlobalData](".", func(name string) *GlobalData { - return &GlobalData{ - CreateAt: time.Now(), - } - }) - - fmt.Println(storage != nil) - // Output: - // true -} diff --git a/utils/storage/storages/global_data_file_options.go b/utils/storage/storages/global_data_file_options.go deleted file mode 100644 index 1f05719..0000000 --- a/utils/storage/storages/global_data_file_options.go +++ /dev/null @@ -1,28 +0,0 @@ -package storages - -// GlobalDataFileStorageOption 全局数据文件存储选项 -type GlobalDataFileStorageOption[T any] func(storage *GlobalDataFileStorage[T]) - -// WithGlobalDataFileStorageEncoder 设置编码器 -// - 默认为 FileStorageJSONEncoder 编码器 -func WithGlobalDataFileStorageEncoder[T any](encoder FileStorageEncoder[T]) GlobalDataFileStorageOption[T] { - return func(storage *GlobalDataFileStorage[T]) { - storage.encoder = encoder - } -} - -// WithGlobalDataFileStorageDecoder 设置解码器 -// - 默认为 FileStorageJSONDecoder 解码器 -func WithGlobalDataFileStorageDecoder[T any](decoder FileStorageDecoder[T]) GlobalDataFileStorageOption[T] { - return func(storage *GlobalDataFileStorage[T]) { - storage.decoder = decoder - } -} - -// WithGlobalDataFileStorageSuffix 设置文件后缀 -// - 默认为 GlobalDataFileDefaultSuffix -func WithGlobalDataFileStorageSuffix[T any](suffix string) GlobalDataFileStorageOption[T] { - return func(storage *GlobalDataFileStorage[T]) { - storage.suffix = suffix - } -} diff --git a/utils/storage/storages/global_data_file_test.go b/utils/storage/storages/global_data_file_test.go deleted file mode 100644 index 9241893..0000000 --- a/utils/storage/storages/global_data_file_test.go +++ /dev/null @@ -1,42 +0,0 @@ -package storages_test - -import ( - "github.com/kercylan98/minotaur/utils/storage" - "github.com/kercylan98/minotaur/utils/storage/storages" - . "github.com/smartystreets/goconvey/convey" - "testing" - "time" -) - -type GlobalData struct { - CreateAt time.Time - TotalCount int -} - -func TestGlobalDataFileStorage_Save(t *testing.T) { - Convey("TestGlobalDataFileStorage_Save", t, func() { - data := storage.NewGlobalData[*GlobalData]("global_data_file_test", storages.NewGlobalDataFileStorage[*GlobalData]("./example-data", func(name string) *GlobalData { - return &GlobalData{ - CreateAt: time.Now(), - } - })) - data.Handle(func(name string, data *GlobalData) { - data.TotalCount = 10 - }) - if err := data.SaveData(); err != nil { - t.Fatal(err) - } - So(data.GetData().TotalCount, ShouldEqual, 10) - }) -} - -func TestGlobalDataFileStorage_Load(t *testing.T) { - Convey("TestGlobalDataFileStorage_Load", t, func() { - data := storage.NewGlobalData[*GlobalData]("global_data_file_test", storages.NewGlobalDataFileStorage[*GlobalData]("./example-data", func(name string) *GlobalData { - return &GlobalData{ - CreateAt: time.Now(), - } - })) - So(data.GetData().TotalCount, ShouldEqual, 10) - }) -} diff --git a/utils/storage/storages/index_data_file.go b/utils/storage/storages/index_data_file.go deleted file mode 100644 index 4459dae..0000000 --- a/utils/storage/storages/index_data_file.go +++ /dev/null @@ -1,117 +0,0 @@ -package storages - -import ( - "fmt" - "github.com/kercylan98/minotaur/utils/file" - "github.com/kercylan98/minotaur/utils/generic" - "github.com/kercylan98/minotaur/utils/storage" - "os" - "path/filepath" - "strings" - "time" -) - -const ( - // IndexDataFileDefaultSuffix 是 IndexDataFileStorage 的文件默认后缀 - IndexDataFileDefaultSuffix = "stock" - - indexNameFormat = "%s.%v.%s" -) - -// NewIndexDataFileStorage 创建索引数据文件存储器 -func NewIndexDataFileStorage[I generic.Ordered, T storage.IndexDataItem[I]](dir string, generate func(name string, index I) T, generateZero func(name string) T, options ...IndexDataFileStorageOption[I, T]) *IndexDataFileStorage[I, T] { - s := &IndexDataFileStorage[I, T]{ - dir: dir, - suffix: IndexDataFileDefaultSuffix, - generate: generate, - generateZero: generateZero, - encoder: FileStorageJSONEncoder[T](), - decoder: FileStorageJSONDecoder[T](), - } - for _, option := range options { - option(s) - } - return s -} - -// IndexDataFileStorage 索引数据文件存储器 -type IndexDataFileStorage[I generic.Ordered, T storage.IndexDataItem[I]] struct { - dir string - suffix string - generate func(name string, index I) T - generateZero func(name string) T - encoder FileStorageEncoder[T] - decoder FileStorageDecoder[T] -} - -func (slf *IndexDataFileStorage[I, T]) Load(name string, index I) T { - bytes, err := file.ReadOnce(filepath.Join(slf.dir, fmt.Sprintf(indexNameFormat, name, index, slf.suffix))) - if err != nil { - return slf.generate(name, index) - } - var data = slf.generate(name, index) - _ = slf.decoder(bytes, data) - return data -} - -func (slf *IndexDataFileStorage[I, T]) LoadAll(name string) map[I]T { - var result = make(map[I]T) - files, err := os.ReadDir(slf.dir) - if err != nil { - return result - } - for _, entry := range files { - if entry.IsDir() || !strings.HasPrefix(entry.Name(), name) || !strings.HasSuffix(entry.Name(), slf.suffix) { - continue - } - bytes, err := file.ReadOnce(filepath.Join(slf.dir, entry.Name())) - if err != nil { - continue - } - data := slf.generateZero(name) - if err := slf.decoder(bytes, data); err == nil { - result[data.GetIndex()] = data - } - } - return result -} - -func (slf *IndexDataFileStorage[I, T]) Save(name string, index I, data T) error { - bytes, err := slf.encoder(data) - if err != nil { - return err - } - return file.WriterFile(filepath.Join(slf.dir, fmt.Sprintf(indexNameFormat, name, index, slf.suffix)), bytes) -} - -func (slf *IndexDataFileStorage[I, T]) SaveAll(name string, data map[I]T, errHandle func(err error) bool, retryInterval time.Duration) { - for index, data := range data { - for { - if err := slf.Save(name, index, data); err != nil { - if !errHandle(err) { - time.Sleep(retryInterval) - continue - } - break - } - break - } - } -} - -func (slf *IndexDataFileStorage[I, T]) Delete(name string, index I) { - _ = os.Remove(filepath.Join(slf.dir, fmt.Sprintf(indexNameFormat, name, index, slf.suffix))) -} - -func (slf *IndexDataFileStorage[I, T]) DeleteAll(name string) { - files, err := os.ReadDir(slf.dir) - if err != nil { - return - } - for _, entry := range files { - if entry.IsDir() || !strings.HasPrefix(entry.Name(), name) || !strings.HasSuffix(entry.Name(), slf.suffix) { - continue - } - _ = os.Remove(filepath.Join(slf.dir, entry.Name())) - } -} diff --git a/utils/storage/storages/index_data_file_example_test.go b/utils/storage/storages/index_data_file_example_test.go deleted file mode 100644 index f6c865d..0000000 --- a/utils/storage/storages/index_data_file_example_test.go +++ /dev/null @@ -1,18 +0,0 @@ -package storages_test - -import ( - "fmt" - "github.com/kercylan98/minotaur/utils/storage/storages" -) - -func ExampleNewIndexDataFileStorage() { - storage := storages.NewIndexDataFileStorage[string, *IndexData[string]]("./example-data", func(name string, index string) *IndexData[string] { - return &IndexData[string]{ID: index} - }, func(name string) *IndexData[string] { - return new(IndexData[string]) - }) - - fmt.Println(storage != nil) - // Output: - // true -} diff --git a/utils/storage/storages/index_data_file_options.go b/utils/storage/storages/index_data_file_options.go deleted file mode 100644 index 9d8bec1..0000000 --- a/utils/storage/storages/index_data_file_options.go +++ /dev/null @@ -1,33 +0,0 @@ -package storages - -import ( - "github.com/kercylan98/minotaur/utils/generic" - "github.com/kercylan98/minotaur/utils/storage" -) - -// IndexDataFileStorageOption 索引数据文件存储器选项 -type IndexDataFileStorageOption[I generic.Ordered, T storage.IndexDataItem[I]] func(storage *IndexDataFileStorage[I, T]) - -// WithIndexDataFileStorageEncoder 设置编码器 -// - 默认为 FileStorageJSONEncoder 编码器 -func WithIndexDataFileStorageEncoder[I generic.Ordered, T storage.IndexDataItem[I]](encoder FileStorageEncoder[T]) IndexDataFileStorageOption[I, T] { - return func(storage *IndexDataFileStorage[I, T]) { - storage.encoder = encoder - } -} - -// WithIndexDataFileStorageDecoder 设置解码器 -// - 默认为 FileStorageJSONDecoder 解码器 -func WithIndexDataFileStorageDecoder[I generic.Ordered, T storage.IndexDataItem[I]](decoder FileStorageDecoder[T]) IndexDataFileStorageOption[I, T] { - return func(storage *IndexDataFileStorage[I, T]) { - storage.decoder = decoder - } -} - -// WithIndexDataFileStorageSuffix 设置文件后缀 -// - 默认为 IndexDataFileDefaultSuffix -func WithIndexDataFileStorageSuffix[I generic.Ordered, T storage.IndexDataItem[I]](suffix string) IndexDataFileStorageOption[I, T] { - return func(storage *IndexDataFileStorage[I, T]) { - storage.suffix = suffix - } -} diff --git a/utils/storage/storages/index_data_file_test.go b/utils/storage/storages/index_data_file_test.go deleted file mode 100644 index 5e12649..0000000 --- a/utils/storage/storages/index_data_file_test.go +++ /dev/null @@ -1,45 +0,0 @@ -package storages_test - -import ( - "github.com/kercylan98/minotaur/utils/storage" - "github.com/kercylan98/minotaur/utils/storage/storages" - . "github.com/smartystreets/goconvey/convey" - "testing" -) - -type IndexData[I string] struct { - ID I - Value int -} - -func (slf *IndexData[I]) GetIndex() I { - return slf.ID -} - -func TestIndexDataFileStorage_Save(t *testing.T) { - Convey("TestIndexDataFileStorage_Save", t, func() { - data := storage.NewIndexData[string, *IndexData[string]]("index_data_file_test", storages.NewIndexDataFileStorage[string, *IndexData[string]]("./example-data", func(name string, index string) *IndexData[string] { - return &IndexData[string]{ID: index} - }, func(name string) *IndexData[string] { - return new(IndexData[string]) - })) - data.Handle("INDEX_001", func(name string, index string, data *IndexData[string]) { - data.Value = 10 - }) - if err := data.SaveData("INDEX_001"); err != nil { - t.Fatal(err) - } - So(data.GetData("INDEX_001").Value, ShouldEqual, 10) - }) -} - -func TestIndexDataFileStorage_Load(t *testing.T) { - Convey("TestIndexDataFileStorage_Load", t, func() { - data := storage.NewIndexData[string, *IndexData[string]]("index_data_file_test", storages.NewIndexDataFileStorage[string, *IndexData[string]]("./example-data", func(name string, index string) *IndexData[string] { - return &IndexData[string]{ID: index} - }, func(name string) *IndexData[string] { - return new(IndexData[string]) - })) - So(data.GetData("INDEX_001").Value, ShouldEqual, 10) - }) -} diff --git a/utils/stream/map.go b/utils/stream/map.go index 1495c98..a770933 100644 --- a/utils/stream/map.go +++ b/utils/stream/map.go @@ -1,9 +1,8 @@ package stream import ( - "github.com/kercylan98/minotaur/utils/asynchronous" + "github.com/kercylan98/minotaur/utils/concurrent" "github.com/kercylan98/minotaur/utils/hash" - "github.com/kercylan98/minotaur/utils/synchronization" "reflect" ) @@ -19,11 +18,6 @@ func WithMapCopy[K comparable, V any](m map[K]V) Map[K, V] { return hash.Copy(m) } -// WithHashMap 使用传入的 map 执行链式操作 -func WithHashMap[K comparable, V any](m hash.Map[K, V]) Map[K, V] { - return m.Map() -} - // Map 提供了 map 的链式操作 type Map[K comparable, V any] map[K]V @@ -180,14 +174,9 @@ func (slf Map[K, V]) ToSliceStreamWithKey() Slice[K] { return hash.KeyToSlice(slf) } -// ToSyncMap 将当前 Map 转换为 synchronization.Map -func (slf Map[K, V]) ToSyncMap() *synchronization.Map[K, V] { - return synchronization.NewMap[K, V](synchronization.WithMapSource(slf)) -} - -// ToAsyncMap 将当前 Map 转换为 asynchronous.Map -func (slf Map[K, V]) ToAsyncMap() *asynchronous.Map[K, V] { - return asynchronous.NewMap[K, V](asynchronous.WithMapSource(slf)) +// ToSyncMap 将当前 Map 转换为 concurrent.BalanceMap +func (slf Map[K, V]) ToSyncMap() *concurrent.BalanceMap[K, V] { + return concurrent.NewBalanceMap[K, V](concurrent.WithBalanceMapSource(slf)) } // ToMap 将当前 Map 转换为 map diff --git a/utils/super/error.go b/utils/super/error.go index e89d276..226b84c 100644 --- a/utils/super/error.go +++ b/utils/super/error.go @@ -5,6 +5,7 @@ import ( ) var errorMapper = make(map[error]int) +var errorMapperRef = make(map[error]error) // RegError 通过错误码注册错误,返回错误的引用 func RegError(code int, message string) error { @@ -16,7 +17,21 @@ func RegError(code int, message string) error { return err } -// GetErrorCode 通过错误引用获取错误码,如果错误不存在则返回 0 -func GetErrorCode(err error) int { - return errorMapper[err] +// RegErrorRef 通过错误码注册错误,返回错误的引用 +func RegErrorRef(code int, message string, ref error) error { + if code == 0 { + panic("error code can not be 0") + } + err := errors.New(message) + errorMapper[err] = code + errorMapperRef[ref] = err + return ref +} + +// GetErrorCode 通过错误引用获取错误码,如果错误不存在则返回 0 +func GetErrorCode(err error) (int, error) { + if ref, exist := errorMapperRef[err]; exist { + err = ref + } + return errorMapper[err], err } diff --git a/utils/super/unsafe.go b/utils/super/unsafe.go new file mode 100644 index 0000000..3383101 --- /dev/null +++ b/utils/super/unsafe.go @@ -0,0 +1,11 @@ +package super + +import "unsafe" + +func StringToBytes(s string) []byte { + return unsafe.Slice(unsafe.StringData(s), len(s)) +} + +func BytesToString(b []byte) string { + return unsafe.String(&b[0], len(b)) +} diff --git a/utils/synchronization/map.go b/utils/synchronization/map.go deleted file mode 100644 index f1b0974..0000000 --- a/utils/synchronization/map.go +++ /dev/null @@ -1,285 +0,0 @@ -package synchronization - -import ( - "encoding/json" - "github.com/kercylan98/minotaur/utils/hash" - "sync" -) - -func NewMap[Key comparable, value any](options ...MapOption[Key, value]) *Map[Key, value] { - m := &Map[Key, value]{ - data: make(map[Key]value), - } - for _, option := range options { - option(m) - } - return m -} - -// Map 并发安全的字典数据结构 -type Map[Key comparable, Value any] struct { - lock sync.RWMutex - data map[Key]Value - atom bool -} - -func (slf *Map[Key, Value]) Set(key Key, value Value) { - if !slf.atom { - slf.lock.Lock() - defer slf.lock.Unlock() - } - slf.data[key] = value -} - -func (slf *Map[Key, Value]) Get(key Key) Value { - if !slf.atom { - slf.lock.RLock() - defer slf.lock.RUnlock() - } - return slf.data[key] -} - -// AtomGetSet 原子方式获取一个值并在之后进行赋值 -func (slf *Map[Key, Value]) AtomGetSet(key Key, handle func(value Value, exist bool) (newValue Value, isSet bool)) { - if !slf.atom { - slf.lock.Lock() - defer slf.lock.Unlock() - } - value, exist := slf.data[key] - if newValue, isSet := handle(value, exist); isSet { - slf.data[key] = newValue - } -} - -// Atom 原子操作 -func (slf *Map[Key, Value]) Atom(handle func(m hash.Map[Key, Value])) { - slf.lock.Lock() - slf.atom = true - handle(slf) - slf.atom = false - slf.lock.Unlock() -} - -func (slf *Map[Key, Value]) Exist(key Key) bool { - if !slf.atom { - slf.lock.RLock() - defer slf.lock.RUnlock() - } - _, exist := slf.data[key] - return exist -} - -func (slf *Map[Key, Value]) GetExist(key Key) (Value, bool) { - if !slf.atom { - slf.lock.RLock() - defer slf.lock.RUnlock() - } - value, exist := slf.data[key] - return value, exist -} - -func (slf *Map[Key, Value]) Delete(key Key) { - if !slf.atom { - slf.lock.Lock() - defer slf.lock.Unlock() - } - delete(slf.data, key) -} - -func (slf *Map[Key, Value]) DeleteGet(key Key) Value { - if !slf.atom { - slf.lock.Lock() - defer slf.lock.Unlock() - } - v := slf.data[key] - delete(slf.data, key) - return v -} - -func (slf *Map[Key, Value]) DeleteGetExist(key Key) (Value, bool) { - if !slf.atom { - slf.lock.Lock() - defer slf.lock.Unlock() - } - v, exist := slf.data[key] - delete(slf.data, key) - return v, exist -} - -func (slf *Map[Key, Value]) DeleteExist(key Key) bool { - if !slf.atom { - slf.lock.Lock() - defer slf.lock.Unlock() - } - if _, exist := slf.data[key]; !exist { - return exist - } - delete(slf.data, key) - return true -} - -func (slf *Map[Key, Value]) Clear() { - if !slf.atom { - slf.lock.Lock() - defer slf.lock.Unlock() - } - for k := range slf.data { - delete(slf.data, k) - } -} - -func (slf *Map[Key, Value]) ClearHandle(handle func(key Key, value Value)) { - if !slf.atom { - slf.lock.Lock() - defer slf.lock.Unlock() - } - for k, v := range slf.data { - handle(k, v) - delete(slf.data, k) - } -} - -func (slf *Map[Key, Value]) Range(handle func(key Key, value Value)) { - if !slf.atom { - slf.lock.RLock() - defer slf.lock.RUnlock() - } - for k, v := range slf.data { - key, value := k, v - handle(key, value) - } -} - -func (slf *Map[Key, Value]) RangeSkip(handle func(key Key, value Value) bool) { - if !slf.atom { - slf.lock.RLock() - defer slf.lock.RUnlock() - } - for k, v := range slf.data { - key, value := k, v - if !handle(key, value) { - continue - } - } -} - -func (slf *Map[Key, Value]) RangeBreakout(handle func(key Key, value Value) bool) { - slf.rangeBreakout(handle) -} - -func (slf *Map[Key, Value]) rangeBreakout(handle func(key Key, value Value) bool) bool { - if !slf.atom { - slf.lock.RLock() - defer slf.lock.RUnlock() - } - for k, v := range slf.data { - key, value := k, v - if !handle(key, value) { - return true - } - } - return false -} - -func (slf *Map[Key, Value]) RangeFree(handle func(key Key, value Value, skip func(), breakout func())) { - slf.rangeFree(handle) -} - -func (slf *Map[Key, Value]) rangeFree(handle func(key Key, value Value, skip func(), breakout func())) bool { - var skipExec, breakoutExec bool - var skip = func() { - skipExec = true - } - var breakout = func() { - breakoutExec = true - } - if !slf.atom { - slf.lock.RLock() - defer slf.lock.RUnlock() - } - for k, v := range slf.data { - key, value := k, v - handle(key, value, skip, breakout) - if skipExec { - continue - } - if breakoutExec { - break - } - } - return breakoutExec -} - -func (slf *Map[Key, Value]) Keys() []Key { - if !slf.atom { - slf.lock.RLock() - defer slf.lock.RUnlock() - } - var s = make([]Key, 0, len(slf.data)) - for k := range slf.data { - s = append(s, k) - } - return s -} - -func (slf *Map[Key, Value]) Slice() []Value { - if !slf.atom { - slf.lock.RLock() - defer slf.lock.RUnlock() - } - var s = make([]Value, 0, len(slf.data)) - for _, v := range slf.data { - s = append(s, v) - } - return s -} - -func (slf *Map[Key, Value]) Map() map[Key]Value { - var m = make(map[Key]Value) - if !slf.atom { - slf.lock.RLock() - defer slf.lock.RUnlock() - } - for k, v := range slf.data { - m[k] = v - } - return m -} - -func (slf *Map[Key, Value]) Size() int { - if !slf.atom { - slf.lock.RLock() - defer slf.lock.RUnlock() - } - return len(slf.data) -} - -// GetOne 获取一个 -func (slf *Map[Key, Value]) GetOne() (value Value) { - if !slf.atom { - slf.lock.RLock() - defer slf.lock.RUnlock() - } - for _, v := range slf.data { - return v - } - return value -} - -func (slf *Map[Key, Value]) MarshalJSON() ([]byte, error) { - m := slf.Map() - return json.Marshal(m) -} - -func (slf *Map[Key, Value]) UnmarshalJSON(bytes []byte) error { - var m = make(map[Key]Value) - if err := json.Unmarshal(bytes, &m); err != nil { - return err - } - if !slf.atom { - slf.lock.Lock() - defer slf.lock.Unlock() - } - slf.data = m - return nil -} diff --git a/utils/synchronization/map_options.go b/utils/synchronization/map_options.go deleted file mode 100644 index 0286200..0000000 --- a/utils/synchronization/map_options.go +++ /dev/null @@ -1,10 +0,0 @@ -package synchronization - -type MapOption[Key comparable, Value any] func(m *Map[Key, Value]) - -// WithMapSource 通过传入的 map 初始化 -func WithMapSource[Key comparable, Value any](source map[Key]Value) MapOption[Key, Value] { - return func(m *Map[Key, Value]) { - m.data = source - } -} \ No newline at end of file diff --git a/utils/synchronization/map_segment.go b/utils/synchronization/map_segment.go deleted file mode 100644 index 414e72e..0000000 --- a/utils/synchronization/map_segment.go +++ /dev/null @@ -1,259 +0,0 @@ -package synchronization - -import ( - "encoding/json" - "github.com/kercylan98/minotaur/utils/hash" - "sync" -) - -func NewMapSegment[Key comparable, value any](segmentCount int) *MapSegment[Key, value] { - ms := &MapSegment[Key, value]{ - segments: map[int]*Map[Key, value]{}, - cache: map[Key]int{}, - consistency: hash.NewConsistency(segmentCount), - } - for i := 0; i < segmentCount; i++ { - ms.consistency.AddNode(i) - ms.segments[i] = NewMap[Key, value]() - } - - return ms -} - -// MapSegment 基于分段锁实现的并发安全的字典数据结构map -type MapSegment[Key comparable, Value any] struct { - segments map[int]*Map[Key, Value] - cache map[Key]int - consistency *hash.Consistency - lock sync.RWMutex -} - -func (slf *MapSegment[Key, Value]) Atom(handle func(m hash.Map[Key, Value])) { - panic("this function is currently not supported") -} - -func (slf *MapSegment[Key, Value]) Set(key Key, value Value) { - slf.lock.RLock() - s, exist := slf.cache[key] - slf.lock.RUnlock() - if !exist { - s = slf.consistency.PickNode(key) - slf.lock.Lock() - slf.cache[key] = s - slf.lock.Unlock() - } - slf.segments[s].Set(key, value) -} - -func (slf *MapSegment[Key, Value]) Get(key Key) (value Value) { - slf.lock.RLock() - s, exist := slf.cache[key] - slf.lock.RUnlock() - if !exist { - return value - } - return slf.segments[s].Get(key) -} - -// AtomGetSet 原子方式获取一个值并在之后进行赋值 -func (slf *MapSegment[Key, Value]) AtomGetSet(key Key, handle func(value Value, exist bool) (newValue Value, isSet bool)) { - var value Value - slf.lock.RLock() - s, exist := slf.cache[key] - slf.lock.RUnlock() - if !exist { - if newValue, isSet := handle(value, exist); isSet { - slf.Set(key, newValue) - } - return - } - slf.segments[s].AtomGetSet(key, handle) -} - -func (slf *MapSegment[Key, Value]) Exist(key Key) bool { - slf.lock.RLock() - _, exist := slf.cache[key] - slf.lock.RUnlock() - return exist -} - -func (slf *MapSegment[Key, Value]) GetExist(key Key) (value Value, exist bool) { - slf.lock.RLock() - s, exist := slf.cache[key] - slf.lock.RUnlock() - if !exist { - return value, false - } - return slf.segments[s].GetExist(key) -} - -func (slf *MapSegment[Key, Value]) Delete(key Key) { - slf.lock.Lock() - s, exist := slf.cache[key] - delete(slf.cache, key) - slf.lock.Unlock() - if exist { - slf.segments[s].Delete(key) - } -} - -func (slf *MapSegment[Key, Value]) DeleteGet(key Key) (value Value) { - slf.lock.Lock() - s, exist := slf.cache[key] - delete(slf.cache, key) - slf.lock.Unlock() - if exist { - return slf.segments[s].DeleteGet(key) - } - return -} - -func (slf *MapSegment[Key, Value]) DeleteGetExist(key Key) (value Value, exist bool) { - slf.lock.Lock() - s, exist := slf.cache[key] - delete(slf.cache, key) - slf.lock.Unlock() - if exist { - return slf.segments[s].DeleteGetExist(key) - } - return value, exist -} - -func (slf *MapSegment[Key, Value]) DeleteExist(key Key) bool { - slf.lock.Lock() - s, exist := slf.cache[key] - delete(slf.cache, key) - slf.lock.Unlock() - if exist { - return slf.segments[s].DeleteExist(key) - } - return false -} - -func (slf *MapSegment[Key, Value]) Clear() { - slf.lock.Lock() - for k := range slf.cache { - delete(slf.cache, k) - } - for _, m := range slf.segments { - m.Clear() - } - slf.lock.Unlock() -} - -func (slf *MapSegment[Key, Value]) ClearHandle(handle func(key Key, value Value)) { - slf.lock.Lock() - for k := range slf.cache { - delete(slf.cache, k) - } - for _, m := range slf.segments { - m.ClearHandle(handle) - } - slf.lock.Unlock() -} - -func (slf *MapSegment[Key, Value]) Range(handle func(key Key, value Value)) { - for _, m := range slf.segments { - m.Range(handle) - } -} - -func (slf *MapSegment[Key, Value]) RangeSkip(handle func(key Key, value Value) bool) { - for _, m := range slf.segments { - m.RangeSkip(handle) - } -} - -func (slf *MapSegment[Key, Value]) RangeBreakout(handle func(key Key, value Value) bool) { - for _, m := range slf.segments { - if m.rangeBreakout(handle) { - break - } - } -} - -func (slf *MapSegment[Key, Value]) RangeFree(handle func(key Key, value Value, skip func(), breakout func())) { - for _, m := range slf.segments { - if m.rangeFree(handle) { - break - } - } -} - -func (slf *MapSegment[Key, Value]) Keys() []Key { - var s = make([]Key, 0, len(slf.cache)) - slf.lock.RLock() - for k := range slf.cache { - s = append(s, k) - } - defer slf.lock.RUnlock() - return s -} - -func (slf *MapSegment[Key, Value]) Slice() []Value { - slf.lock.RLock() - var s = make([]Value, 0, len(slf.cache)) - slf.lock.RUnlock() - for _, m := range slf.segments { - s = append(s, m.Slice()...) - } - return s -} - -func (slf *MapSegment[Key, Value]) Map() map[Key]Value { - slf.lock.RLock() - var s = map[Key]Value{} - slf.lock.RUnlock() - for _, m := range slf.segments { - for k, v := range m.Map() { - s[k] = v - } - } - return s -} - -func (slf *MapSegment[Key, Value]) Size() int { - slf.lock.RLock() - defer slf.lock.RUnlock() - return len(slf.cache) -} - -// GetOne 获取一个 -func (slf *MapSegment[Key, Value]) GetOne() (value Value) { - for k, s := range slf.cache { - return slf.segments[s].Get(k) - } - return value -} - -func (slf *MapSegment[Key, Value]) MarshalJSON() ([]byte, error) { - var ms struct { - Segments map[int]*Map[Key, Value] - Cache map[Key]int - SegmentCount int - } - ms.Segments = slf.segments - ms.Cache = slf.cache - ms.SegmentCount = len(slf.segments) - return json.Marshal(ms) -} - -func (slf *MapSegment[Key, Value]) UnmarshalJSON(bytes []byte) error { - var ms struct { - Segments map[int]*Map[Key, Value] - Cache map[Key]int - SegmentCount int - } - if err := json.Unmarshal(bytes, &ms); err != nil { - return err - } - slf.lock.Lock() - slf.consistency = hash.NewConsistency(ms.SegmentCount) - for i := 0; i < ms.SegmentCount; i++ { - slf.consistency.AddNode(i) - } - slf.segments = ms.Segments - slf.cache = ms.Cache - slf.lock.Unlock() - return nil -}