Merge branch 'develop'
This commit is contained in:
commit
c5b0fbe2c2
|
@ -13,12 +13,12 @@ mindmap
|
||||||
root((Minotaur))
|
root((Minotaur))
|
||||||
/component 通用组件接口定义
|
/component 通用组件接口定义
|
||||||
/components 通用组件内置实现
|
/components 通用组件内置实现
|
||||||
/config 针对配置导表的配置加载
|
/configuration 配置管理功能
|
||||||
/game 游戏通用功能接口定义
|
/game 游戏通用功能接口定义
|
||||||
/builtin 游戏通用功能内置实现
|
/builtin 游戏通用功能内置实现
|
||||||
/notify 通知功能接口定义
|
/notify 通知功能接口定义
|
||||||
/planner 策划相关工具目录
|
/planner 策划相关工具目录
|
||||||
/configexport 配置导表功能实现
|
/pce 配置导表功能实现
|
||||||
/report 数据埋点及上报功能
|
/report 数据埋点及上报功能
|
||||||
/server 网络服务器支持
|
/server 网络服务器支持
|
||||||
/cross 内置跨服功能实现
|
/cross 内置跨服功能实现
|
||||||
|
@ -113,7 +113,6 @@ func main() {
|
||||||
```
|
```
|
||||||
其他的一些支持事件的结构体(非所有):
|
其他的一些支持事件的结构体(非所有):
|
||||||
- `game.Room` 游戏房间实现
|
- `game.Room` 游戏房间实现
|
||||||
- `synchronization.Map` 并发安全的`Map`实现
|
|
||||||
- ...
|
- ...
|
||||||
### 可选项
|
### 可选项
|
||||||
大部分的 `New` 函数均可使用可选项进行创建,具体函数前缀通常为 `With`。
|
大部分的 `New` 函数均可使用可选项进行创建,具体函数前缀通常为 `With`。
|
||||||
|
|
|
@ -3,7 +3,7 @@ package components
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"github.com/kercylan98/minotaur/component"
|
"github.com/kercylan98/minotaur/component"
|
||||||
"github.com/kercylan98/minotaur/utils/synchronization"
|
"github.com/kercylan98/minotaur/utils/concurrent"
|
||||||
"github.com/kercylan98/minotaur/utils/timer"
|
"github.com/kercylan98/minotaur/utils/timer"
|
||||||
"sync"
|
"sync"
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
|
@ -13,8 +13,8 @@ import (
|
||||||
// NewLockstep 创建一个锁步(帧)同步默认实现的组件(Lockstep)进行返回
|
// NewLockstep 创建一个锁步(帧)同步默认实现的组件(Lockstep)进行返回
|
||||||
func NewLockstep[ClientID comparable, Command any](options ...LockstepOption[ClientID, Command]) *Lockstep[ClientID, Command] {
|
func NewLockstep[ClientID comparable, Command any](options ...LockstepOption[ClientID, Command]) *Lockstep[ClientID, Command] {
|
||||||
lockstep := &Lockstep[ClientID, Command]{
|
lockstep := &Lockstep[ClientID, Command]{
|
||||||
clients: synchronization.NewMap[ClientID, component.LockstepClient[ClientID]](),
|
clients: concurrent.NewBalanceMap[ClientID, component.LockstepClient[ClientID]](),
|
||||||
frames: synchronization.NewMap[int, []Command](),
|
frames: concurrent.NewBalanceMap[int, []Command](),
|
||||||
ticker: timer.GetTicker(10),
|
ticker: timer.GetTicker(10),
|
||||||
frameRate: 15,
|
frameRate: 15,
|
||||||
serialization: func(frame int, commands []Command) []byte {
|
serialization: func(frame int, commands []Command) []byte {
|
||||||
|
@ -25,7 +25,7 @@ func NewLockstep[ClientID comparable, Command any](options ...LockstepOption[Cli
|
||||||
data, _ := json.Marshal(frameStruct)
|
data, _ := json.Marshal(frameStruct)
|
||||||
return data
|
return data
|
||||||
},
|
},
|
||||||
clientCurrentFrame: synchronization.NewMap[ClientID, int](),
|
clientCurrentFrame: concurrent.NewBalanceMap[ClientID, int](),
|
||||||
}
|
}
|
||||||
for _, option := range options {
|
for _, option := range options {
|
||||||
option(lockstep)
|
option(lockstep)
|
||||||
|
@ -40,12 +40,12 @@ func NewLockstep[ClientID comparable, Command any](options ...LockstepOption[Cli
|
||||||
// - 从特定帧开始追帧
|
// - 从特定帧开始追帧
|
||||||
// - 兼容各种基于TCP/UDP/Unix的网络类型,可通过客户端实现其他网络类型同步
|
// - 兼容各种基于TCP/UDP/Unix的网络类型,可通过客户端实现其他网络类型同步
|
||||||
type Lockstep[ClientID comparable, Command any] struct {
|
type Lockstep[ClientID comparable, Command any] struct {
|
||||||
clients *synchronization.Map[ClientID, component.LockstepClient[ClientID]] // 接受广播的客户端
|
clients *concurrent.BalanceMap[ClientID, component.LockstepClient[ClientID]] // 接受广播的客户端
|
||||||
frames *synchronization.Map[int, []Command] // 所有帧指令
|
frames *concurrent.BalanceMap[int, []Command] // 所有帧指令
|
||||||
ticker *timer.Ticker // 定时器
|
ticker *timer.Ticker // 定时器
|
||||||
frameMutex sync.Mutex // 帧锁
|
frameMutex sync.Mutex // 帧锁
|
||||||
currentFrame int // 当前帧
|
currentFrame int // 当前帧
|
||||||
clientCurrentFrame *synchronization.Map[ClientID, int] // 客户端当前帧数
|
clientCurrentFrame *concurrent.BalanceMap[ClientID, int] // 客户端当前帧数
|
||||||
running atomic.Bool
|
running atomic.Bool
|
||||||
|
|
||||||
frameRate int // 帧率(每秒N帧)
|
frameRate int // 帧率(每秒N帧)
|
||||||
|
@ -123,8 +123,8 @@ func (slf *Lockstep[ClientID, Command]) StopBroadcast() {
|
||||||
|
|
||||||
// AddCommand 添加命令到当前帧
|
// AddCommand 添加命令到当前帧
|
||||||
func (slf *Lockstep[ClientID, Command]) AddCommand(command Command) {
|
func (slf *Lockstep[ClientID, Command]) AddCommand(command Command) {
|
||||||
slf.frames.AtomGetSet(slf.currentFrame, func(value []Command, exist bool) (newValue []Command, isSet bool) {
|
slf.frames.Atom(func(m map[int][]Command) {
|
||||||
return append(value, command), true
|
m[slf.currentFrame] = append(m[slf.currentFrame], command)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -2,18 +2,18 @@ package builtin
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/kercylan98/minotaur/game"
|
"github.com/kercylan98/minotaur/game"
|
||||||
|
"github.com/kercylan98/minotaur/utils/concurrent"
|
||||||
"github.com/kercylan98/minotaur/utils/huge"
|
"github.com/kercylan98/minotaur/utils/huge"
|
||||||
"github.com/kercylan98/minotaur/utils/synchronization"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func NewAttrs() *Attrs {
|
func NewAttrs() *Attrs {
|
||||||
return &Attrs{
|
return &Attrs{
|
||||||
attrs: synchronization.NewMap[int, any](),
|
attrs: concurrent.NewBalanceMap[int, any](),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type Attrs struct {
|
type Attrs struct {
|
||||||
attrs *synchronization.Map[int, any]
|
attrs *concurrent.BalanceMap[int, any]
|
||||||
|
|
||||||
attrChangeEventHandles []game.AttrChangeEventHandle
|
attrChangeEventHandles []game.AttrChangeEventHandle
|
||||||
attrIdChangeEventHandles map[int][]game.AttrChangeEventHandle
|
attrIdChangeEventHandles map[int][]game.AttrChangeEventHandle
|
||||||
|
|
|
@ -3,15 +3,15 @@ package builtin
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"github.com/kercylan98/minotaur/game"
|
"github.com/kercylan98/minotaur/game"
|
||||||
|
"github.com/kercylan98/minotaur/utils/concurrent"
|
||||||
"github.com/kercylan98/minotaur/utils/generic"
|
"github.com/kercylan98/minotaur/utils/generic"
|
||||||
"github.com/kercylan98/minotaur/utils/synchronization"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// NewRankingList 创建一个排名从0开始的排行榜
|
// NewRankingList 创建一个排名从0开始的排行榜
|
||||||
func NewRankingList[CompetitorID comparable, Score generic.Ordered](options ...RankingListOption[CompetitorID, Score]) *RankingList[CompetitorID, Score] {
|
func NewRankingList[CompetitorID comparable, Score generic.Ordered](options ...RankingListOption[CompetitorID, Score]) *RankingList[CompetitorID, Score] {
|
||||||
rankingList := &RankingList[CompetitorID, Score]{
|
rankingList := &RankingList[CompetitorID, Score]{
|
||||||
rankCount: 100,
|
rankCount: 100,
|
||||||
competitors: synchronization.NewMap[CompetitorID, Score](),
|
competitors: concurrent.NewBalanceMap[CompetitorID, Score](),
|
||||||
}
|
}
|
||||||
for _, option := range options {
|
for _, option := range options {
|
||||||
option(rankingList)
|
option(rankingList)
|
||||||
|
@ -22,7 +22,7 @@ func NewRankingList[CompetitorID comparable, Score generic.Ordered](options ...R
|
||||||
type RankingList[CompetitorID comparable, Score generic.Ordered] struct {
|
type RankingList[CompetitorID comparable, Score generic.Ordered] struct {
|
||||||
asc bool
|
asc bool
|
||||||
rankCount int
|
rankCount int
|
||||||
competitors *synchronization.Map[CompetitorID, Score]
|
competitors *concurrent.BalanceMap[CompetitorID, Score]
|
||||||
scores []*scoreItem[CompetitorID, Score] // CompetitorID, Score
|
scores []*scoreItem[CompetitorID, Score] // CompetitorID, Score
|
||||||
|
|
||||||
rankChangeEventHandles []game.RankChangeEventHandle[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 {
|
func (slf *RankingList[CompetitorID, Score]) UnmarshalJSON(bytes []byte) error {
|
||||||
var t struct {
|
var t struct {
|
||||||
Competitors *synchronization.Map[CompetitorID, Score] `json:"competitors,omitempty"`
|
Competitors *concurrent.BalanceMap[CompetitorID, Score] `json:"competitors,omitempty"`
|
||||||
Scores []*scoreItem[CompetitorID, Score] `json:"scores,omitempty"`
|
Scores []*scoreItem[CompetitorID, Score] `json:"scores,omitempty"`
|
||||||
Asc bool `json:"asc,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 {
|
if err := json.Unmarshal(bytes, &t); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -260,9 +260,9 @@ func (slf *RankingList[CompetitorID, Score]) UnmarshalJSON(bytes []byte) error {
|
||||||
|
|
||||||
func (slf *RankingList[CompetitorID, Score]) MarshalJSON() ([]byte, error) {
|
func (slf *RankingList[CompetitorID, Score]) MarshalJSON() ([]byte, error) {
|
||||||
var t struct {
|
var t struct {
|
||||||
Competitors *synchronization.Map[CompetitorID, Score] `json:"competitors,omitempty"`
|
Competitors *concurrent.BalanceMap[CompetitorID, Score] `json:"competitors,omitempty"`
|
||||||
Scores []*scoreItem[CompetitorID, Score] `json:"scores,omitempty"`
|
Scores []*scoreItem[CompetitorID, Score] `json:"scores,omitempty"`
|
||||||
Asc bool `json:"asc,omitempty"`
|
Asc bool `json:"asc,omitempty"`
|
||||||
}
|
}
|
||||||
t.Competitors = slf.competitors
|
t.Competitors = slf.competitors
|
||||||
t.Scores = slf.scores
|
t.Scores = slf.scores
|
||||||
|
|
|
@ -2,8 +2,7 @@ package builtin
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/kercylan98/minotaur/game"
|
"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/log"
|
"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] {
|
func NewRoom[PlayerID comparable, Player game.Player[PlayerID]](guid int64, options ...RoomOption[PlayerID, Player]) *Room[PlayerID, Player] {
|
||||||
room := &Room[PlayerID, Player]{
|
room := &Room[PlayerID, Player]{
|
||||||
guid: guid,
|
guid: guid,
|
||||||
players: asynchronous.NewMap[PlayerID, Player](),
|
players: concurrent.NewBalanceMap[PlayerID, Player](),
|
||||||
}
|
}
|
||||||
for _, option := range options {
|
for _, option := range options {
|
||||||
option(room)
|
option(room)
|
||||||
|
@ -27,7 +26,7 @@ type Room[PlayerID comparable, Player game.Player[PlayerID]] struct {
|
||||||
owner PlayerID
|
owner PlayerID
|
||||||
noMaster bool
|
noMaster bool
|
||||||
playerLimit int
|
playerLimit int
|
||||||
players hash.Map[PlayerID, Player]
|
players *concurrent.BalanceMap[PlayerID, Player]
|
||||||
kickCheckHandle func(room *Room[PlayerID, Player], id, target PlayerID) error
|
kickCheckHandle func(room *Room[PlayerID, Player], id, target PlayerID) error
|
||||||
|
|
||||||
playerJoinRoomEventHandles []game.PlayerJoinRoomEventHandle[PlayerID, Player]
|
playerJoinRoomEventHandles []game.PlayerJoinRoomEventHandle[PlayerID, Player]
|
||||||
|
@ -51,8 +50,8 @@ func (slf *Room[PlayerID, Player]) GetPlayer(id PlayerID) Player {
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetPlayers 获取所有玩家
|
// GetPlayers 获取所有玩家
|
||||||
func (slf *Room[PlayerID, Player]) GetPlayers() hash.MapReadonly[PlayerID, Player] {
|
func (slf *Room[PlayerID, Player]) GetPlayers() map[PlayerID]Player {
|
||||||
return slf.players.(hash.MapReadonly[PlayerID, Player])
|
return slf.players.Map()
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetPlayerCount 获取玩家数量
|
// GetPlayerCount 获取玩家数量
|
||||||
|
|
|
@ -2,20 +2,20 @@ package builtin
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/kercylan98/minotaur/game"
|
"github.com/kercylan98/minotaur/game"
|
||||||
"github.com/kercylan98/minotaur/utils/synchronization"
|
"github.com/kercylan98/minotaur/utils/concurrent"
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
)
|
)
|
||||||
|
|
||||||
func NewRoomManager[PlayerID comparable, Room game.Room[PlayerID, game.Player[PlayerID]]]() *RoomManager[PlayerID, Room] {
|
func NewRoomManager[PlayerID comparable, Room game.Room[PlayerID, game.Player[PlayerID]]]() *RoomManager[PlayerID, Room] {
|
||||||
return &RoomManager[PlayerID, Room]{
|
return &RoomManager[PlayerID, Room]{
|
||||||
rooms: synchronization.NewMap[int64, Room](),
|
rooms: concurrent.NewBalanceMap[int64, Room](),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// RoomManager 房间管理器
|
// RoomManager 房间管理器
|
||||||
type RoomManager[PlayerID comparable, Room game.Room[PlayerID, game.Player[PlayerID]]] struct {
|
type RoomManager[PlayerID comparable, Room game.Room[PlayerID, game.Player[PlayerID]]] struct {
|
||||||
guid atomic.Int64
|
guid atomic.Int64
|
||||||
rooms *synchronization.Map[int64, Room]
|
rooms *concurrent.BalanceMap[int64, Room]
|
||||||
}
|
}
|
||||||
|
|
||||||
// GenGuid 生成一个新的房间guid
|
// GenGuid 生成一个新的房间guid
|
||||||
|
|
|
@ -2,7 +2,7 @@ package builtin
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/kercylan98/minotaur/game"
|
"github.com/kercylan98/minotaur/game"
|
||||||
"github.com/kercylan98/minotaur/utils/synchronization"
|
"github.com/kercylan98/minotaur/utils/concurrent"
|
||||||
)
|
)
|
||||||
|
|
||||||
// RoomOption 房间构建可选项
|
// RoomOption 房间构建可选项
|
||||||
|
@ -11,7 +11,7 @@ type RoomOption[PlayerID comparable, Player game.Player[PlayerID]] func(room *Ro
|
||||||
// WithRoomSync 通过线程安全的方式创建房间
|
// WithRoomSync 通过线程安全的方式创建房间
|
||||||
func WithRoomSync[PlayerID comparable, Player game.Player[PlayerID]]() RoomOption[PlayerID, Player] {
|
func WithRoomSync[PlayerID comparable, Player game.Player[PlayerID]]() RoomOption[PlayerID, Player] {
|
||||||
return func(room *Room[PlayerID, Player]) {
|
return func(room *Room[PlayerID, Player]) {
|
||||||
room.players = synchronization.NewMap[PlayerID, Player]()
|
room.players = concurrent.NewBalanceMap[PlayerID, Player]()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -2,7 +2,7 @@ package builtin
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/kercylan98/minotaur/game"
|
"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/hash"
|
||||||
"github.com/kercylan98/minotaur/utils/slice"
|
"github.com/kercylan98/minotaur/utils/slice"
|
||||||
"sync"
|
"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] {
|
func NewRoomSeat[PlayerID comparable, Player game.Player[PlayerID]](room game.Room[PlayerID, Player], options ...RoomSeatOption[PlayerID, Player]) *RoomSeat[PlayerID, Player] {
|
||||||
roomSeat := &RoomSeat[PlayerID, Player]{
|
roomSeat := &RoomSeat[PlayerID, Player]{
|
||||||
Room: room,
|
Room: room,
|
||||||
seatPS: asynchronous.NewMap[PlayerID, int](),
|
seatPS: concurrent.NewBalanceMap[PlayerID, int](),
|
||||||
}
|
}
|
||||||
for _, option := range options {
|
for _, option := range options {
|
||||||
option(roomSeat)
|
option(roomSeat)
|
||||||
|
@ -26,7 +26,7 @@ type RoomSeat[PlayerID comparable, Player game.Player[PlayerID]] struct {
|
||||||
game.Room[PlayerID, Player]
|
game.Room[PlayerID, Player]
|
||||||
mutex sync.RWMutex
|
mutex sync.RWMutex
|
||||||
vacancy []int
|
vacancy []int
|
||||||
seatPS hash.Map[PlayerID, int]
|
seatPS *concurrent.BalanceMap[PlayerID, int]
|
||||||
seatSP []*PlayerID
|
seatSP []*PlayerID
|
||||||
duplicateLock bool
|
duplicateLock bool
|
||||||
fillIn bool
|
fillIn bool
|
||||||
|
@ -240,6 +240,41 @@ func (slf *RoomSeat[PlayerID, Player]) GetNextSeatVacancy(seat int) int {
|
||||||
return seat
|
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) {
|
func (slf *RoomSeat[PlayerID, Player]) onJoinRoom(room game.Room[PlayerID, Player], player Player) {
|
||||||
slf.AddSeat(player.GetID())
|
slf.AddSeat(player.GetID())
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,9 +2,8 @@ package builtin
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/kercylan98/minotaur/game"
|
"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/log"
|
||||||
"github.com/kercylan98/minotaur/utils/synchronization"
|
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -12,10 +11,10 @@ import (
|
||||||
func NewWorld[PlayerID comparable, Player game.Player[PlayerID]](guid int64, options ...WorldOption[PlayerID, Player]) *World[PlayerID, Player] {
|
func NewWorld[PlayerID comparable, Player game.Player[PlayerID]](guid int64, options ...WorldOption[PlayerID, Player]) *World[PlayerID, Player] {
|
||||||
world := &World[PlayerID, Player]{
|
world := &World[PlayerID, Player]{
|
||||||
guid: guid,
|
guid: guid,
|
||||||
players: synchronization.NewMap[PlayerID, Player](),
|
players: concurrent.NewBalanceMap[PlayerID, Player](),
|
||||||
playerActors: synchronization.NewMap[PlayerID, hash.Map[int64, game.Actor]](),
|
playerActors: concurrent.NewBalanceMap[PlayerID, *concurrent.BalanceMap[int64, game.Actor]](),
|
||||||
owners: synchronization.NewMap[int64, PlayerID](),
|
owners: concurrent.NewBalanceMap[int64, PlayerID](),
|
||||||
actors: synchronization.NewMap[int64, game.Actor](),
|
actors: concurrent.NewBalanceMap[int64, game.Actor](),
|
||||||
}
|
}
|
||||||
for _, option := range options {
|
for _, option := range options {
|
||||||
option(world)
|
option(world)
|
||||||
|
@ -28,10 +27,10 @@ type World[PlayerID comparable, Player game.Player[PlayerID]] struct {
|
||||||
guid int64
|
guid int64
|
||||||
actorGuid atomic.Int64
|
actorGuid atomic.Int64
|
||||||
playerLimit int
|
playerLimit int
|
||||||
players hash.Map[PlayerID, Player]
|
players *concurrent.BalanceMap[PlayerID, Player]
|
||||||
playerActors hash.Map[PlayerID, hash.Map[int64, game.Actor]]
|
playerActors *concurrent.BalanceMap[PlayerID, *concurrent.BalanceMap[int64, game.Actor]]
|
||||||
owners hash.Map[int64, PlayerID]
|
owners *concurrent.BalanceMap[int64, PlayerID]
|
||||||
actors hash.Map[int64, game.Actor]
|
actors *concurrent.BalanceMap[int64, game.Actor]
|
||||||
|
|
||||||
playerJoinWorldEventHandles []game.PlayerJoinWorldEventHandle[PlayerID, Player]
|
playerJoinWorldEventHandles []game.PlayerJoinWorldEventHandle[PlayerID, Player]
|
||||||
playerLeaveWorldEventHandles []game.PlayerLeaveWorldEventHandle[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)
|
return slf.players.Get(id)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (slf *World[PlayerID, Player]) GetPlayers() hash.MapReadonly[PlayerID, Player] {
|
func (slf *World[PlayerID, Player]) GetPlayers() map[PlayerID]Player {
|
||||||
return slf.players.(hash.MapReadonly[PlayerID, Player])
|
return slf.players.Map()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (slf *World[PlayerID, Player]) GetActor(guid int64) game.Actor {
|
func (slf *World[PlayerID, Player]) GetActor(guid int64) game.Actor {
|
||||||
return slf.actors.Get(guid)
|
return slf.actors.Get(guid)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (slf *World[PlayerID, Player]) GetActors() hash.MapReadonly[int64, game.Actor] {
|
func (slf *World[PlayerID, Player]) GetActors() map[int64]game.Actor {
|
||||||
return slf.actors.(hash.MapReadonly[int64, game.Actor])
|
return slf.actors.Map()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (slf *World[PlayerID, Player]) GetPlayerActor(id PlayerID, guid int64) game.Actor {
|
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
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (slf *World[PlayerID, Player]) GetPlayerActors(id PlayerID) hash.MapReadonly[int64, game.Actor] {
|
func (slf *World[PlayerID, Player]) GetPlayerActors(id PlayerID) map[int64]game.Actor {
|
||||||
return slf.playerActors.Get(id).(hash.MapReadonly[int64, game.Actor])
|
return slf.playerActors.Get(id).Map()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (slf *World[PlayerID, Player]) IsExistPlayer(id PlayerID) bool {
|
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()))
|
log.Debug("World.Join", log.Int64("guid", slf.GetGuid()), log.Any("player", player.GetID()))
|
||||||
slf.players.Set(player.GetID(), player)
|
slf.players.Set(player.GetID(), player)
|
||||||
if actors := slf.playerActors.Get(player.GetID()); actors == nil {
|
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.playerActors.Set(player.GetID(), actors)
|
||||||
}
|
}
|
||||||
slf.OnPlayerJoinWorldEvent(player)
|
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()))
|
log.Debug("World.Leave", log.Int64("guid", slf.GetGuid()), log.Any("player", player.GetID()))
|
||||||
slf.OnPlayerLeaveWorldEvent(player)
|
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.OnActorAnnihilationEvent(actor)
|
||||||
slf.owners.Delete(guid)
|
slf.owners.Delete(guid)
|
||||||
|
return false
|
||||||
})
|
})
|
||||||
slf.playerActors.Delete(player.GetID())
|
slf.playerActors.Delete(player.GetID())
|
||||||
slf.players.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() {
|
func (slf *World[PlayerID, Player]) Reset() {
|
||||||
log.Debug("World", log.Int64("Reset", slf.guid))
|
log.Debug("World", log.Int64("Reset", slf.guid))
|
||||||
slf.players.Clear()
|
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()
|
actors.Clear()
|
||||||
|
return false
|
||||||
})
|
})
|
||||||
slf.playerActors.Clear()
|
slf.playerActors.Clear()
|
||||||
slf.owners.Clear()
|
slf.owners.Clear()
|
||||||
|
|
|
@ -1,159 +1,13 @@
|
||||||
package poker
|
package poker
|
||||||
|
|
||||||
import (
|
import "github.com/kercylan98/minotaur/utils/generic"
|
||||||
"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
|
|
||||||
}
|
|
||||||
|
|
||||||
// Card 扑克牌
|
// Card 扑克牌
|
||||||
type Card struct {
|
type Card[P, C generic.Number] interface {
|
||||||
point Point
|
// GetGuid 获取扑克牌的唯一标识
|
||||||
color Color
|
GetGuid() int64
|
||||||
}
|
// GetPoint 获取扑克牌的点数
|
||||||
|
GetPoint() P
|
||||||
// GetPoint 返回扑克牌的点数
|
// GetColor 获取扑克牌的花色
|
||||||
func (slf Card) GetPoint() Point {
|
GetColor() C
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
|
||||||
}
|
|
|
@ -2,6 +2,7 @@ package poker
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"github.com/kercylan98/minotaur/utils/generic"
|
||||||
"github.com/kercylan98/minotaur/utils/hash"
|
"github.com/kercylan98/minotaur/utils/hash"
|
||||||
"github.com/kercylan98/minotaur/utils/random"
|
"github.com/kercylan98/minotaur/utils/random"
|
||||||
"github.com/kercylan98/minotaur/utils/slice"
|
"github.com/kercylan98/minotaur/utils/slice"
|
||||||
|
@ -10,12 +11,17 @@ import (
|
||||||
|
|
||||||
// NewCardPile 返回一个新的牌堆,其中 size 表示了该牌堆由多少副牌组成
|
// NewCardPile 返回一个新的牌堆,其中 size 表示了该牌堆由多少副牌组成
|
||||||
// - 在不洗牌的情况下,默认牌堆顶部到底部为从大到小排列
|
// - 在不洗牌的情况下,默认牌堆顶部到底部为从大到小排列
|
||||||
func NewCardPile(size int, options ...CardPileOption) *CardPile {
|
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{
|
pile := &CardPile[P, C, T]{
|
||||||
size: size,
|
size: size,
|
||||||
pile: make([]Card, 0, size*54),
|
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 {
|
sort.Slice(cards, func(i, j int) bool {
|
||||||
return random.Float64() >= 0.5
|
return random.Float64() >= 0.5
|
||||||
})
|
})
|
||||||
|
@ -29,28 +35,47 @@ func NewCardPile(size int, options ...CardPileOption) *CardPile {
|
||||||
}
|
}
|
||||||
|
|
||||||
// CardPile 扑克牌堆
|
// CardPile 扑克牌堆
|
||||||
type CardPile struct {
|
type CardPile[P, C generic.Number, T Card[P, C]] struct {
|
||||||
pile []Card
|
pile []T
|
||||||
size int
|
size int
|
||||||
shuffleHandle func(cards []Card) []Card
|
shuffleHandle func(cards []T) []T
|
||||||
excludeColor map[Color]struct{}
|
excludeColor map[C]struct{}
|
||||||
excludePoint map[Point]struct{}
|
excludePoint map[P]struct{}
|
||||||
excludeCard map[Point]map[Color]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 重置牌堆的扑克牌数量及顺序
|
// Reset 重置牌堆的扑克牌数量及顺序
|
||||||
func (slf *CardPile) Reset() {
|
func (slf *CardPile[P, C, T]) Reset() {
|
||||||
var cards = make([]Card, 0, 54)
|
var cards = make([]T, 0, 54*slf.size)
|
||||||
if !slf.IsExclude(PointRedJoker, ColorNone) {
|
for i := 0; i < slf.size; i++ {
|
||||||
cards = append(cards, NewCard(PointRedJoker, ColorNone))
|
for _, joker := range slf.jokers {
|
||||||
}
|
if !slf.IsExclude(joker, C(0)) {
|
||||||
if !slf.IsExclude(PointBlackJoker, ColorNone) {
|
slf.guid++
|
||||||
cards = append(cards, NewCard(PointBlackJoker, ColorNone))
|
card := slf.generateCard(slf.guid, joker, C(0))
|
||||||
}
|
slf.cards[slf.guid] = card
|
||||||
for point := PointK; point >= PointA; point-- {
|
cards = append(cards, card)
|
||||||
for color := ColorDiamond; color <= ColorSpade; color++ {
|
}
|
||||||
if !slf.IsExclude(point, color) {
|
}
|
||||||
cards = append(cards, NewCard(point, color))
|
|
||||||
|
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 检查特定点数和花色是否被排除在外
|
// IsExclude 检查特定点数和花色是否被排除在外
|
||||||
func (slf *CardPile) IsExclude(point Point, color Color) bool {
|
func (slf *CardPile[P, C, T]) IsExclude(point P, color C) bool {
|
||||||
if point == PointRedJoker || point == PointBlackJoker {
|
|
||||||
color = ColorNone
|
|
||||||
}
|
|
||||||
return hash.Exist(slf.excludePoint, point) || hash.Exist(slf.excludeColor, color) || hash.Exist(slf.excludeCard[point], color)
|
return hash.Exist(slf.excludePoint, point) || hash.Exist(slf.excludeColor, color) || hash.Exist(slf.excludeCard[point], color)
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsExcludeWithCard 检查特定扑克牌是否被排除在外
|
// IsExcludeWithCard 检查特定扑克牌是否被排除在外
|
||||||
func (slf *CardPile) IsExcludeWithCard(card Card) bool {
|
func (slf *CardPile[P, C, T]) IsExcludeWithCard(card T) bool {
|
||||||
point, color := card.GetPointAndColor()
|
point, color := GetPointAndColor[P, C, T](card)
|
||||||
return hash.Exist(slf.excludePoint, point) || hash.Exist(slf.excludeColor, color) || hash.Exist(slf.excludeCard[point], color)
|
return hash.Exist(slf.excludePoint, point) || hash.Exist(slf.excludeColor, color) || hash.Exist(slf.excludeCard[point], color)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Shuffle 洗牌
|
// Shuffle 洗牌
|
||||||
func (slf *CardPile) Shuffle() {
|
func (slf *CardPile[P, C, T]) Shuffle() {
|
||||||
before := slf.Count()
|
before := slf.Count()
|
||||||
cards := slf.shuffleHandle(slf.Cards())
|
cards := slf.shuffleHandle(slf.Cards())
|
||||||
if len(cards) != before {
|
if len(cards) != before {
|
||||||
|
@ -85,22 +107,22 @@ func (slf *CardPile) Shuffle() {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Cards 获取当前牌堆的所有扑克牌
|
// Cards 获取当前牌堆的所有扑克牌
|
||||||
func (slf *CardPile) Cards() []Card {
|
func (slf *CardPile[P, C, T]) Cards() []T {
|
||||||
return slf.pile
|
return slf.pile
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsFree 返回牌堆是否没有扑克牌了
|
// IsFree 返回牌堆是否没有扑克牌了
|
||||||
func (slf *CardPile) IsFree() bool {
|
func (slf *CardPile[P, C, T]) IsFree() bool {
|
||||||
return len(slf.pile) == 0
|
return len(slf.pile) == 0
|
||||||
}
|
}
|
||||||
|
|
||||||
// Count 获取牌堆剩余牌量
|
// Count 获取牌堆剩余牌量
|
||||||
func (slf *CardPile) Count() int {
|
func (slf *CardPile[P, C, T]) Count() int {
|
||||||
return len(slf.pile)
|
return len(slf.pile)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Pull 从牌堆特定位置抽出一张牌
|
// Pull 从牌堆特定位置抽出一张牌
|
||||||
func (slf *CardPile) Pull(index int) Card {
|
func (slf *CardPile[P, C, T]) Pull(index int) T {
|
||||||
if index >= slf.Count() || index < 0 {
|
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))
|
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 从牌堆顶部抽出一张牌
|
// PullTop 从牌堆顶部抽出一张牌
|
||||||
func (slf *CardPile) PullTop() Card {
|
func (slf *CardPile[P, C, T]) PullTop() T {
|
||||||
if slf.IsFree() {
|
if slf.IsFree() {
|
||||||
panic("empty poker cards pile")
|
panic("empty poker cards pile")
|
||||||
}
|
}
|
||||||
|
@ -120,7 +142,7 @@ func (slf *CardPile) PullTop() Card {
|
||||||
}
|
}
|
||||||
|
|
||||||
// PullBottom 从牌堆底部抽出一张牌
|
// PullBottom 从牌堆底部抽出一张牌
|
||||||
func (slf *CardPile) PullBottom() Card {
|
func (slf *CardPile[P, C, T]) PullBottom() T {
|
||||||
if slf.IsFree() {
|
if slf.IsFree() {
|
||||||
panic("empty poker cards pile")
|
panic("empty poker cards pile")
|
||||||
}
|
}
|
||||||
|
@ -131,17 +153,17 @@ func (slf *CardPile) PullBottom() Card {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Push 将扑克牌插入到牌堆特定位置
|
// 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)
|
slice.Insert(&slf.pile, index, card)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// PushTop 将扑克牌插入到牌堆顶部
|
// PushTop 将扑克牌插入到牌堆顶部
|
||||||
func (slf *CardPile) PushTop(card Card) {
|
func (slf *CardPile[P, C, T]) PushTop(card T) {
|
||||||
slf.pile = append([]Card{card}, slf.pile...)
|
slf.pile = append([]T{card}, slf.pile...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// PushBottom 将扑克牌插入到牌堆底部
|
// PushBottom 将扑克牌插入到牌堆底部
|
||||||
func (slf *CardPile) PushBottom(card Card) {
|
func (slf *CardPile[P, C, T]) PushBottom(card T) {
|
||||||
slf.pile = append(slf.pile, card)
|
slf.pile = append(slf.pile, card)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)]
|
|
||||||
}
|
|
|
@ -1,11 +1,13 @@
|
||||||
package poker
|
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 通过特定的洗牌算法创建牌堆
|
// WithCardPileShuffle 通过特定的洗牌算法创建牌堆
|
||||||
// - 需要保证洗牌后的牌堆剩余扑克数量与之前相同,否则将会引发 panic
|
// - 需要保证洗牌后的牌堆剩余扑克数量与之前相同,否则将会引发 panic
|
||||||
func WithCardPileShuffle(shuffleHandle func(pile []Card) []Card) CardPileOption {
|
func WithCardPileShuffle[P, C generic.Number, T Card[P, C]](shuffleHandle func(pile []T) []T) CardPileOption[P, C, T] {
|
||||||
return func(pile *CardPile) {
|
return func(pile *CardPile[P, C, T]) {
|
||||||
if shuffleHandle == nil {
|
if shuffleHandle == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -14,10 +16,10 @@ func WithCardPileShuffle(shuffleHandle func(pile []Card) []Card) CardPileOption
|
||||||
}
|
}
|
||||||
|
|
||||||
// WithCardPileExcludeColor 通过排除特定花色的方式创建牌堆
|
// WithCardPileExcludeColor 通过排除特定花色的方式创建牌堆
|
||||||
func WithCardPileExcludeColor(colors ...Color) CardPileOption {
|
func WithCardPileExcludeColor[P, C generic.Number, T Card[P, C]](colors ...C) CardPileOption[P, C, T] {
|
||||||
return func(pile *CardPile) {
|
return func(pile *CardPile[P, C, T]) {
|
||||||
if pile.excludeColor == nil {
|
if pile.excludeColor == nil {
|
||||||
pile.excludeColor = map[Color]struct{}{}
|
pile.excludeColor = map[C]struct{}{}
|
||||||
}
|
}
|
||||||
for _, color := range colors {
|
for _, color := range colors {
|
||||||
pile.excludeColor[color] = struct{}{}
|
pile.excludeColor[color] = struct{}{}
|
||||||
|
@ -26,10 +28,10 @@ func WithCardPileExcludeColor(colors ...Color) CardPileOption {
|
||||||
}
|
}
|
||||||
|
|
||||||
// WithCardPileExcludePoint 通过排除特定点数的方式创建牌堆
|
// WithCardPileExcludePoint 通过排除特定点数的方式创建牌堆
|
||||||
func WithCardPileExcludePoint(points ...Point) CardPileOption {
|
func WithCardPileExcludePoint[P, C generic.Number, T Card[P, C]](points ...P) CardPileOption[P, C, T] {
|
||||||
return func(pile *CardPile) {
|
return func(pile *CardPile[P, C, T]) {
|
||||||
if pile.excludePoint == nil {
|
if pile.excludePoint == nil {
|
||||||
pile.excludePoint = map[Point]struct{}{}
|
pile.excludePoint = map[P]struct{}{}
|
||||||
}
|
}
|
||||||
for _, point := range points {
|
for _, point := range points {
|
||||||
pile.excludePoint[point] = struct{}{}
|
pile.excludePoint[point] = struct{}{}
|
||||||
|
@ -38,22 +40,22 @@ func WithCardPileExcludePoint(points ...Point) CardPileOption {
|
||||||
}
|
}
|
||||||
|
|
||||||
// WithCardPileExcludeCard 通过排除特定扑克牌的方式创建牌堆
|
// WithCardPileExcludeCard 通过排除特定扑克牌的方式创建牌堆
|
||||||
func WithCardPileExcludeCard(cards ...Card) CardPileOption {
|
func WithCardPileExcludeCard[P, C generic.Number, T Card[P, C]](cards ...Card[P, C]) CardPileOption[P, C, T] {
|
||||||
return func(pile *CardPile) {
|
return func(pile *CardPile[P, C, T]) {
|
||||||
if pile.excludeCard == nil {
|
if pile.excludeCard == nil {
|
||||||
pile.excludeCard = map[Point]map[Color]struct{}{}
|
pile.excludeCard = map[P]map[C]struct{}{}
|
||||||
}
|
}
|
||||||
for _, card := range cards {
|
for _, card := range cards {
|
||||||
point := card.GetPoint()
|
point := card.GetPoint()
|
||||||
cs, exist := pile.excludeCard[point]
|
cs, exist := pile.excludeCard[point]
|
||||||
if !exist {
|
if !exist {
|
||||||
cs = map[Color]struct{}{}
|
cs = map[C]struct{}{}
|
||||||
pile.excludeCard[point] = cs
|
pile.excludeCard[point] = cs
|
||||||
}
|
}
|
||||||
if point == PointRedJoker || point == PointBlackJoker {
|
for _, joker := range pile.jokers {
|
||||||
cs[ColorNone] = struct{}{}
|
if point != joker {
|
||||||
} else {
|
cs[card.GetColor()] = struct{}{}
|
||||||
cs[card.GetColor()] = struct{}{}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
|
||||||
})
|
|
||||||
}
|
|
|
@ -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
|
|
||||||
}
|
|
|
@ -1,37 +1,39 @@
|
||||||
package poker
|
package poker
|
||||||
|
|
||||||
|
import "github.com/kercylan98/minotaur/utils/generic"
|
||||||
|
|
||||||
const (
|
const (
|
||||||
HandNone = "None" // 无牌型
|
HandNone = "None" // 无牌型
|
||||||
)
|
)
|
||||||
|
|
||||||
// HandHandle 扑克牌型验证函数
|
// 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 单牌
|
// HandSingle 单牌
|
||||||
func HandSingle() HandHandle {
|
func HandSingle[P, C generic.Number, T Card[P, C]]() HandHandle[P, C, T] {
|
||||||
return func(rule *Rule, cards []Card) bool {
|
return func(rule *Rule[P, C, T], cards []T) bool {
|
||||||
return len(cards) == 1
|
return len(cards) == 1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// HandPairs 对子
|
// HandPairs 对子
|
||||||
func HandPairs() HandHandle {
|
func HandPairs[P, C generic.Number, T Card[P, C]]() HandHandle[P, C, T] {
|
||||||
return func(rule *Rule, cards []Card) bool {
|
return func(rule *Rule[P, C, T], cards []T) bool {
|
||||||
return len(cards) == 2 && rule.IsPointContinuity(2, cards...)
|
return len(cards) == 2 && rule.IsPointContinuity(2, cards...)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// HandThreeOfKind 三张
|
// HandThreeOfKind 三张
|
||||||
func HandThreeOfKind() HandHandle {
|
func HandThreeOfKind[P, C generic.Number, T Card[P, C]]() HandHandle[P, C, T] {
|
||||||
return func(rule *Rule, cards []Card) bool {
|
return func(rule *Rule[P, C, T], cards []T) bool {
|
||||||
return len(cards) == 3 && rule.IsPointContinuity(3, cards...)
|
return len(cards) == 3 && rule.IsPointContinuity(3, cards...)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// HandThreeOfKindWithOne 三带一
|
// HandThreeOfKindWithOne 三带一
|
||||||
func HandThreeOfKindWithOne() HandHandle {
|
func HandThreeOfKindWithOne[P, C generic.Number, T Card[P, C]]() HandHandle[P, C, T] {
|
||||||
return func(rule *Rule, cards []Card) bool {
|
return func(rule *Rule[P, C, T], cards []T) bool {
|
||||||
group := GroupByPoint(cards...)
|
group := GroupByPoint[P, C, T](cards...)
|
||||||
if len(group) != 2 {
|
if len(group) != 2 {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
@ -49,9 +51,9 @@ func HandThreeOfKindWithOne() HandHandle {
|
||||||
}
|
}
|
||||||
|
|
||||||
// HandThreeOfKindWithTwo 三带二
|
// HandThreeOfKindWithTwo 三带二
|
||||||
func HandThreeOfKindWithTwo() HandHandle {
|
func HandThreeOfKindWithTwo[P, C generic.Number, T Card[P, C]]() HandHandle[P, C, T] {
|
||||||
return func(rule *Rule, cards []Card) bool {
|
return func(rule *Rule[P, C, T], cards []T) bool {
|
||||||
group := GroupByPoint(cards...)
|
group := GroupByPoint[P, C, T](cards...)
|
||||||
if len(group) != 2 {
|
if len(group) != 2 {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
@ -69,15 +71,15 @@ func HandThreeOfKindWithTwo() HandHandle {
|
||||||
}
|
}
|
||||||
|
|
||||||
// HandOrderSingle 顺子
|
// HandOrderSingle 顺子
|
||||||
func HandOrderSingle(count int) HandHandle {
|
func HandOrderSingle[P, C generic.Number, T Card[P, C]](count int) HandHandle[P, C, T] {
|
||||||
return func(rule *Rule, cards []Card) bool {
|
return func(rule *Rule[P, C, T], cards []T) bool {
|
||||||
return len(cards) >= count && rule.IsPointContinuity(1, cards...)
|
return len(cards) >= count && rule.IsPointContinuity(1, cards...)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// HandOrderPairs 对子顺子
|
// HandOrderPairs 对子顺子
|
||||||
func HandOrderPairs(count int) HandHandle {
|
func HandOrderPairs[P, C generic.Number, T Card[P, C]](count int) HandHandle[P, C, T] {
|
||||||
return func(rule *Rule, cards []Card) bool {
|
return func(rule *Rule[P, C, T], cards []T) bool {
|
||||||
if len(cards) < count*2 || len(cards)%2 != 0 {
|
if len(cards) < count*2 || len(cards)%2 != 0 {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
@ -86,8 +88,8 @@ func HandOrderPairs(count int) HandHandle {
|
||||||
}
|
}
|
||||||
|
|
||||||
// HandOrderSingleThree 三张顺子
|
// HandOrderSingleThree 三张顺子
|
||||||
func HandOrderSingleThree(count int) HandHandle {
|
func HandOrderSingleThree[P, C generic.Number, T Card[P, C]](count int) HandHandle[P, C, T] {
|
||||||
return func(rule *Rule, cards []Card) bool {
|
return func(rule *Rule[P, C, T], cards []T) bool {
|
||||||
if len(cards) < count*3 || len(cards)%3 != 0 {
|
if len(cards) < count*3 || len(cards)%3 != 0 {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
@ -96,8 +98,8 @@ func HandOrderSingleThree(count int) HandHandle {
|
||||||
}
|
}
|
||||||
|
|
||||||
// HandOrderSingleFour 四张顺子
|
// HandOrderSingleFour 四张顺子
|
||||||
func HandOrderSingleFour(count int) HandHandle {
|
func HandOrderSingleFour[P, C generic.Number, T Card[P, C]](count int) HandHandle[P, C, T] {
|
||||||
return func(rule *Rule, cards []Card) bool {
|
return func(rule *Rule[P, C, T], cards []T) bool {
|
||||||
if len(cards) < count*4 || len(cards)%4 != 0 {
|
if len(cards) < count*4 || len(cards)%4 != 0 {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
@ -106,10 +108,10 @@ func HandOrderSingleFour(count int) HandHandle {
|
||||||
}
|
}
|
||||||
|
|
||||||
// HandOrderThreeWithOne 三带一顺子
|
// HandOrderThreeWithOne 三带一顺子
|
||||||
func HandOrderThreeWithOne(count int) HandHandle {
|
func HandOrderThreeWithOne[P, C generic.Number, T Card[P, C]](count int) HandHandle[P, C, T] {
|
||||||
return func(rule *Rule, cards []Card) bool {
|
return func(rule *Rule[P, C, T], cards []T) bool {
|
||||||
group := GroupByPoint(cards...)
|
group := GroupByPoint[P, C, T](cards...)
|
||||||
var continuous []Card
|
var continuous []T
|
||||||
var other int
|
var other int
|
||||||
for _, cards := range group {
|
for _, cards := range group {
|
||||||
if len(cards) == 3 {
|
if len(cards) == 3 {
|
||||||
|
@ -126,10 +128,10 @@ func HandOrderThreeWithOne(count int) HandHandle {
|
||||||
}
|
}
|
||||||
|
|
||||||
// HandOrderThreeWithTwo 三带二顺子
|
// HandOrderThreeWithTwo 三带二顺子
|
||||||
func HandOrderThreeWithTwo(count int) HandHandle {
|
func HandOrderThreeWithTwo[P, C generic.Number, T Card[P, C]](count int) HandHandle[P, C, T] {
|
||||||
return func(rule *Rule, cards []Card) bool {
|
return func(rule *Rule[P, C, T], cards []T) bool {
|
||||||
group := GroupByPoint(cards...)
|
group := GroupByPoint[P, C, T](cards...)
|
||||||
var continuous []Card
|
var continuous []T
|
||||||
var other int
|
var other int
|
||||||
for _, cards := range group {
|
for _, cards := range group {
|
||||||
if len(cards) == 3 {
|
if len(cards) == 3 {
|
||||||
|
@ -148,10 +150,10 @@ func HandOrderThreeWithTwo(count int) HandHandle {
|
||||||
}
|
}
|
||||||
|
|
||||||
// HandOrderFourWithOne 四带一顺子
|
// HandOrderFourWithOne 四带一顺子
|
||||||
func HandOrderFourWithOne(count int) HandHandle {
|
func HandOrderFourWithOne[P, C generic.Number, T Card[P, C]](count int) HandHandle[P, C, T] {
|
||||||
return func(rule *Rule, cards []Card) bool {
|
return func(rule *Rule[P, C, T], cards []T) bool {
|
||||||
group := GroupByPoint(cards...)
|
group := GroupByPoint[P, C, T](cards...)
|
||||||
var continuous []Card
|
var continuous []T
|
||||||
var other int
|
var other int
|
||||||
for _, cards := range group {
|
for _, cards := range group {
|
||||||
if len(cards) == 4 {
|
if len(cards) == 4 {
|
||||||
|
@ -168,10 +170,10 @@ func HandOrderFourWithOne(count int) HandHandle {
|
||||||
}
|
}
|
||||||
|
|
||||||
// HandOrderFourWithTwo 四带二顺子
|
// HandOrderFourWithTwo 四带二顺子
|
||||||
func HandOrderFourWithTwo(count int) HandHandle {
|
func HandOrderFourWithTwo[P, C generic.Number, T Card[P, C]](count int) HandHandle[P, C, T] {
|
||||||
return func(rule *Rule, cards []Card) bool {
|
return func(rule *Rule[P, C, T], cards []T) bool {
|
||||||
group := GroupByPoint(cards...)
|
group := GroupByPoint[P, C, T](cards...)
|
||||||
var continuous []Card
|
var continuous []T
|
||||||
var other int
|
var other int
|
||||||
for _, cards := range group {
|
for _, cards := range group {
|
||||||
if len(cards) == 4 {
|
if len(cards) == 4 {
|
||||||
|
@ -190,10 +192,10 @@ func HandOrderFourWithTwo(count int) HandHandle {
|
||||||
}
|
}
|
||||||
|
|
||||||
// HandOrderFourWithThree 四带三顺子
|
// HandOrderFourWithThree 四带三顺子
|
||||||
func HandOrderFourWithThree(count int) HandHandle {
|
func HandOrderFourWithThree[P, C generic.Number, T Card[P, C]](count int) HandHandle[P, C, T] {
|
||||||
return func(rule *Rule, cards []Card) bool {
|
return func(rule *Rule[P, C, T], cards []T) bool {
|
||||||
group := GroupByPoint(cards...)
|
group := GroupByPoint[P, C, T](cards...)
|
||||||
var continuous []Card
|
var continuous []T
|
||||||
var other int
|
var other int
|
||||||
for _, cards := range group {
|
for _, cards := range group {
|
||||||
if len(cards) == 4 {
|
if len(cards) == 4 {
|
||||||
|
@ -212,9 +214,9 @@ func HandOrderFourWithThree(count int) HandHandle {
|
||||||
}
|
}
|
||||||
|
|
||||||
// HandFourWithOne 四带一
|
// HandFourWithOne 四带一
|
||||||
func HandFourWithOne() HandHandle {
|
func HandFourWithOne[P, C generic.Number, T Card[P, C]]() HandHandle[P, C, T] {
|
||||||
return func(rule *Rule, cards []Card) bool {
|
return func(rule *Rule[P, C, T], cards []T) bool {
|
||||||
group := GroupByPoint(cards...)
|
group := GroupByPoint[P, C, T](cards...)
|
||||||
var hasFour bool
|
var hasFour bool
|
||||||
var count int
|
var count int
|
||||||
for _, cards := range group {
|
for _, cards := range group {
|
||||||
|
@ -229,9 +231,9 @@ func HandFourWithOne() HandHandle {
|
||||||
}
|
}
|
||||||
|
|
||||||
// HandFourWithTwo 四带二
|
// HandFourWithTwo 四带二
|
||||||
func HandFourWithTwo() HandHandle {
|
func HandFourWithTwo[P, C generic.Number, T Card[P, C]]() HandHandle[P, C, T] {
|
||||||
return func(rule *Rule, cards []Card) bool {
|
return func(rule *Rule[P, C, T], cards []T) bool {
|
||||||
group := GroupByPoint(cards...)
|
group := GroupByPoint[P, C, T](cards...)
|
||||||
var hasFour bool
|
var hasFour bool
|
||||||
var count int
|
var count int
|
||||||
for _, cards := range group {
|
for _, cards := range group {
|
||||||
|
@ -246,9 +248,9 @@ func HandFourWithTwo() HandHandle {
|
||||||
}
|
}
|
||||||
|
|
||||||
// HandFourWithThree 四带三
|
// HandFourWithThree 四带三
|
||||||
func HandFourWithThree() HandHandle {
|
func HandFourWithThree[P, C generic.Number, T Card[P, C]]() HandHandle[P, C, T] {
|
||||||
return func(rule *Rule, cards []Card) bool {
|
return func(rule *Rule[P, C, T], cards []T) bool {
|
||||||
group := GroupByPoint(cards...)
|
group := GroupByPoint[P, C, T](cards...)
|
||||||
var hasFour bool
|
var hasFour bool
|
||||||
var count int
|
var count int
|
||||||
for _, cards := range group {
|
for _, cards := range group {
|
||||||
|
@ -263,9 +265,9 @@ func HandFourWithThree() HandHandle {
|
||||||
}
|
}
|
||||||
|
|
||||||
// HandFourWithTwoPairs 四带两对
|
// HandFourWithTwoPairs 四带两对
|
||||||
func HandFourWithTwoPairs() HandHandle {
|
func HandFourWithTwoPairs[P, C generic.Number, T Card[P, C]]() HandHandle[P, C, T] {
|
||||||
return func(rule *Rule, cards []Card) bool {
|
return func(rule *Rule[P, C, T], cards []T) bool {
|
||||||
group := GroupByPoint(cards...)
|
group := GroupByPoint[P, C, T](cards...)
|
||||||
var hasFour bool
|
var hasFour bool
|
||||||
var count int
|
var count int
|
||||||
for _, cards := range group {
|
for _, cards := range group {
|
||||||
|
@ -286,15 +288,15 @@ func HandFourWithTwoPairs() HandHandle {
|
||||||
}
|
}
|
||||||
|
|
||||||
// HandBomb 炸弹
|
// HandBomb 炸弹
|
||||||
func HandBomb() HandHandle {
|
func HandBomb[P, C generic.Number, T Card[P, C]]() HandHandle[P, C, T] {
|
||||||
return func(rule *Rule, cards []Card) bool {
|
return func(rule *Rule[P, C, T], cards []T) bool {
|
||||||
return len(cards) == 4 && rule.IsPointContinuity(4, cards...)
|
return len(cards) == 4 && rule.IsPointContinuity(4, cards...)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// HandStraightPairs 连对
|
// HandStraightPairs 连对
|
||||||
func HandStraightPairs() HandHandle {
|
func HandStraightPairs[P, C generic.Number, T Card[P, C]]() HandHandle[P, C, T] {
|
||||||
return func(rule *Rule, cards []Card) bool {
|
return func(rule *Rule[P, C, T], cards []T) bool {
|
||||||
if len(cards) < 6 || len(cards)%2 != 0 {
|
if len(cards) < 6 || len(cards)%2 != 0 {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
@ -304,8 +306,8 @@ func HandStraightPairs() HandHandle {
|
||||||
|
|
||||||
// HandPlane 飞机
|
// HandPlane 飞机
|
||||||
// - 表示三张点数相同的牌组成的连续的牌
|
// - 表示三张点数相同的牌组成的连续的牌
|
||||||
func HandPlane() HandHandle {
|
func HandPlane[P, C generic.Number, T Card[P, C]]() HandHandle[P, C, T] {
|
||||||
return func(rule *Rule, cards []Card) bool {
|
return func(rule *Rule[P, C, T], cards []T) bool {
|
||||||
if len(cards) < 6 || len(cards)%3 != 0 {
|
if len(cards) < 6 || len(cards)%3 != 0 {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
@ -314,9 +316,9 @@ func HandPlane() HandHandle {
|
||||||
}
|
}
|
||||||
|
|
||||||
// HandPlaneWithOne 飞机带单
|
// HandPlaneWithOne 飞机带单
|
||||||
func HandPlaneWithOne() HandHandle {
|
func HandPlaneWithOne[P, C generic.Number, T Card[P, C]]() HandHandle[P, C, T] {
|
||||||
return func(rule *Rule, cards []Card) bool {
|
return func(rule *Rule[P, C, T], cards []T) bool {
|
||||||
group := GroupByPoint(cards...)
|
group := GroupByPoint[P, C, T](cards...)
|
||||||
if len(group) < 2 {
|
if len(group) < 2 {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
@ -335,20 +337,20 @@ func HandPlaneWithOne() HandHandle {
|
||||||
|
|
||||||
// HandRocket 王炸
|
// HandRocket 王炸
|
||||||
// - 表示一对王牌,即大王和小王
|
// - 表示一对王牌,即大王和小王
|
||||||
func HandRocket() HandHandle {
|
func HandRocket[P, C generic.Number, T Card[P, C]](pile *CardPile[P, C, T]) HandHandle[P, C, T] {
|
||||||
return func(rule *Rule, cards []Card) bool {
|
return func(rule *Rule[P, C, T], cards []T) bool {
|
||||||
if len(cards) != 2 {
|
if len(cards) != 2 {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
return IsRocket(cards[0], cards[1])
|
return IsRocket[P, C, T](pile, cards[0], cards[1])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// HandFlush 同花
|
// HandFlush 同花
|
||||||
// - 表示所有牌的花色都相同
|
// - 表示所有牌的花色都相同
|
||||||
func HandFlush() HandHandle {
|
func HandFlush[P, C generic.Number, T Card[P, C]]() HandHandle[P, C, T] {
|
||||||
return func(rule *Rule, cards []Card) bool {
|
return func(rule *Rule[P, C, T], cards []T) bool {
|
||||||
return IsFlush(cards...)
|
return IsFlush[P, C, T](cards...)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -356,12 +358,12 @@ func HandFlush() HandHandle {
|
||||||
// - count: 顺子的对子数量,例如当 count = 2 时,可以是 334455、445566、556677、667788、778899
|
// - count: 顺子的对子数量,例如当 count = 2 时,可以是 334455、445566、556677、667788、778899
|
||||||
// - lower: 顺子的最小连续数量
|
// - lower: 顺子的最小连续数量
|
||||||
// - limit: 顺子的最大连续数量
|
// - limit: 顺子的最大连续数量
|
||||||
func HandFlushStraight(count, lower, limit int) HandHandle {
|
func HandFlushStraight[P, C generic.Number, T Card[P, C]](count, lower, limit int) HandHandle[P, C, T] {
|
||||||
return func(rule *Rule, cards []Card) bool {
|
return func(rule *Rule[P, C, T], cards []T) bool {
|
||||||
if len(cards) < lower*count || len(cards) > limit*count || len(cards)%count != 0 {
|
if len(cards) < lower*count || len(cards) > limit*count || len(cards)%count != 0 {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
if !IsFlush(cards...) {
|
if !IsFlush[P, C, T](cards...) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
return rule.IsPointContinuity(count, cards...)
|
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
|
// - 例如:333、444、555、666、777、888、999、JJJ、QQQ、KKK、AAA
|
||||||
// - 大小王不能用于豹子,因为他们没有点数
|
// - 大小王不能用于豹子,因为他们没有点数
|
||||||
func HandLeopard() HandHandle {
|
func HandLeopard[P, C generic.Number, T Card[P, C]]() HandHandle[P, C, T] {
|
||||||
return func(rule *Rule, cards []Card) bool {
|
return func(rule *Rule[P, C, T], cards []T) bool {
|
||||||
if len(cards) == 0 {
|
if len(cards) == 0 {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
@ -382,7 +384,7 @@ func HandLeopard() HandHandle {
|
||||||
}
|
}
|
||||||
var card = cards[0]
|
var card = cards[0]
|
||||||
for i := 1; i < len(cards); i++ {
|
for i := 1; i < len(cards); i++ {
|
||||||
if !card.Equal(cards[1]) {
|
if !Equal[P, C, T](card, cards[1]) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -395,9 +397,9 @@ func HandLeopard() HandHandle {
|
||||||
// - 例如:334、445、556、667、778、889、99J、TTQ、JJK、QQA、AA2
|
// - 例如:334、445、556、667、778、889、99J、TTQ、JJK、QQA、AA2
|
||||||
// - 大小王不能用于二带一,因为他们没有点数
|
// - 大小王不能用于二带一,因为他们没有点数
|
||||||
// - 通常用于炸金花玩法中检查对子
|
// - 通常用于炸金花玩法中检查对子
|
||||||
func HandTwoWithOne() HandHandle {
|
func HandTwoWithOne[P, C generic.Number, T Card[P, C]]() HandHandle[P, C, T] {
|
||||||
return func(rule *Rule, cards []Card) bool {
|
return func(rule *Rule[P, C, T], cards []T) bool {
|
||||||
group := GroupByPoint(cards...)
|
group := GroupByPoint[P, C, T](cards...)
|
||||||
var hasTwo bool
|
var hasTwo bool
|
||||||
var count int
|
var count int
|
||||||
for _, cards := range group {
|
for _, cards := range group {
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
|
@ -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
|
||||||
|
}
|
|
@ -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
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
|
@ -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()))
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,13 +1,16 @@
|
||||||
package poker
|
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 通过绑定特定牌型的方式创建扑克玩法
|
// WithHand 通过绑定特定牌型的方式创建扑克玩法
|
||||||
// - 牌型顺序决定了牌型的优先级
|
// - 牌型顺序决定了牌型的优先级
|
||||||
func WithHand(pokerHand string, value int, handle HandHandle) Option {
|
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) {
|
return func(rule *Rule[P, C, T]) {
|
||||||
if _, exist := rule.pokerHand[pokerHand]; exist {
|
if _, exist := rule.pokerHand[pokerHand]; exist {
|
||||||
panic(fmt.Errorf("same poker hand name: %s", pokerHand))
|
panic(fmt.Errorf("same poker hand name: %s", pokerHand))
|
||||||
}
|
}
|
||||||
|
@ -24,8 +27,8 @@ func WithHand(pokerHand string, value int, handle HandHandle) Option {
|
||||||
}
|
}
|
||||||
|
|
||||||
// WithHandRestraint 通过绑定特定克制牌型的方式创建扑克玩法
|
// WithHandRestraint 通过绑定特定克制牌型的方式创建扑克玩法
|
||||||
func WithHandRestraint(pokerHand, restraint string) Option {
|
func WithHandRestraint[P, C generic.Number, T Card[P, C]](pokerHand, restraint string) Option[P, C, T] {
|
||||||
return func(rule *Rule) {
|
return func(rule *Rule[P, C, T]) {
|
||||||
r, exist := rule.restraint[pokerHand]
|
r, exist := rule.restraint[pokerHand]
|
||||||
if !exist {
|
if !exist {
|
||||||
r = map[string]struct{}{}
|
r = map[string]struct{}{}
|
||||||
|
@ -37,8 +40,8 @@ func WithHandRestraint(pokerHand, restraint string) Option {
|
||||||
|
|
||||||
// WithHandRestraintFull 通过绑定所有克制牌型的方式创建扑克玩法
|
// WithHandRestraintFull 通过绑定所有克制牌型的方式创建扑克玩法
|
||||||
// - 需要确保在牌型声明之后调用
|
// - 需要确保在牌型声明之后调用
|
||||||
func WithHandRestraintFull(pokerHand string) Option {
|
func WithHandRestraintFull[P, C generic.Number, T Card[P, C]](pokerHand string) Option[P, C, T] {
|
||||||
return func(rule *Rule) {
|
return func(rule *Rule[P, C, T]) {
|
||||||
for hand := range rule.pokerHand {
|
for hand := range rule.pokerHand {
|
||||||
r, exist := rule.restraint[pokerHand]
|
r, exist := rule.restraint[pokerHand]
|
||||||
if !exist {
|
if !exist {
|
||||||
|
@ -51,22 +54,22 @@ func WithHandRestraintFull(pokerHand string) Option {
|
||||||
}
|
}
|
||||||
|
|
||||||
// WithPointValue 通过特定的扑克点数牌值创建扑克玩法
|
// WithPointValue 通过特定的扑克点数牌值创建扑克玩法
|
||||||
func WithPointValue(pointValues map[Point]int) Option {
|
func WithPointValue[P, C generic.Number, T Card[P, C]](pointValues map[P]int) Option[P, C, T] {
|
||||||
return func(rule *Rule) {
|
return func(rule *Rule[P, C, T]) {
|
||||||
rule.pointValue = pointValues
|
rule.pointValue = pointValues
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// WithColorValue 通过特定的扑克花色牌值创建扑克玩法
|
// WithColorValue 通过特定的扑克花色牌值创建扑克玩法
|
||||||
func WithColorValue(colorValues map[Color]int) Option {
|
func WithColorValue[P, C generic.Number, T Card[P, C]](colorValues map[C]int) Option[P, C, T] {
|
||||||
return func(rule *Rule) {
|
return func(rule *Rule[P, C, T]) {
|
||||||
rule.colorValue = colorValues
|
rule.colorValue = colorValues
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// WithPointSort 通过特定的扑克点数顺序创建扑克玩法,顺序必须为连续的
|
// WithPointSort 通过特定的扑克点数顺序创建扑克玩法,顺序必须为连续的
|
||||||
func WithPointSort(pointSort map[Point]int) Option {
|
func WithPointSort[P, C generic.Number, T Card[P, C]](pointSort map[P]int) Option[P, C, T] {
|
||||||
return func(rule *Rule) {
|
return func(rule *Rule[P, C, T]) {
|
||||||
for k, v := range pointSort {
|
for k, v := range pointSort {
|
||||||
rule.pointSort[k] = v
|
rule.pointSort[k] = v
|
||||||
}
|
}
|
||||||
|
@ -74,8 +77,8 @@ func WithPointSort(pointSort map[Point]int) Option {
|
||||||
}
|
}
|
||||||
|
|
||||||
// WithColorSort 通过特定的扑克花色顺序创建扑克玩法,顺序必须为连续的
|
// WithColorSort 通过特定的扑克花色顺序创建扑克玩法,顺序必须为连续的
|
||||||
func WithColorSort(colorSort map[Color]int) Option {
|
func WithColorSort[P, C generic.Number, T Card[P, C]](colorSort map[C]int) Option[P, C, T] {
|
||||||
return func(rule *Rule) {
|
return func(rule *Rule[P, C, T]) {
|
||||||
for k, v := range colorSort {
|
for k, v := range colorSort {
|
||||||
rule.colorSort[k] = v
|
rule.colorSort[k] = v
|
||||||
}
|
}
|
||||||
|
@ -83,10 +86,10 @@ func WithColorSort(colorSort map[Color]int) Option {
|
||||||
}
|
}
|
||||||
|
|
||||||
// WithExcludeContinuityPoint 排除连续的点数
|
// WithExcludeContinuityPoint 排除连续的点数
|
||||||
func WithExcludeContinuityPoint(points ...Point) Option {
|
func WithExcludeContinuityPoint[P, C generic.Number, T Card[P, C]](points ...P) Option[P, C, T] {
|
||||||
return func(rule *Rule) {
|
return func(rule *Rule[P, C, T]) {
|
||||||
if rule.excludeContinuityPoint == nil {
|
if rule.excludeContinuityPoint == nil {
|
||||||
rule.excludeContinuityPoint = make(map[Point]struct{})
|
rule.excludeContinuityPoint = make(map[P]struct{})
|
||||||
}
|
}
|
||||||
for _, point := range points {
|
for _, point := range points {
|
||||||
rule.excludeContinuityPoint[point] = struct{}{}
|
rule.excludeContinuityPoint[point] = struct{}{}
|
||||||
|
|
|
@ -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
|
|
||||||
}
|
|
|
@ -1,9 +1,14 @@
|
||||||
package poker
|
package poker
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/kercylan98/minotaur/utils/generic"
|
||||||
|
"math"
|
||||||
|
)
|
||||||
|
|
||||||
// IsContainJoker 检查扑克牌是否包含大小王
|
// 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 {
|
for _, card := range cards {
|
||||||
if card.IsJoker() {
|
if IsJoker[P, C, T](pile, card) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -11,8 +16,8 @@ func IsContainJoker(cards ...Card) bool {
|
||||||
}
|
}
|
||||||
|
|
||||||
// GroupByPoint 将扑克牌按照点数分组
|
// GroupByPoint 将扑克牌按照点数分组
|
||||||
func GroupByPoint(cards ...Card) map[Point][]Card {
|
func GroupByPoint[P, C generic.Number, T Card[P, C]](cards ...T) map[P][]T {
|
||||||
group := map[Point][]Card{}
|
group := map[P][]T{}
|
||||||
for _, card := range cards {
|
for _, card := range cards {
|
||||||
group[card.GetPoint()] = append(group[card.GetPoint()], card)
|
group[card.GetPoint()] = append(group[card.GetPoint()], card)
|
||||||
}
|
}
|
||||||
|
@ -20,8 +25,8 @@ func GroupByPoint(cards ...Card) map[Point][]Card {
|
||||||
}
|
}
|
||||||
|
|
||||||
// GroupByColor 将扑克牌按照花色分组
|
// GroupByColor 将扑克牌按照花色分组
|
||||||
func GroupByColor(cards ...Card) map[Color][]Card {
|
func GroupByColor[P, C generic.Number, T Card[P, C]](cards ...T) map[C][]T {
|
||||||
group := map[Color][]Card{}
|
group := map[C][]T{}
|
||||||
for _, card := range cards {
|
for _, card := range cards {
|
||||||
group[card.GetColor()] = append(group[card.GetColor()], card)
|
group[card.GetColor()] = append(group[card.GetColor()], card)
|
||||||
}
|
}
|
||||||
|
@ -29,12 +34,18 @@ func GroupByColor(cards ...Card) map[Color][]Card {
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsRocket 两张牌能否组成红黑 Joker
|
// IsRocket 两张牌能否组成红黑 Joker
|
||||||
func IsRocket(cardA, cardB Card) bool {
|
func IsRocket[P, C generic.Number, T Card[P, C]](pile *CardPile[P, C, T], cardA, cardB T) bool {
|
||||||
return cardA.GetPoint() == PointRedJoker && cardB.GetPoint() == PointBlackJoker || cardA.GetPoint() == PointBlackJoker && cardB.GetPoint() == PointRedJoker
|
var num int
|
||||||
|
for _, joker := range pile.jokers {
|
||||||
|
if cardA.GetPoint() == joker || cardB.GetPoint() == joker {
|
||||||
|
num++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return num == 2
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsFlush 判断是否是同花
|
// IsFlush 判断是否是同花
|
||||||
func IsFlush(cards ...Card) bool {
|
func IsFlush[P, C generic.Number, T Card[P, C]](cards ...T) bool {
|
||||||
if len(cards) == 0 {
|
if len(cards) == 0 {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
@ -52,8 +63,8 @@ func IsFlush(cards ...Card) bool {
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetCardsPoint 获取一组扑克牌的点数
|
// GetCardsPoint 获取一组扑克牌的点数
|
||||||
func GetCardsPoint(cards ...Card) []Point {
|
func GetCardsPoint[P, C generic.Number, T Card[P, C]](cards ...T) []P {
|
||||||
var points = make([]Point, len(cards))
|
var points = make([]P, len(cards))
|
||||||
for i, card := range cards {
|
for i, card := range cards {
|
||||||
points[i] = card.GetPoint()
|
points[i] = card.GetPoint()
|
||||||
}
|
}
|
||||||
|
@ -61,10 +72,136 @@ func GetCardsPoint(cards ...Card) []Point {
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetCardsColor 获取一组扑克牌的花色
|
// GetCardsColor 获取一组扑克牌的花色
|
||||||
func GetCardsColor(cards ...Card) []Color {
|
func GetCardsColor[P, C generic.Number, T Card[P, C]](cards ...T) []C {
|
||||||
var colors = make([]Color, len(cards))
|
var colors = make([]C, len(cards))
|
||||||
for i, card := range cards {
|
for i, card := range cards {
|
||||||
colors[i] = card.GetColor()
|
colors[i] = card.GetColor()
|
||||||
}
|
}
|
||||||
return colors
|
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
|
||||||
|
}
|
||||||
|
|
|
@ -1,16 +1,17 @@
|
||||||
package poker
|
package poker
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"github.com/kercylan98/minotaur/utils/generic"
|
||||||
"github.com/kercylan98/minotaur/utils/hash"
|
"github.com/kercylan98/minotaur/utils/hash"
|
||||||
"github.com/kercylan98/minotaur/utils/maths"
|
"github.com/kercylan98/minotaur/utils/maths"
|
||||||
"sort"
|
"sort"
|
||||||
)
|
)
|
||||||
|
|
||||||
func NewRule(options ...Option) *Rule {
|
func NewRule[P, C generic.Number, T Card[P, C]](options ...Option[P, C, T]) *Rule[P, C, T] {
|
||||||
poker := &Rule{
|
poker := &Rule[P, C, T]{
|
||||||
pokerHand: map[string]HandHandle{},
|
pokerHand: map[string]HandHandle[P, C, T]{},
|
||||||
pointSort: hash.Copy(defaultPointSort),
|
//pointSort: hash.Copy(defaultPointSort),
|
||||||
colorSort: hash.Copy(defaultColorSort),
|
//colorSort: hash.Copy(defaultColorSort),
|
||||||
pokerHandValue: map[string]int{},
|
pokerHandValue: map[string]int{},
|
||||||
restraint: map[string]map[string]struct{}{},
|
restraint: map[string]map[string]struct{}{},
|
||||||
}
|
}
|
||||||
|
@ -23,19 +24,19 @@ func NewRule(options ...Option) *Rule {
|
||||||
return poker
|
return poker
|
||||||
}
|
}
|
||||||
|
|
||||||
type Rule struct {
|
type Rule[P, C generic.Number, T Card[P, C]] struct {
|
||||||
pokerHand map[string]HandHandle
|
pokerHand map[string]HandHandle[P, C, T]
|
||||||
pokerHandValue map[string]int
|
pokerHandValue map[string]int
|
||||||
pointValue map[Point]int
|
pointValue map[P]int
|
||||||
colorValue map[Color]int
|
colorValue map[C]int
|
||||||
pointSort map[Point]int
|
pointSort map[P]int
|
||||||
colorSort map[Color]int
|
colorSort map[C]int
|
||||||
excludeContinuityPoint map[Point]struct{}
|
excludeContinuityPoint map[P]struct{}
|
||||||
restraint map[string]map[string]struct{}
|
restraint map[string]map[string]struct{}
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetCardCountWithPointMaximumNumber 获取指定点数的牌的数量
|
// 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
|
count := 0
|
||||||
for _, card := range cards {
|
for _, card := range cards {
|
||||||
if card.GetPoint() == point {
|
if card.GetPoint() == point {
|
||||||
|
@ -49,7 +50,7 @@ func (slf *Rule) GetCardCountWithPointMaximumNumber(cards []Card, point Point, m
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetCardCountWithColorMaximumNumber 获取指定花色的牌的数量
|
// 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
|
count := 0
|
||||||
for _, card := range cards {
|
for _, card := range cards {
|
||||||
if card.GetColor() == color {
|
if card.GetColor() == color {
|
||||||
|
@ -63,10 +64,10 @@ func (slf *Rule) GetCardCountWithColorMaximumNumber(cards []Card, color Color, m
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetCardCountWithMaximumNumber 获取指定牌的数量
|
// 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
|
count := 0
|
||||||
for _, c := range cards {
|
for _, c := range cards {
|
||||||
if c.Equal(card) {
|
if Equal[P, C, T](c, card) {
|
||||||
count++
|
count++
|
||||||
if count >= maximumNumber {
|
if count >= maximumNumber {
|
||||||
return count
|
return count
|
||||||
|
@ -77,7 +78,7 @@ func (slf *Rule) GetCardCountWithMaximumNumber(cards []Card, card Card, maximumN
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetCardCountWithPoint 获取指定点数的牌的数量
|
// GetCardCountWithPoint 获取指定点数的牌的数量
|
||||||
func (slf *Rule) GetCardCountWithPoint(cards []Card, point Point) int {
|
func (slf *Rule[P, C, T]) GetCardCountWithPoint(cards []T, point P) int {
|
||||||
count := 0
|
count := 0
|
||||||
for _, card := range cards {
|
for _, card := range cards {
|
||||||
if card.GetPoint() == point {
|
if card.GetPoint() == point {
|
||||||
|
@ -88,7 +89,7 @@ func (slf *Rule) GetCardCountWithPoint(cards []Card, point Point) int {
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetCardCountWithColor 获取指定花色的牌的数量
|
// GetCardCountWithColor 获取指定花色的牌的数量
|
||||||
func (slf *Rule) GetCardCountWithColor(cards []Card, color Color) int {
|
func (slf *Rule[P, C, T]) GetCardCountWithColor(cards []T, color C) int {
|
||||||
count := 0
|
count := 0
|
||||||
for _, card := range cards {
|
for _, card := range cards {
|
||||||
if card.GetColor() == color {
|
if card.GetColor() == color {
|
||||||
|
@ -99,10 +100,10 @@ func (slf *Rule) GetCardCountWithColor(cards []Card, color Color) int {
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetCardCount 获取指定牌的数量
|
// GetCardCount 获取指定牌的数量
|
||||||
func (slf *Rule) GetCardCount(cards []Card, card Card) int {
|
func (slf *Rule[P, C, T]) GetCardCount(cards []T, card T) int {
|
||||||
count := 0
|
count := 0
|
||||||
for _, c := range cards {
|
for _, c := range cards {
|
||||||
if c.Equal(card) {
|
if Equal[P, C, T](c, card) {
|
||||||
count++
|
count++
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -110,7 +111,7 @@ func (slf *Rule) GetCardCount(cards []Card, card Card) int {
|
||||||
}
|
}
|
||||||
|
|
||||||
// PokerHandIsMatch 检查两组扑克牌牌型是否匹配
|
// 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...)
|
handA, exist := slf.PokerHand(cardsA...)
|
||||||
if !exist {
|
if !exist {
|
||||||
return false
|
return false
|
||||||
|
@ -126,7 +127,7 @@ func (slf *Rule) PokerHandIsMatch(cardsA, cardsB []Card) bool {
|
||||||
}
|
}
|
||||||
|
|
||||||
// PokerHand 获取一组扑克的牌型
|
// 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 {
|
for phn := range slf.pokerHandValue {
|
||||||
if slf.pokerHand[phn](slf, cards) {
|
if slf.pokerHand[phn](slf, cards) {
|
||||||
return phn, true
|
return phn, true
|
||||||
|
@ -136,11 +137,11 @@ func (slf *Rule) PokerHand(cards ...Card) (pokerHand string, hit bool) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsPointContinuity 检查一组扑克牌点数是否连续,count 表示了每个点数的数量
|
// 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 {
|
if len(cards) == 0 {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
group := GroupByPoint(cards...)
|
group := GroupByPoint[P, C, T](cards...)
|
||||||
var values []int
|
var values []int
|
||||||
for point, cards := range group {
|
for point, cards := range group {
|
||||||
if _, exist := slf.excludeContinuityPoint[point]; exist {
|
if _, exist := slf.excludeContinuityPoint[point]; exist {
|
||||||
|
@ -155,7 +156,7 @@ func (slf *Rule) IsPointContinuity(count int, cards ...Card) bool {
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsSameColor 检查一组扑克牌是否同花
|
// IsSameColor 检查一组扑克牌是否同花
|
||||||
func (slf *Rule) IsSameColor(cards ...Card) bool {
|
func (slf *Rule[P, C, T]) IsSameColor(cards ...T) bool {
|
||||||
length := len(cards)
|
length := len(cards)
|
||||||
if length == 0 {
|
if length == 0 {
|
||||||
return false
|
return false
|
||||||
|
@ -173,7 +174,7 @@ func (slf *Rule) IsSameColor(cards ...Card) bool {
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsSamePoint 检查一组扑克牌是否同点
|
// IsSamePoint 检查一组扑克牌是否同点
|
||||||
func (slf *Rule) IsSamePoint(cards ...Card) bool {
|
func (slf *Rule[P, C, T]) IsSamePoint(cards ...T) bool {
|
||||||
length := len(cards)
|
length := len(cards)
|
||||||
if length == 0 {
|
if length == 0 {
|
||||||
return false
|
return false
|
||||||
|
@ -191,7 +192,7 @@ func (slf *Rule) IsSamePoint(cards ...Card) bool {
|
||||||
}
|
}
|
||||||
|
|
||||||
// SortByPointDesc 将扑克牌按照点数从大到小排序
|
// 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 {
|
sort.Slice(cards, func(i, j int) bool {
|
||||||
return slf.pointSort[cards[i].GetPoint()] > slf.pointSort[cards[j].GetPoint()]
|
return slf.pointSort[cards[i].GetPoint()] > slf.pointSort[cards[j].GetPoint()]
|
||||||
})
|
})
|
||||||
|
@ -199,7 +200,7 @@ func (slf *Rule) SortByPointDesc(cards []Card) []Card {
|
||||||
}
|
}
|
||||||
|
|
||||||
// SortByPointAsc 将扑克牌按照点数从小到大排序
|
// 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 {
|
sort.Slice(cards, func(i, j int) bool {
|
||||||
return slf.pointSort[cards[i].GetPoint()] < slf.pointSort[cards[j].GetPoint()]
|
return slf.pointSort[cards[i].GetPoint()] < slf.pointSort[cards[j].GetPoint()]
|
||||||
})
|
})
|
||||||
|
@ -207,7 +208,7 @@ func (slf *Rule) SortByPointAsc(cards []Card) []Card {
|
||||||
}
|
}
|
||||||
|
|
||||||
// SortByColorDesc 将扑克牌按照花色从大到小排序
|
// 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 {
|
sort.Slice(cards, func(i, j int) bool {
|
||||||
return slf.colorSort[cards[i].GetColor()] > slf.colorSort[cards[j].GetColor()]
|
return slf.colorSort[cards[i].GetColor()] > slf.colorSort[cards[j].GetColor()]
|
||||||
})
|
})
|
||||||
|
@ -215,7 +216,7 @@ func (slf *Rule) SortByColorDesc(cards []Card) []Card {
|
||||||
}
|
}
|
||||||
|
|
||||||
// SortByColorAsc 将扑克牌按照花色从小到大排序
|
// 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 {
|
sort.Slice(cards, func(i, j int) bool {
|
||||||
return slf.colorSort[cards[i].GetColor()] < slf.colorSort[cards[j].GetColor()]
|
return slf.colorSort[cards[i].GetColor()] < slf.colorSort[cards[j].GetColor()]
|
||||||
})
|
})
|
||||||
|
@ -223,13 +224,13 @@ func (slf *Rule) SortByColorAsc(cards []Card) []Card {
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetValueWithPokerHand 获取扑克牌的牌型牌值
|
// 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]
|
hv := slf.pokerHandValue[hand]
|
||||||
return hv * slf.GetValueWithCards(cards...)
|
return hv * slf.GetValueWithCards(cards...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetValueWithCards 获取扑克牌的牌值
|
// GetValueWithCards 获取扑克牌的牌值
|
||||||
func (slf *Rule) GetValueWithCards(cards ...Card) int {
|
func (slf *Rule[P, C, T]) GetValueWithCards(cards ...T) int {
|
||||||
var value int
|
var value int
|
||||||
for _, card := range cards {
|
for _, card := range cards {
|
||||||
value += slf.GetValueWithPoint(card.GetPoint())
|
value += slf.GetValueWithPoint(card.GetPoint())
|
||||||
|
@ -239,16 +240,16 @@ func (slf *Rule) GetValueWithCards(cards ...Card) int {
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetValueWithPoint 获取扑克牌的点数牌值
|
// GetValueWithPoint 获取扑克牌的点数牌值
|
||||||
func (slf *Rule) GetValueWithPoint(point Point) int {
|
func (slf *Rule[P, C, T]) GetValueWithPoint(point P) int {
|
||||||
return slf.pointValue[point]
|
return slf.pointValue[point]
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetValueWithColor 获取扑克牌的花色牌值
|
// GetValueWithColor 获取扑克牌的花色牌值
|
||||||
func (slf *Rule) GetValueWithColor(color Color) int {
|
func (slf *Rule[P, C, T]) GetValueWithColor(color C) int {
|
||||||
return slf.colorValue[color]
|
return slf.colorValue[color]
|
||||||
}
|
}
|
||||||
|
|
||||||
// CompareValueWithCards 根据特定的条件表达式比较两组扑克牌的牌值
|
// 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...))
|
return maths.Compare(slf.GetValueWithCards(cards1...), expression, slf.GetValueWithCards(cards2...))
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,9 +1,5 @@
|
||||||
package game
|
package game
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/kercylan98/minotaur/utils/hash"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Room 房间类似于简版的游戏世界(World),不过没有游戏实体
|
// Room 房间类似于简版的游戏世界(World),不过没有游戏实体
|
||||||
type Room[PlayerID comparable, P Player[PlayerID]] interface {
|
type Room[PlayerID comparable, P Player[PlayerID]] interface {
|
||||||
// GetGuid 获取房间的唯一标识符
|
// GetGuid 获取房间的唯一标识符
|
||||||
|
@ -13,7 +9,7 @@ type Room[PlayerID comparable, P Player[PlayerID]] interface {
|
||||||
// GetPlayer 根据玩家id获取玩家
|
// GetPlayer 根据玩家id获取玩家
|
||||||
GetPlayer(id PlayerID) P
|
GetPlayer(id PlayerID) P
|
||||||
// GetPlayers 获取房间中的所有玩家
|
// GetPlayers 获取房间中的所有玩家
|
||||||
GetPlayers() hash.MapReadonly[PlayerID, P]
|
GetPlayers() map[PlayerID]P
|
||||||
// GetPlayerCount 获取玩家数量
|
// GetPlayerCount 获取玩家数量
|
||||||
GetPlayerCount() int
|
GetPlayerCount() int
|
||||||
// IsExistPlayer 检查房间中是否存在特定玩家
|
// IsExistPlayer 检查房间中是否存在特定玩家
|
||||||
|
|
|
@ -36,4 +36,12 @@ type RoomSeat[PlayerID comparable, P Player[PlayerID]] interface {
|
||||||
// - 超出范围将返回-1
|
// - 超出范围将返回-1
|
||||||
// - 当没有下一个座位号时将始终返回本身
|
// - 当没有下一个座位号时将始终返回本身
|
||||||
GetNextSeatVacancy(seat int) int
|
GetNextSeatVacancy(seat int) int
|
||||||
|
// GetPrevSeat 获取上一个座位号,空缺的位置将会被跳过
|
||||||
|
// - 超出范围将返回-1
|
||||||
|
// - 当没有上一个座位号时将始终返回本身
|
||||||
|
GetPrevSeat(seat int) int
|
||||||
|
// GetPrevSeatVacancy 获取上一个座位号,空缺的位置将被保留
|
||||||
|
// - 超出范围将返回-1
|
||||||
|
// - 当没有上一个座位号时将始终返回本身
|
||||||
|
GetPrevSeatVacancy(seat int) int
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,9 +1,5 @@
|
||||||
package game
|
package game
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/kercylan98/minotaur/utils/hash"
|
|
||||||
)
|
|
||||||
|
|
||||||
// World 游戏世界接口定义
|
// World 游戏世界接口定义
|
||||||
type World[PlayerID comparable, P Player[PlayerID]] interface {
|
type World[PlayerID comparable, P Player[PlayerID]] interface {
|
||||||
// GetGuid 获取世界的唯一标识符
|
// GetGuid 获取世界的唯一标识符
|
||||||
|
@ -13,15 +9,15 @@ type World[PlayerID comparable, P Player[PlayerID]] interface {
|
||||||
// GetPlayer 根据玩家id获取玩家
|
// GetPlayer 根据玩家id获取玩家
|
||||||
GetPlayer(id PlayerID) P
|
GetPlayer(id PlayerID) P
|
||||||
// GetPlayers 获取世界中的所有玩家
|
// GetPlayers 获取世界中的所有玩家
|
||||||
GetPlayers() hash.MapReadonly[PlayerID, P]
|
GetPlayers() map[PlayerID]P
|
||||||
// GetActor 根据唯一标识符获取世界中的游戏对象
|
// GetActor 根据唯一标识符获取世界中的游戏对象
|
||||||
GetActor(guid int64) Actor
|
GetActor(guid int64) Actor
|
||||||
// GetActors 获取世界中的所有游戏对象
|
// GetActors 获取世界中的所有游戏对象
|
||||||
GetActors() hash.MapReadonly[int64, Actor]
|
GetActors() map[int64]Actor
|
||||||
// GetPlayerActor 获取游戏世界中归属特定玩家的特定游戏对象
|
// GetPlayerActor 获取游戏世界中归属特定玩家的特定游戏对象
|
||||||
GetPlayerActor(id PlayerID, guid int64) Actor
|
GetPlayerActor(id PlayerID, guid int64) Actor
|
||||||
// GetPlayerActors 获取游戏世界中归属特定玩家的所有游戏对象
|
// GetPlayerActors 获取游戏世界中归属特定玩家的所有游戏对象
|
||||||
GetPlayerActors(id PlayerID) hash.MapReadonly[int64, Actor]
|
GetPlayerActors(id PlayerID) map[int64]Actor
|
||||||
// IsExistPlayer 检查游戏世界中是否存在特定玩家
|
// IsExistPlayer 检查游戏世界中是否存在特定玩家
|
||||||
IsExistPlayer(id PlayerID) bool
|
IsExistPlayer(id PlayerID) bool
|
||||||
// IsExistActor 检查游戏世界中是否存在特定游戏对象
|
// IsExistActor 检查游戏世界中是否存在特定游戏对象
|
||||||
|
|
2
go.mod
2
go.mod
|
@ -23,6 +23,7 @@ require (
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
|
github.com/alphadose/haxmap v1.2.0 // indirect
|
||||||
github.com/bytedance/sonic v1.9.1 // indirect
|
github.com/bytedance/sonic v1.9.1 // indirect
|
||||||
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 // indirect
|
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 // indirect
|
||||||
github.com/gabriel-vasile/mimetype v1.4.2 // indirect
|
github.com/gabriel-vasile/mimetype v1.4.2 // indirect
|
||||||
|
@ -61,6 +62,7 @@ require (
|
||||||
go.uber.org/multierr v1.11.0 // indirect
|
go.uber.org/multierr v1.11.0 // indirect
|
||||||
golang.org/x/arch v0.3.0 // indirect
|
golang.org/x/arch v0.3.0 // indirect
|
||||||
golang.org/x/crypto v0.9.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/net v0.10.0 // indirect
|
||||||
golang.org/x/sys v0.8.0 // indirect
|
golang.org/x/sys v0.8.0 // indirect
|
||||||
golang.org/x/text v0.9.0 // indirect
|
golang.org/x/text v0.9.0 // indirect
|
||||||
|
|
4
go.sum
4
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/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 h1:iPugyBI7oFtbDZXC4dnY093M1kZx6k/95sen92gafbY=
|
||||||
github.com/RussellLuo/timingwheel v0.0.0-20220218152713-54845bda3108/go.mod h1:WAMLHwunr1hi3u7OjGV6/VWG9QbdMhGpEKjROiSFd10=
|
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 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8=
|
||||||
github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=
|
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=
|
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 h1:LF6fAI+IutBocDJ2OT0Q1g8plpYljMZ4+lty+dsqw3g=
|
||||||
golang.org/x/crypto v0.9.0/go.mod h1:yrmDGqONDYtNj3tH8X9dzUun2m2lzPa9ngI6/RUPGR0=
|
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-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-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-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
||||||
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||||
|
|
|
@ -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)
|
|
||||||
})
|
|
||||||
}
|
|
|
@ -1,8 +1,7 @@
|
||||||
package report
|
package report
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/kercylan98/minotaur/utils/asynchronous"
|
"github.com/kercylan98/minotaur/utils/concurrent"
|
||||||
"github.com/kercylan98/minotaur/utils/hash"
|
|
||||||
"sync"
|
"sync"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -10,7 +9,7 @@ import (
|
||||||
func NewDataBuried[DataID comparable, Data any](name string, hitLogic HitLogic[Data], options ...DataBuriedOption[DataID, Data]) *DataBuried[DataID, Data] {
|
func NewDataBuried[DataID comparable, Data any](name string, hitLogic HitLogic[Data], options ...DataBuriedOption[DataID, Data]) *DataBuried[DataID, Data] {
|
||||||
buried := &DataBuried[DataID, Data]{
|
buried := &DataBuried[DataID, Data]{
|
||||||
name: name,
|
name: name,
|
||||||
data: asynchronous.NewMap[DataID, Data](),
|
data: concurrent.NewBalanceMap[DataID, Data](),
|
||||||
hitLogic: hitLogic,
|
hitLogic: hitLogic,
|
||||||
}
|
}
|
||||||
buried.setData = func(id DataID, data Data) {
|
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 {
|
type DataBuried[DataID comparable, Data any] struct {
|
||||||
name string
|
name string
|
||||||
data hash.Map[DataID, Data]
|
data *concurrent.BalanceMap[DataID, Data]
|
||||||
hitLogic HitLogic[Data]
|
hitLogic HitLogic[Data]
|
||||||
getData func(DataID) Data
|
getData func(DataID) Data
|
||||||
setData func(id DataID, data Data)
|
setData func(id DataID, data Data)
|
||||||
|
|
|
@ -2,7 +2,7 @@ package server
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/gorilla/websocket"
|
"github.com/gorilla/websocket"
|
||||||
"github.com/kercylan98/minotaur/utils/synchronization"
|
"github.com/kercylan98/minotaur/utils/concurrent"
|
||||||
"github.com/panjf2000/gnet"
|
"github.com/panjf2000/gnet"
|
||||||
"github.com/xtaci/kcp-go/v5"
|
"github.com/xtaci/kcp-go/v5"
|
||||||
"net"
|
"net"
|
||||||
|
@ -75,7 +75,7 @@ type Conn struct {
|
||||||
kcp *kcp.UDPSession
|
kcp *kcp.UDPSession
|
||||||
data map[any]any
|
data map[any]any
|
||||||
mutex sync.Mutex
|
mutex sync.Mutex
|
||||||
packetPool *synchronization.Pool[*connPacket]
|
packetPool *concurrent.Pool[*connPacket]
|
||||||
packets []*connPacket
|
packets []*connPacket
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -187,7 +187,7 @@ func (slf *Conn) WriteWithCallback(packet Packet, callback func(err error), mess
|
||||||
|
|
||||||
// writeLoop 写循环
|
// writeLoop 写循环
|
||||||
func (slf *Conn) writeLoop(wait *sync.WaitGroup) {
|
func (slf *Conn) writeLoop(wait *sync.WaitGroup) {
|
||||||
slf.packetPool = synchronization.NewPool[*connPacket](10*1024,
|
slf.packetPool = concurrent.NewPool[*connPacket](10*1024,
|
||||||
func() *connPacket {
|
func() *connPacket {
|
||||||
return &connPacket{}
|
return &connPacket{}
|
||||||
}, func(data *connPacket) {
|
}, func(data *connPacket) {
|
||||||
|
|
|
@ -4,8 +4,8 @@ import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/kercylan98/minotaur/server"
|
"github.com/kercylan98/minotaur/server"
|
||||||
|
"github.com/kercylan98/minotaur/utils/concurrent"
|
||||||
"github.com/kercylan98/minotaur/utils/log"
|
"github.com/kercylan98/minotaur/utils/log"
|
||||||
"github.com/kercylan98/minotaur/utils/synchronization"
|
|
||||||
"github.com/nats-io/nats.go"
|
"github.com/nats-io/nats.go"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
@ -18,7 +18,7 @@ func NewNats(url string, options ...NatsOption) *Nats {
|
||||||
n := &Nats{
|
n := &Nats{
|
||||||
url: url,
|
url: url,
|
||||||
subject: "MINOTAUR_CROSS",
|
subject: "MINOTAUR_CROSS",
|
||||||
messagePool: synchronization.NewPool[*Message](1024*100, func() *Message {
|
messagePool: concurrent.NewPool[*Message](1024*100, func() *Message {
|
||||||
return new(Message)
|
return new(Message)
|
||||||
}, func(data *Message) {
|
}, func(data *Message) {
|
||||||
data.ServerId = 0
|
data.ServerId = 0
|
||||||
|
@ -36,7 +36,7 @@ type Nats struct {
|
||||||
url string
|
url string
|
||||||
subject string
|
subject string
|
||||||
options []nats.Option
|
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) {
|
func (slf *Nats) Init(server *server.Server, packetHandle func(serverId int64, packet []byte)) (err error) {
|
||||||
|
|
|
@ -5,9 +5,9 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"github.com/gorilla/websocket"
|
"github.com/gorilla/websocket"
|
||||||
|
"github.com/kercylan98/minotaur/utils/concurrent"
|
||||||
"github.com/kercylan98/minotaur/utils/log"
|
"github.com/kercylan98/minotaur/utils/log"
|
||||||
"github.com/kercylan98/minotaur/utils/super"
|
"github.com/kercylan98/minotaur/utils/super"
|
||||||
"github.com/kercylan98/minotaur/utils/synchronization"
|
|
||||||
"github.com/kercylan98/minotaur/utils/timer"
|
"github.com/kercylan98/minotaur/utils/timer"
|
||||||
"github.com/kercylan98/minotaur/utils/times"
|
"github.com/kercylan98/minotaur/utils/times"
|
||||||
"github.com/panjf2000/ants/v2"
|
"github.com/panjf2000/ants/v2"
|
||||||
|
@ -33,7 +33,7 @@ func New(network Network, options ...Option) *Server {
|
||||||
runtime: &runtime{messagePoolSize: DefaultMessageBufferSize, messageChannelSize: DefaultMessageChannelSize},
|
runtime: &runtime{messagePoolSize: DefaultMessageBufferSize, messageChannelSize: DefaultMessageChannelSize},
|
||||||
option: &option{},
|
option: &option{},
|
||||||
network: network,
|
network: network,
|
||||||
online: synchronization.NewMap[string, *Conn](),
|
online: concurrent.NewBalanceMap[string, *Conn](),
|
||||||
closeChannel: make(chan struct{}, 1),
|
closeChannel: make(chan struct{}, 1),
|
||||||
systemSignal: make(chan os.Signal, 1),
|
systemSignal: make(chan os.Signal, 1),
|
||||||
}
|
}
|
||||||
|
@ -72,26 +72,26 @@ func New(network Network, options ...Option) *Server {
|
||||||
|
|
||||||
// Server 网络服务器
|
// Server 网络服务器
|
||||||
type Server struct {
|
type Server struct {
|
||||||
*event // 事件
|
*event // 事件
|
||||||
*runtime // 运行时
|
*runtime // 运行时
|
||||||
*option // 可选项
|
*option // 可选项
|
||||||
network Network // 网络类型
|
network Network // 网络类型
|
||||||
addr string // 侦听地址
|
addr string // 侦听地址
|
||||||
systemSignal chan os.Signal // 系统信号
|
systemSignal chan os.Signal // 系统信号
|
||||||
online *synchronization.Map[string, *Conn] // 在线连接
|
online *concurrent.BalanceMap[string, *Conn] // 在线连接
|
||||||
ginServer *gin.Engine // HTTP模式下的路由器
|
ginServer *gin.Engine // HTTP模式下的路由器
|
||||||
httpServer *http.Server // HTTP模式下的服务器
|
httpServer *http.Server // HTTP模式下的服务器
|
||||||
grpcServer *grpc.Server // GRPC模式下的服务器
|
grpcServer *grpc.Server // GRPC模式下的服务器
|
||||||
gServer *gNet // TCP或UDP模式下的服务器
|
gServer *gNet // TCP或UDP模式下的服务器
|
||||||
isRunning bool // 是否正在运行
|
isRunning bool // 是否正在运行
|
||||||
isShutdown atomic.Bool // 是否已关闭
|
isShutdown atomic.Bool // 是否已关闭
|
||||||
closeChannel chan struct{} // 关闭信号
|
closeChannel chan struct{} // 关闭信号
|
||||||
ants *ants.Pool // 协程池
|
ants *ants.Pool // 协程池
|
||||||
messagePool *synchronization.Pool[*Message] // 消息池
|
messagePool *concurrent.Pool[*Message] // 消息池
|
||||||
messageChannel chan *Message // 消息管道
|
messageChannel chan *Message // 消息管道
|
||||||
multiple *MultipleServer // 多服务器模式下的服务器
|
multiple *MultipleServer // 多服务器模式下的服务器
|
||||||
multipleRuntimeErrorChan chan error // 多服务器模式下的运行时错误
|
multipleRuntimeErrorChan chan error // 多服务器模式下的运行时错误
|
||||||
runMode RunMode // 运行模式
|
runMode RunMode // 运行模式
|
||||||
}
|
}
|
||||||
|
|
||||||
// Run 使用特定地址运行服务器
|
// Run 使用特定地址运行服务器
|
||||||
|
@ -115,7 +115,7 @@ func (slf *Server) Run(addr string) error {
|
||||||
var protoAddr = fmt.Sprintf("%s://%s", slf.network, slf.addr)
|
var protoAddr = fmt.Sprintf("%s://%s", slf.network, slf.addr)
|
||||||
var messageInitFinish = make(chan struct{}, 1)
|
var messageInitFinish = make(chan struct{}, 1)
|
||||||
var connectionInitHandle = func(callback func()) {
|
var connectionInitHandle = func(callback func()) {
|
||||||
slf.messagePool = synchronization.NewPool[*Message](slf.messagePoolSize,
|
slf.messagePool = concurrent.NewPool[*Message](slf.messagePoolSize,
|
||||||
func() *Message {
|
func() *Message {
|
||||||
return &Message{}
|
return &Message{}
|
||||||
},
|
},
|
||||||
|
|
|
@ -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
|
|
||||||
}
|
|
|
@ -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
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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
|
||||||
|
}
|
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,4 +1,4 @@
|
||||||
package synchronization
|
package concurrent
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/kercylan98/minotaur/utils/log"
|
"github.com/kercylan98/minotaur/utils/log"
|
|
@ -1,4 +1,4 @@
|
||||||
package synchronization
|
package concurrent
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/kercylan98/minotaur/utils/slice"
|
"github.com/kercylan98/minotaur/utils/slice"
|
|
@ -1,4 +1,4 @@
|
||||||
package synchronization
|
package concurrent
|
||||||
|
|
||||||
type SliceOption[T any] func(slice *Slice[T])
|
type SliceOption[T any] func(slice *Slice[T])
|
||||||
|
|
|
@ -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)
|
|
||||||
}
|
|
|
@ -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)
|
|
||||||
}
|
|
|
@ -197,3 +197,125 @@ func ContainsAny[V any](slice []V, values V) bool {
|
||||||
}
|
}
|
||||||
return false
|
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
|
||||||
|
}
|
||||||
|
|
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,37 +1,43 @@
|
||||||
package sole
|
package sole
|
||||||
|
|
||||||
import "sync"
|
import (
|
||||||
|
"sync/atomic"
|
||||||
var (
|
|
||||||
global int64
|
|
||||||
namespace map[any]int64
|
|
||||||
mutex sync.Mutex
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
var (
|
||||||
namespace = map[any]int64{}
|
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 {
|
func Get() int64 {
|
||||||
global++
|
return global.Add(1)
|
||||||
return global
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Reset 重置全局唯一标识符
|
||||||
|
func Reset() {
|
||||||
|
global.Store(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetWith 获取特定命名空间的唯一标识符
|
||||||
func GetWith(name any) int64 {
|
func GetWith(name any) int64 {
|
||||||
namespace[name]++
|
return namespace[name].Add(1)
|
||||||
return namespace[name]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetSync() int64 {
|
// ResetWith 重置特定命名空间的唯一标识符
|
||||||
mutex.Lock()
|
func ResetWith(name any) {
|
||||||
defer mutex.Unlock()
|
namespace[name].Store(0)
|
||||||
global++
|
|
||||||
return global
|
|
||||||
}
|
|
||||||
|
|
||||||
func GetSyncWith(name any) int64 {
|
|
||||||
mutex.Lock()
|
|
||||||
defer mutex.Unlock()
|
|
||||||
namespace[name]++
|
|
||||||
return namespace[name]
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
||||||
|
}
|
|
@ -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))
|
||||||
|
}
|
|
@ -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
|
|
||||||
}
|
|
|
@ -1,10 +0,0 @@
|
||||||
package storage
|
|
||||||
|
|
||||||
// GlobalDataStorage 全局数据存储器接口
|
|
||||||
type GlobalDataStorage[T any] interface {
|
|
||||||
// Load 加载全局数据
|
|
||||||
// - 当全局数据不存在时,应当返回新的全局数据实例
|
|
||||||
Load(name string) T
|
|
||||||
// Save 保存全局数据
|
|
||||||
Save(name string, data T) error
|
|
||||||
}
|
|
|
@ -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
|
|
||||||
}
|
|
|
@ -1,7 +0,0 @@
|
||||||
package storage
|
|
||||||
|
|
||||||
import "github.com/kercylan98/minotaur/utils/generic"
|
|
||||||
|
|
||||||
type IndexDataItem[I generic.Ordered] interface {
|
|
||||||
GetIndex() I
|
|
||||||
}
|
|
|
@ -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)
|
|
||||||
}
|
|
|
@ -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
|
||||||
|
}
|
|
@ -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, " "))
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,26 +1,11 @@
|
||||||
package storage
|
package storage
|
||||||
|
|
||||||
import "time"
|
import "github.com/kercylan98/minotaur/utils/generic"
|
||||||
|
|
||||||
var (
|
type Storage[PrimaryKey generic.Ordered, Body any] interface {
|
||||||
// globalDataSaveHandles 全局数据保存句柄
|
// Load 加载数据
|
||||||
globalDataSaveHandles []func() error
|
Load(index PrimaryKey) (Body, error)
|
||||||
)
|
|
||||||
|
|
||||||
// SaveAll 保存所有数据
|
// Save 保存数据
|
||||||
// - errorHandle 错误处理中如果返回 false 将重试,否则跳过当前保存下一个
|
Save(set *Set[PrimaryKey, Body], index PrimaryKey, data any) error
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1 +0,0 @@
|
||||||
{"CreateAt":"2023-07-19T14:49:35.7235348+08:00","TotalCount":10}
|
|
|
@ -1 +0,0 @@
|
||||||
{"ID":"INDEX_001","Value":10}
|
|
|
@ -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)
|
|
||||||
}
|
|
|
@ -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
|
|
||||||
}
|
|
|
@ -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
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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)
|
|
||||||
})
|
|
||||||
}
|
|
|
@ -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()))
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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
|
|
||||||
}
|
|
|
@ -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
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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)
|
|
||||||
})
|
|
||||||
}
|
|
|
@ -1,9 +1,8 @@
|
||||||
package stream
|
package stream
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/kercylan98/minotaur/utils/asynchronous"
|
"github.com/kercylan98/minotaur/utils/concurrent"
|
||||||
"github.com/kercylan98/minotaur/utils/hash"
|
"github.com/kercylan98/minotaur/utils/hash"
|
||||||
"github.com/kercylan98/minotaur/utils/synchronization"
|
|
||||||
"reflect"
|
"reflect"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -19,11 +18,6 @@ func WithMapCopy[K comparable, V any](m map[K]V) Map[K, V] {
|
||||||
return hash.Copy(m)
|
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 的链式操作
|
// Map 提供了 map 的链式操作
|
||||||
type Map[K comparable, V any] map[K]V
|
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)
|
return hash.KeyToSlice(slf)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ToSyncMap 将当前 Map 转换为 synchronization.Map
|
// ToSyncMap 将当前 Map 转换为 concurrent.BalanceMap
|
||||||
func (slf Map[K, V]) ToSyncMap() *synchronization.Map[K, V] {
|
func (slf Map[K, V]) ToSyncMap() *concurrent.BalanceMap[K, V] {
|
||||||
return synchronization.NewMap[K, V](synchronization.WithMapSource(slf))
|
return concurrent.NewBalanceMap[K, V](concurrent.WithBalanceMapSource(slf))
|
||||||
}
|
|
||||||
|
|
||||||
// ToAsyncMap 将当前 Map 转换为 asynchronous.Map
|
|
||||||
func (slf Map[K, V]) ToAsyncMap() *asynchronous.Map[K, V] {
|
|
||||||
return asynchronous.NewMap[K, V](asynchronous.WithMapSource(slf))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ToMap 将当前 Map 转换为 map
|
// ToMap 将当前 Map 转换为 map
|
||||||
|
|
|
@ -5,6 +5,7 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
var errorMapper = make(map[error]int)
|
var errorMapper = make(map[error]int)
|
||||||
|
var errorMapperRef = make(map[error]error)
|
||||||
|
|
||||||
// RegError 通过错误码注册错误,返回错误的引用
|
// RegError 通过错误码注册错误,返回错误的引用
|
||||||
func RegError(code int, message string) error {
|
func RegError(code int, message string) error {
|
||||||
|
@ -16,7 +17,21 @@ func RegError(code int, message string) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetErrorCode 通过错误引用获取错误码,如果错误不存在则返回 0
|
// RegErrorRef 通过错误码注册错误,返回错误的引用
|
||||||
func GetErrorCode(err error) int {
|
func RegErrorRef(code int, message string, ref error) error {
|
||||||
return errorMapper[err]
|
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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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))
|
||||||
|
}
|
|
@ -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
|
|
||||||
}
|
|
|
@ -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
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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
|
|
||||||
}
|
|
Loading…
Reference in New Issue