Merge branch 'develop'

This commit is contained in:
kercylan98 2023-07-25 20:01:53 +08:00
commit c5b0fbe2c2
74 changed files with 1634 additions and 2340 deletions

View File

@ -13,12 +13,12 @@ mindmap
root((Minotaur))
/component 通用组件接口定义
/components 通用组件内置实现
/config 针对配置导表的配置加载
/configuration 配置管理功能
/game 游戏通用功能接口定义
/builtin 游戏通用功能内置实现
/notify 通知功能接口定义
/planner 策划相关工具目录
/configexport 配置导表功能实现
/pce 配置导表功能实现
/report 数据埋点及上报功能
/server 网络服务器支持
/cross 内置跨服功能实现
@ -113,7 +113,6 @@ func main() {
```
其他的一些支持事件的结构体(非所有):
- `game.Room` 游戏房间实现
- `synchronization.Map` 并发安全的`Map`实现
- ...
### 可选项
大部分的 `New` 函数均可使用可选项进行创建,具体函数前缀通常为 `With`

View File

@ -3,7 +3,7 @@ package components
import (
"encoding/json"
"github.com/kercylan98/minotaur/component"
"github.com/kercylan98/minotaur/utils/synchronization"
"github.com/kercylan98/minotaur/utils/concurrent"
"github.com/kercylan98/minotaur/utils/timer"
"sync"
"sync/atomic"
@ -13,8 +13,8 @@ import (
// NewLockstep 创建一个锁步(帧)同步默认实现的组件(Lockstep)进行返回
func NewLockstep[ClientID comparable, Command any](options ...LockstepOption[ClientID, Command]) *Lockstep[ClientID, Command] {
lockstep := &Lockstep[ClientID, Command]{
clients: synchronization.NewMap[ClientID, component.LockstepClient[ClientID]](),
frames: synchronization.NewMap[int, []Command](),
clients: concurrent.NewBalanceMap[ClientID, component.LockstepClient[ClientID]](),
frames: concurrent.NewBalanceMap[int, []Command](),
ticker: timer.GetTicker(10),
frameRate: 15,
serialization: func(frame int, commands []Command) []byte {
@ -25,7 +25,7 @@ func NewLockstep[ClientID comparable, Command any](options ...LockstepOption[Cli
data, _ := json.Marshal(frameStruct)
return data
},
clientCurrentFrame: synchronization.NewMap[ClientID, int](),
clientCurrentFrame: concurrent.NewBalanceMap[ClientID, int](),
}
for _, option := range options {
option(lockstep)
@ -40,12 +40,12 @@ func NewLockstep[ClientID comparable, Command any](options ...LockstepOption[Cli
// - 从特定帧开始追帧
// - 兼容各种基于TCP/UDP/Unix的网络类型可通过客户端实现其他网络类型同步
type Lockstep[ClientID comparable, Command any] struct {
clients *synchronization.Map[ClientID, component.LockstepClient[ClientID]] // 接受广播的客户端
frames *synchronization.Map[int, []Command] // 所有帧指令
ticker *timer.Ticker // 定时器
frameMutex sync.Mutex // 帧锁
currentFrame int // 当前帧
clientCurrentFrame *synchronization.Map[ClientID, int] // 客户端当前帧数
clients *concurrent.BalanceMap[ClientID, component.LockstepClient[ClientID]] // 接受广播的客户端
frames *concurrent.BalanceMap[int, []Command] // 所有帧指令
ticker *timer.Ticker // 定时器
frameMutex sync.Mutex // 帧锁
currentFrame int // 当前帧
clientCurrentFrame *concurrent.BalanceMap[ClientID, int] // 客户端当前帧数
running atomic.Bool
frameRate int // 帧率每秒N帧
@ -123,8 +123,8 @@ func (slf *Lockstep[ClientID, Command]) StopBroadcast() {
// AddCommand 添加命令到当前帧
func (slf *Lockstep[ClientID, Command]) AddCommand(command Command) {
slf.frames.AtomGetSet(slf.currentFrame, func(value []Command, exist bool) (newValue []Command, isSet bool) {
return append(value, command), true
slf.frames.Atom(func(m map[int][]Command) {
m[slf.currentFrame] = append(m[slf.currentFrame], command)
})
}

View File

@ -2,18 +2,18 @@ package builtin
import (
"github.com/kercylan98/minotaur/game"
"github.com/kercylan98/minotaur/utils/concurrent"
"github.com/kercylan98/minotaur/utils/huge"
"github.com/kercylan98/minotaur/utils/synchronization"
)
func NewAttrs() *Attrs {
return &Attrs{
attrs: synchronization.NewMap[int, any](),
attrs: concurrent.NewBalanceMap[int, any](),
}
}
type Attrs struct {
attrs *synchronization.Map[int, any]
attrs *concurrent.BalanceMap[int, any]
attrChangeEventHandles []game.AttrChangeEventHandle
attrIdChangeEventHandles map[int][]game.AttrChangeEventHandle

View File

@ -3,15 +3,15 @@ package builtin
import (
"encoding/json"
"github.com/kercylan98/minotaur/game"
"github.com/kercylan98/minotaur/utils/concurrent"
"github.com/kercylan98/minotaur/utils/generic"
"github.com/kercylan98/minotaur/utils/synchronization"
)
// NewRankingList 创建一个排名从0开始的排行榜
func NewRankingList[CompetitorID comparable, Score generic.Ordered](options ...RankingListOption[CompetitorID, Score]) *RankingList[CompetitorID, Score] {
rankingList := &RankingList[CompetitorID, Score]{
rankCount: 100,
competitors: synchronization.NewMap[CompetitorID, Score](),
competitors: concurrent.NewBalanceMap[CompetitorID, Score](),
}
for _, option := range options {
option(rankingList)
@ -22,7 +22,7 @@ func NewRankingList[CompetitorID comparable, Score generic.Ordered](options ...R
type RankingList[CompetitorID comparable, Score generic.Ordered] struct {
asc bool
rankCount int
competitors *synchronization.Map[CompetitorID, Score]
competitors *concurrent.BalanceMap[CompetitorID, Score]
scores []*scoreItem[CompetitorID, Score] // CompetitorID, Score
rankChangeEventHandles []game.RankChangeEventHandle[CompetitorID, Score]
@ -244,11 +244,11 @@ func (slf *RankingList[CompetitorID, Score]) competitor(competitorId CompetitorI
func (slf *RankingList[CompetitorID, Score]) UnmarshalJSON(bytes []byte) error {
var t struct {
Competitors *synchronization.Map[CompetitorID, Score] `json:"competitors,omitempty"`
Scores []*scoreItem[CompetitorID, Score] `json:"scores,omitempty"`
Asc bool `json:"asc,omitempty"`
Competitors *concurrent.BalanceMap[CompetitorID, Score] `json:"competitors,omitempty"`
Scores []*scoreItem[CompetitorID, Score] `json:"scores,omitempty"`
Asc bool `json:"asc,omitempty"`
}
t.Competitors = synchronization.NewMap[CompetitorID, Score]()
t.Competitors = concurrent.NewBalanceMap[CompetitorID, Score]()
if err := json.Unmarshal(bytes, &t); err != nil {
return err
}
@ -260,9 +260,9 @@ func (slf *RankingList[CompetitorID, Score]) UnmarshalJSON(bytes []byte) error {
func (slf *RankingList[CompetitorID, Score]) MarshalJSON() ([]byte, error) {
var t struct {
Competitors *synchronization.Map[CompetitorID, Score] `json:"competitors,omitempty"`
Scores []*scoreItem[CompetitorID, Score] `json:"scores,omitempty"`
Asc bool `json:"asc,omitempty"`
Competitors *concurrent.BalanceMap[CompetitorID, Score] `json:"competitors,omitempty"`
Scores []*scoreItem[CompetitorID, Score] `json:"scores,omitempty"`
Asc bool `json:"asc,omitempty"`
}
t.Competitors = slf.competitors
t.Scores = slf.scores

View File

@ -2,8 +2,7 @@ package builtin
import (
"github.com/kercylan98/minotaur/game"
"github.com/kercylan98/minotaur/utils/asynchronous"
"github.com/kercylan98/minotaur/utils/hash"
"github.com/kercylan98/minotaur/utils/concurrent"
"github.com/kercylan98/minotaur/utils/log"
)
@ -11,7 +10,7 @@ import (
func NewRoom[PlayerID comparable, Player game.Player[PlayerID]](guid int64, options ...RoomOption[PlayerID, Player]) *Room[PlayerID, Player] {
room := &Room[PlayerID, Player]{
guid: guid,
players: asynchronous.NewMap[PlayerID, Player](),
players: concurrent.NewBalanceMap[PlayerID, Player](),
}
for _, option := range options {
option(room)
@ -27,7 +26,7 @@ type Room[PlayerID comparable, Player game.Player[PlayerID]] struct {
owner PlayerID
noMaster bool
playerLimit int
players hash.Map[PlayerID, Player]
players *concurrent.BalanceMap[PlayerID, Player]
kickCheckHandle func(room *Room[PlayerID, Player], id, target PlayerID) error
playerJoinRoomEventHandles []game.PlayerJoinRoomEventHandle[PlayerID, Player]
@ -51,8 +50,8 @@ func (slf *Room[PlayerID, Player]) GetPlayer(id PlayerID) Player {
}
// GetPlayers 获取所有玩家
func (slf *Room[PlayerID, Player]) GetPlayers() hash.MapReadonly[PlayerID, Player] {
return slf.players.(hash.MapReadonly[PlayerID, Player])
func (slf *Room[PlayerID, Player]) GetPlayers() map[PlayerID]Player {
return slf.players.Map()
}
// GetPlayerCount 获取玩家数量

View File

@ -2,20 +2,20 @@ package builtin
import (
"github.com/kercylan98/minotaur/game"
"github.com/kercylan98/minotaur/utils/synchronization"
"github.com/kercylan98/minotaur/utils/concurrent"
"sync/atomic"
)
func NewRoomManager[PlayerID comparable, Room game.Room[PlayerID, game.Player[PlayerID]]]() *RoomManager[PlayerID, Room] {
return &RoomManager[PlayerID, Room]{
rooms: synchronization.NewMap[int64, Room](),
rooms: concurrent.NewBalanceMap[int64, Room](),
}
}
// RoomManager 房间管理器
type RoomManager[PlayerID comparable, Room game.Room[PlayerID, game.Player[PlayerID]]] struct {
guid atomic.Int64
rooms *synchronization.Map[int64, Room]
rooms *concurrent.BalanceMap[int64, Room]
}
// GenGuid 生成一个新的房间guid

View File

@ -2,7 +2,7 @@ package builtin
import (
"github.com/kercylan98/minotaur/game"
"github.com/kercylan98/minotaur/utils/synchronization"
"github.com/kercylan98/minotaur/utils/concurrent"
)
// RoomOption 房间构建可选项
@ -11,7 +11,7 @@ type RoomOption[PlayerID comparable, Player game.Player[PlayerID]] func(room *Ro
// WithRoomSync 通过线程安全的方式创建房间
func WithRoomSync[PlayerID comparable, Player game.Player[PlayerID]]() RoomOption[PlayerID, Player] {
return func(room *Room[PlayerID, Player]) {
room.players = synchronization.NewMap[PlayerID, Player]()
room.players = concurrent.NewBalanceMap[PlayerID, Player]()
}
}

View File

@ -2,7 +2,7 @@ package builtin
import (
"github.com/kercylan98/minotaur/game"
"github.com/kercylan98/minotaur/utils/asynchronous"
"github.com/kercylan98/minotaur/utils/concurrent"
"github.com/kercylan98/minotaur/utils/hash"
"github.com/kercylan98/minotaur/utils/slice"
"sync"
@ -12,7 +12,7 @@ import (
func NewRoomSeat[PlayerID comparable, Player game.Player[PlayerID]](room game.Room[PlayerID, Player], options ...RoomSeatOption[PlayerID, Player]) *RoomSeat[PlayerID, Player] {
roomSeat := &RoomSeat[PlayerID, Player]{
Room: room,
seatPS: asynchronous.NewMap[PlayerID, int](),
seatPS: concurrent.NewBalanceMap[PlayerID, int](),
}
for _, option := range options {
option(roomSeat)
@ -26,7 +26,7 @@ type RoomSeat[PlayerID comparable, Player game.Player[PlayerID]] struct {
game.Room[PlayerID, Player]
mutex sync.RWMutex
vacancy []int
seatPS hash.Map[PlayerID, int]
seatPS *concurrent.BalanceMap[PlayerID, int]
seatSP []*PlayerID
duplicateLock bool
fillIn bool
@ -240,6 +240,41 @@ func (slf *RoomSeat[PlayerID, Player]) GetNextSeatVacancy(seat int) int {
return seat
}
// GetPrevSeat 获取特定座位号上一个未缺席的座位号
func (slf *RoomSeat[PlayerID, Player]) GetPrevSeat(seat int) int {
l := len(slf.seatSP)
if l == 0 || seat >= l || seat < 0 {
return -1
}
var target = seat
for {
target--
if target < 0 {
target = l - 1
}
if target == seat {
return seat
}
if slf.seatSP[target] != nil {
return target
}
}
}
// GetPrevSeatVacancy 获取特定座位号上一个座位号
// - 缺席将不会被忽略
func (slf *RoomSeat[PlayerID, Player]) GetPrevSeatVacancy(seat int) int {
l := len(slf.seatSP)
if l == 0 || seat >= l || seat < 0 {
return -1
}
seat--
if seat < 0 {
seat = l - 1
}
return seat
}
func (slf *RoomSeat[PlayerID, Player]) onJoinRoom(room game.Room[PlayerID, Player], player Player) {
slf.AddSeat(player.GetID())
}

View File

@ -2,9 +2,8 @@ package builtin
import (
"github.com/kercylan98/minotaur/game"
"github.com/kercylan98/minotaur/utils/hash"
"github.com/kercylan98/minotaur/utils/concurrent"
"github.com/kercylan98/minotaur/utils/log"
"github.com/kercylan98/minotaur/utils/synchronization"
"sync/atomic"
)
@ -12,10 +11,10 @@ import (
func NewWorld[PlayerID comparable, Player game.Player[PlayerID]](guid int64, options ...WorldOption[PlayerID, Player]) *World[PlayerID, Player] {
world := &World[PlayerID, Player]{
guid: guid,
players: synchronization.NewMap[PlayerID, Player](),
playerActors: synchronization.NewMap[PlayerID, hash.Map[int64, game.Actor]](),
owners: synchronization.NewMap[int64, PlayerID](),
actors: synchronization.NewMap[int64, game.Actor](),
players: concurrent.NewBalanceMap[PlayerID, Player](),
playerActors: concurrent.NewBalanceMap[PlayerID, *concurrent.BalanceMap[int64, game.Actor]](),
owners: concurrent.NewBalanceMap[int64, PlayerID](),
actors: concurrent.NewBalanceMap[int64, game.Actor](),
}
for _, option := range options {
option(world)
@ -28,10 +27,10 @@ type World[PlayerID comparable, Player game.Player[PlayerID]] struct {
guid int64
actorGuid atomic.Int64
playerLimit int
players hash.Map[PlayerID, Player]
playerActors hash.Map[PlayerID, hash.Map[int64, game.Actor]]
owners hash.Map[int64, PlayerID]
actors hash.Map[int64, game.Actor]
players *concurrent.BalanceMap[PlayerID, Player]
playerActors *concurrent.BalanceMap[PlayerID, *concurrent.BalanceMap[int64, game.Actor]]
owners *concurrent.BalanceMap[int64, PlayerID]
actors *concurrent.BalanceMap[int64, game.Actor]
playerJoinWorldEventHandles []game.PlayerJoinWorldEventHandle[PlayerID, Player]
playerLeaveWorldEventHandles []game.PlayerLeaveWorldEventHandle[PlayerID, Player]
@ -56,16 +55,16 @@ func (slf *World[PlayerID, Player]) GetPlayer(id PlayerID) Player {
return slf.players.Get(id)
}
func (slf *World[PlayerID, Player]) GetPlayers() hash.MapReadonly[PlayerID, Player] {
return slf.players.(hash.MapReadonly[PlayerID, Player])
func (slf *World[PlayerID, Player]) GetPlayers() map[PlayerID]Player {
return slf.players.Map()
}
func (slf *World[PlayerID, Player]) GetActor(guid int64) game.Actor {
return slf.actors.Get(guid)
}
func (slf *World[PlayerID, Player]) GetActors() hash.MapReadonly[int64, game.Actor] {
return slf.actors.(hash.MapReadonly[int64, game.Actor])
func (slf *World[PlayerID, Player]) GetActors() map[int64]game.Actor {
return slf.actors.Map()
}
func (slf *World[PlayerID, Player]) GetPlayerActor(id PlayerID, guid int64) game.Actor {
@ -75,8 +74,8 @@ func (slf *World[PlayerID, Player]) GetPlayerActor(id PlayerID, guid int64) game
return nil
}
func (slf *World[PlayerID, Player]) GetPlayerActors(id PlayerID) hash.MapReadonly[int64, game.Actor] {
return slf.playerActors.Get(id).(hash.MapReadonly[int64, game.Actor])
func (slf *World[PlayerID, Player]) GetPlayerActors(id PlayerID) map[int64]game.Actor {
return slf.playerActors.Get(id).Map()
}
func (slf *World[PlayerID, Player]) IsExistPlayer(id PlayerID) bool {
@ -105,7 +104,7 @@ func (slf *World[PlayerID, Player]) Join(player Player) error {
log.Debug("World.Join", log.Int64("guid", slf.GetGuid()), log.Any("player", player.GetID()))
slf.players.Set(player.GetID(), player)
if actors := slf.playerActors.Get(player.GetID()); actors == nil {
actors = synchronization.NewMap[int64, game.Actor]()
actors = concurrent.NewBalanceMap[int64, game.Actor]()
slf.playerActors.Set(player.GetID(), actors)
}
slf.OnPlayerJoinWorldEvent(player)
@ -119,9 +118,10 @@ func (slf *World[PlayerID, Player]) Leave(id PlayerID) {
}
log.Debug("World.Leave", log.Int64("guid", slf.GetGuid()), log.Any("player", player.GetID()))
slf.OnPlayerLeaveWorldEvent(player)
slf.playerActors.Get(player.GetID()).Range(func(guid int64, actor game.Actor) {
slf.playerActors.Get(player.GetID()).Range(func(guid int64, actor game.Actor) bool {
slf.OnActorAnnihilationEvent(actor)
slf.owners.Delete(guid)
return false
})
slf.playerActors.Delete(player.GetID())
slf.players.Delete(player.GetID())
@ -171,8 +171,9 @@ func (slf *World[PlayerID, Player]) RemoveActorOwner(guid int64) {
func (slf *World[PlayerID, Player]) Reset() {
log.Debug("World", log.Int64("Reset", slf.guid))
slf.players.Clear()
slf.playerActors.Range(func(id PlayerID, actors hash.Map[int64, game.Actor]) {
slf.playerActors.Range(func(id PlayerID, actors *concurrent.BalanceMap[int64, game.Actor]) bool {
actors.Clear()
return false
})
slf.playerActors.Clear()
slf.owners.Clear()

View File

@ -1,159 +1,13 @@
package poker
import (
"fmt"
"math"
)
// NewCard 创建一张扑克牌
// - 当 point 为 PointBlackJoker 或 PointRedJoker 时color 将没有效果
func NewCard(point Point, color Color) Card {
if point == PointRedJoker || point == PointBlackJoker {
color = ColorNone
}
card := Card{
point: point,
color: color,
}
return card
}
import "github.com/kercylan98/minotaur/utils/generic"
// Card 扑克牌
type Card struct {
point Point
color Color
}
// GetPoint 返回扑克牌的点数
func (slf Card) GetPoint() Point {
return slf.point
}
// GetColor 返回扑克牌的花色
func (slf Card) GetColor() Color {
if slf.point == PointRedJoker || slf.point == PointBlackJoker {
return ColorNone
}
return slf.color
}
// GetPointAndColor 返回扑克牌的点数和花色
func (slf Card) GetPointAndColor() (Point, Color) {
return slf.GetPoint(), slf.GetColor()
}
// EqualPoint 比较与另一张扑克牌的点数是否相同
func (slf Card) EqualPoint(card Card) bool {
return slf.GetPoint() == card.GetPoint()
}
// EqualColor 比较与另一张扑克牌的花色是否相同
func (slf Card) EqualColor(card Card) bool {
return slf.GetColor() == card.GetColor()
}
// Equal 比较与另一张扑克牌的点数和花色是否相同
func (slf Card) Equal(card Card) bool {
return slf.GetPoint() == card.GetPoint() && slf.GetColor() == card.GetColor()
}
// MaxPoint 返回两张扑克牌中点数较大的一张
func (slf Card) MaxPoint(card Card) Card {
if slf.GetPoint() > card.GetPoint() {
return slf
}
return card
}
// MinPoint 返回两张扑克牌中点数较小的一张
func (slf Card) MinPoint(card Card) Card {
if slf.GetPoint() < card.GetPoint() {
return slf
}
return card
}
// MaxColor 返回两张扑克牌中花色较大的一张
func (slf Card) MaxColor(card Card) Card {
if slf.GetColor() > card.GetColor() {
return slf
}
return card
}
// MinColor 返回两张扑克牌中花色较小的一张
func (slf Card) MinColor(card Card) Card {
if slf.GetColor() < card.GetColor() {
return slf
}
return card
}
// Max 返回两张扑克牌中点数和花色较大的一张
func (slf Card) Max(card Card) Card {
if slf.GetPoint() > card.GetPoint() {
return slf
} else if slf.GetPoint() < card.GetPoint() {
return card
} else {
if slf.GetColor() > card.GetColor() {
return slf
}
return card
}
}
// Min 返回两张扑克牌中点数和花色较小的一张
func (slf Card) Min(card Card) Card {
if slf.GetPoint() < card.GetPoint() {
return slf
} else if slf.GetPoint() > card.GetPoint() {
return card
} else {
if slf.GetColor() < card.GetColor() {
return slf
}
return card
}
}
// IsJoker 判断是否为大小王
func (slf Card) IsJoker() bool {
point := slf.GetPoint()
return point == PointRedJoker || point == PointBlackJoker
}
// CalcPointDifference 计算两张扑克牌的点数差
func (slf Card) CalcPointDifference(card Card) int {
return int(slf.GetPoint()) - int(card.GetPoint())
}
// CalcPointDifferenceAbs 计算两张扑克牌的点数差的绝对值
func (slf Card) CalcPointDifferenceAbs(card Card) int {
return int(math.Abs(float64(slf.CalcPointDifference(card))))
}
// CalcColorDifference 计算两张扑克牌的花色差
func (slf Card) CalcColorDifference(card Card) int {
return int(slf.GetColor()) - int(card.GetColor())
}
// CalcColorDifferenceAbs 计算两张扑克牌的花色差的绝对值
func (slf Card) CalcColorDifferenceAbs(card Card) int {
return int(math.Abs(float64(slf.CalcColorDifference(card))))
}
// IsNeighborPoint 判断两张扑克牌是否为相邻的点数
func (slf Card) IsNeighborPoint(card Card) bool {
return slf.CalcPointDifferenceAbs(card) == 1
}
// IsNeighborColor 判断两张扑克牌是否为相邻的花色
func (slf Card) IsNeighborColor(card Card) bool {
return slf.CalcColorDifferenceAbs(card) == 1
}
// String 将扑克牌转换为字符串形式
func (slf Card) String() string {
return fmt.Sprintf("(%s %s)", slf.point, slf.color)
type Card[P, C generic.Number] interface {
// GetGuid 获取扑克牌的唯一标识
GetGuid() int64
// GetPoint 获取扑克牌的点数
GetPoint() P
// GetColor 获取扑克牌的花色
GetColor() C
}

View File

@ -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
}

View File

@ -2,6 +2,7 @@ package poker
import (
"fmt"
"github.com/kercylan98/minotaur/utils/generic"
"github.com/kercylan98/minotaur/utils/hash"
"github.com/kercylan98/minotaur/utils/random"
"github.com/kercylan98/minotaur/utils/slice"
@ -10,12 +11,17 @@ import (
// NewCardPile 返回一个新的牌堆,其中 size 表示了该牌堆由多少副牌组成
// - 在不洗牌的情况下,默认牌堆顶部到底部为从大到小排列
func NewCardPile(size int, options ...CardPileOption) *CardPile {
pile := &CardPile{
size: size,
pile: make([]Card, 0, size*54),
func NewCardPile[P, C generic.Number, T Card[P, C]](size int, jokers [2]P, points [13]P, colors [4]C, generateCard func(guid int64, point P, color C) T, options ...CardPileOption[P, C, T]) *CardPile[P, C, T] {
pile := &CardPile[P, C, T]{
size: size,
pile: make([]T, 0, size*54),
generateCard: generateCard,
cards: map[int64]T{},
jokers: jokers,
points: points,
colors: colors,
}
pile.shuffleHandle = func(cards []Card) []Card {
pile.shuffleHandle = func(cards []T) []T {
sort.Slice(cards, func(i, j int) bool {
return random.Float64() >= 0.5
})
@ -29,28 +35,47 @@ func NewCardPile(size int, options ...CardPileOption) *CardPile {
}
// CardPile 扑克牌堆
type CardPile struct {
pile []Card
type CardPile[P, C generic.Number, T Card[P, C]] struct {
pile []T
size int
shuffleHandle func(cards []Card) []Card
excludeColor map[Color]struct{}
excludePoint map[Point]struct{}
excludeCard map[Point]map[Color]struct{}
shuffleHandle func(cards []T) []T
excludeColor map[C]struct{}
excludePoint map[P]struct{}
excludeCard map[P]map[C]struct{}
generateCard func(guid int64, point P, color C) T
guid int64
cards map[int64]T
jokers [2]P
points [13]P
colors [4]C
}
// GetCard 通过 guid 获取一张牌
func (slf *CardPile[P, C, T]) GetCard(guid int64) T {
return slf.cards[guid]
}
// Reset 重置牌堆的扑克牌数量及顺序
func (slf *CardPile) Reset() {
var cards = make([]Card, 0, 54)
if !slf.IsExclude(PointRedJoker, ColorNone) {
cards = append(cards, NewCard(PointRedJoker, ColorNone))
}
if !slf.IsExclude(PointBlackJoker, ColorNone) {
cards = append(cards, NewCard(PointBlackJoker, ColorNone))
}
for point := PointK; point >= PointA; point-- {
for color := ColorDiamond; color <= ColorSpade; color++ {
if !slf.IsExclude(point, color) {
cards = append(cards, NewCard(point, color))
func (slf *CardPile[P, C, T]) Reset() {
var cards = make([]T, 0, 54*slf.size)
for i := 0; i < slf.size; i++ {
for _, joker := range slf.jokers {
if !slf.IsExclude(joker, C(0)) {
slf.guid++
card := slf.generateCard(slf.guid, joker, C(0))
slf.cards[slf.guid] = card
cards = append(cards, card)
}
}
for p := 0; p < len(slf.points); p++ {
for c := 0; c < len(slf.colors); c++ {
point, color := slf.points[p], slf.colors[c]
if !slf.IsExclude(point, color) {
slf.guid++
card := slf.generateCard(slf.guid, point, color)
slf.cards[slf.guid] = card
}
}
}
}
@ -61,21 +86,18 @@ func (slf *CardPile) Reset() {
}
// IsExclude 检查特定点数和花色是否被排除在外
func (slf *CardPile) IsExclude(point Point, color Color) bool {
if point == PointRedJoker || point == PointBlackJoker {
color = ColorNone
}
func (slf *CardPile[P, C, T]) IsExclude(point P, color C) bool {
return hash.Exist(slf.excludePoint, point) || hash.Exist(slf.excludeColor, color) || hash.Exist(slf.excludeCard[point], color)
}
// IsExcludeWithCard 检查特定扑克牌是否被排除在外
func (slf *CardPile) IsExcludeWithCard(card Card) bool {
point, color := card.GetPointAndColor()
func (slf *CardPile[P, C, T]) IsExcludeWithCard(card T) bool {
point, color := GetPointAndColor[P, C, T](card)
return hash.Exist(slf.excludePoint, point) || hash.Exist(slf.excludeColor, color) || hash.Exist(slf.excludeCard[point], color)
}
// Shuffle 洗牌
func (slf *CardPile) Shuffle() {
func (slf *CardPile[P, C, T]) Shuffle() {
before := slf.Count()
cards := slf.shuffleHandle(slf.Cards())
if len(cards) != before {
@ -85,22 +107,22 @@ func (slf *CardPile) Shuffle() {
}
// Cards 获取当前牌堆的所有扑克牌
func (slf *CardPile) Cards() []Card {
func (slf *CardPile[P, C, T]) Cards() []T {
return slf.pile
}
// IsFree 返回牌堆是否没有扑克牌了
func (slf *CardPile) IsFree() bool {
func (slf *CardPile[P, C, T]) IsFree() bool {
return len(slf.pile) == 0
}
// Count 获取牌堆剩余牌量
func (slf *CardPile) Count() int {
func (slf *CardPile[P, C, T]) Count() int {
return len(slf.pile)
}
// Pull 从牌堆特定位置抽出一张牌
func (slf *CardPile) Pull(index int) Card {
func (slf *CardPile[P, C, T]) Pull(index int) T {
if index >= slf.Count() || index < 0 {
panic(fmt.Errorf("failed to pull a poker card from the pile, the index is less than 0 or exceeds the remaining number of cards in the pile. count: %d, index: %d", slf.Count(), index))
}
@ -110,7 +132,7 @@ func (slf *CardPile) Pull(index int) Card {
}
// PullTop 从牌堆顶部抽出一张牌
func (slf *CardPile) PullTop() Card {
func (slf *CardPile[P, C, T]) PullTop() T {
if slf.IsFree() {
panic("empty poker cards pile")
}
@ -120,7 +142,7 @@ func (slf *CardPile) PullTop() Card {
}
// PullBottom 从牌堆底部抽出一张牌
func (slf *CardPile) PullBottom() Card {
func (slf *CardPile[P, C, T]) PullBottom() T {
if slf.IsFree() {
panic("empty poker cards pile")
}
@ -131,17 +153,17 @@ func (slf *CardPile) PullBottom() Card {
}
// Push 将扑克牌插入到牌堆特定位置
func (slf *CardPile) Push(index int, card Card) {
func (slf *CardPile[P, C, T]) Push(index int, card T) {
slice.Insert(&slf.pile, index, card)
return
}
// PushTop 将扑克牌插入到牌堆顶部
func (slf *CardPile) PushTop(card Card) {
slf.pile = append([]Card{card}, slf.pile...)
func (slf *CardPile[P, C, T]) PushTop(card T) {
slf.pile = append([]T{card}, slf.pile...)
}
// PushBottom 将扑克牌插入到牌堆底部
func (slf *CardPile) PushBottom(card Card) {
func (slf *CardPile[P, C, T]) PushBottom(card T) {
slf.pile = append(slf.pile, card)
}

View File

@ -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)]
}

View File

@ -1,11 +1,13 @@
package poker
type CardPileOption func(pile *CardPile)
import "github.com/kercylan98/minotaur/utils/generic"
type CardPileOption[P, C generic.Number, T Card[P, C]] func(pile *CardPile[P, C, T])
// WithCardPileShuffle 通过特定的洗牌算法创建牌堆
// - 需要保证洗牌后的牌堆剩余扑克数量与之前相同,否则将会引发 panic
func WithCardPileShuffle(shuffleHandle func(pile []Card) []Card) CardPileOption {
return func(pile *CardPile) {
func WithCardPileShuffle[P, C generic.Number, T Card[P, C]](shuffleHandle func(pile []T) []T) CardPileOption[P, C, T] {
return func(pile *CardPile[P, C, T]) {
if shuffleHandle == nil {
return
}
@ -14,10 +16,10 @@ func WithCardPileShuffle(shuffleHandle func(pile []Card) []Card) CardPileOption
}
// WithCardPileExcludeColor 通过排除特定花色的方式创建牌堆
func WithCardPileExcludeColor(colors ...Color) CardPileOption {
return func(pile *CardPile) {
func WithCardPileExcludeColor[P, C generic.Number, T Card[P, C]](colors ...C) CardPileOption[P, C, T] {
return func(pile *CardPile[P, C, T]) {
if pile.excludeColor == nil {
pile.excludeColor = map[Color]struct{}{}
pile.excludeColor = map[C]struct{}{}
}
for _, color := range colors {
pile.excludeColor[color] = struct{}{}
@ -26,10 +28,10 @@ func WithCardPileExcludeColor(colors ...Color) CardPileOption {
}
// WithCardPileExcludePoint 通过排除特定点数的方式创建牌堆
func WithCardPileExcludePoint(points ...Point) CardPileOption {
return func(pile *CardPile) {
func WithCardPileExcludePoint[P, C generic.Number, T Card[P, C]](points ...P) CardPileOption[P, C, T] {
return func(pile *CardPile[P, C, T]) {
if pile.excludePoint == nil {
pile.excludePoint = map[Point]struct{}{}
pile.excludePoint = map[P]struct{}{}
}
for _, point := range points {
pile.excludePoint[point] = struct{}{}
@ -38,22 +40,22 @@ func WithCardPileExcludePoint(points ...Point) CardPileOption {
}
// WithCardPileExcludeCard 通过排除特定扑克牌的方式创建牌堆
func WithCardPileExcludeCard(cards ...Card) CardPileOption {
return func(pile *CardPile) {
func WithCardPileExcludeCard[P, C generic.Number, T Card[P, C]](cards ...Card[P, C]) CardPileOption[P, C, T] {
return func(pile *CardPile[P, C, T]) {
if pile.excludeCard == nil {
pile.excludeCard = map[Point]map[Color]struct{}{}
pile.excludeCard = map[P]map[C]struct{}{}
}
for _, card := range cards {
point := card.GetPoint()
cs, exist := pile.excludeCard[point]
if !exist {
cs = map[Color]struct{}{}
cs = map[C]struct{}{}
pile.excludeCard[point] = cs
}
if point == PointRedJoker || point == PointBlackJoker {
cs[ColorNone] = struct{}{}
} else {
cs[card.GetColor()] = struct{}{}
for _, joker := range pile.jokers {
if point != joker {
cs[card.GetColor()] = struct{}{}
}
}
}
}

View File

@ -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)
})
}

View File

@ -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
}

View File

@ -1,37 +1,39 @@
package poker
import "github.com/kercylan98/minotaur/utils/generic"
const (
HandNone = "None" // 无牌型
)
// HandHandle 扑克牌型验证函数
type HandHandle func(rule *Rule, cards []Card) bool
type HandHandle[P, C generic.Number, T Card[P, C]] func(rule *Rule[P, C, T], cards []T) bool
// HandSingle 单牌
func HandSingle() HandHandle {
return func(rule *Rule, cards []Card) bool {
func HandSingle[P, C generic.Number, T Card[P, C]]() HandHandle[P, C, T] {
return func(rule *Rule[P, C, T], cards []T) bool {
return len(cards) == 1
}
}
// HandPairs 对子
func HandPairs() HandHandle {
return func(rule *Rule, cards []Card) bool {
func HandPairs[P, C generic.Number, T Card[P, C]]() HandHandle[P, C, T] {
return func(rule *Rule[P, C, T], cards []T) bool {
return len(cards) == 2 && rule.IsPointContinuity(2, cards...)
}
}
// HandThreeOfKind 三张
func HandThreeOfKind() HandHandle {
return func(rule *Rule, cards []Card) bool {
func HandThreeOfKind[P, C generic.Number, T Card[P, C]]() HandHandle[P, C, T] {
return func(rule *Rule[P, C, T], cards []T) bool {
return len(cards) == 3 && rule.IsPointContinuity(3, cards...)
}
}
// HandThreeOfKindWithOne 三带一
func HandThreeOfKindWithOne() HandHandle {
return func(rule *Rule, cards []Card) bool {
group := GroupByPoint(cards...)
func HandThreeOfKindWithOne[P, C generic.Number, T Card[P, C]]() HandHandle[P, C, T] {
return func(rule *Rule[P, C, T], cards []T) bool {
group := GroupByPoint[P, C, T](cards...)
if len(group) != 2 {
return false
}
@ -49,9 +51,9 @@ func HandThreeOfKindWithOne() HandHandle {
}
// HandThreeOfKindWithTwo 三带二
func HandThreeOfKindWithTwo() HandHandle {
return func(rule *Rule, cards []Card) bool {
group := GroupByPoint(cards...)
func HandThreeOfKindWithTwo[P, C generic.Number, T Card[P, C]]() HandHandle[P, C, T] {
return func(rule *Rule[P, C, T], cards []T) bool {
group := GroupByPoint[P, C, T](cards...)
if len(group) != 2 {
return false
}
@ -69,15 +71,15 @@ func HandThreeOfKindWithTwo() HandHandle {
}
// HandOrderSingle 顺子
func HandOrderSingle(count int) HandHandle {
return func(rule *Rule, cards []Card) bool {
func HandOrderSingle[P, C generic.Number, T Card[P, C]](count int) HandHandle[P, C, T] {
return func(rule *Rule[P, C, T], cards []T) bool {
return len(cards) >= count && rule.IsPointContinuity(1, cards...)
}
}
// HandOrderPairs 对子顺子
func HandOrderPairs(count int) HandHandle {
return func(rule *Rule, cards []Card) bool {
func HandOrderPairs[P, C generic.Number, T Card[P, C]](count int) HandHandle[P, C, T] {
return func(rule *Rule[P, C, T], cards []T) bool {
if len(cards) < count*2 || len(cards)%2 != 0 {
return false
}
@ -86,8 +88,8 @@ func HandOrderPairs(count int) HandHandle {
}
// HandOrderSingleThree 三张顺子
func HandOrderSingleThree(count int) HandHandle {
return func(rule *Rule, cards []Card) bool {
func HandOrderSingleThree[P, C generic.Number, T Card[P, C]](count int) HandHandle[P, C, T] {
return func(rule *Rule[P, C, T], cards []T) bool {
if len(cards) < count*3 || len(cards)%3 != 0 {
return false
}
@ -96,8 +98,8 @@ func HandOrderSingleThree(count int) HandHandle {
}
// HandOrderSingleFour 四张顺子
func HandOrderSingleFour(count int) HandHandle {
return func(rule *Rule, cards []Card) bool {
func HandOrderSingleFour[P, C generic.Number, T Card[P, C]](count int) HandHandle[P, C, T] {
return func(rule *Rule[P, C, T], cards []T) bool {
if len(cards) < count*4 || len(cards)%4 != 0 {
return false
}
@ -106,10 +108,10 @@ func HandOrderSingleFour(count int) HandHandle {
}
// HandOrderThreeWithOne 三带一顺子
func HandOrderThreeWithOne(count int) HandHandle {
return func(rule *Rule, cards []Card) bool {
group := GroupByPoint(cards...)
var continuous []Card
func HandOrderThreeWithOne[P, C generic.Number, T Card[P, C]](count int) HandHandle[P, C, T] {
return func(rule *Rule[P, C, T], cards []T) bool {
group := GroupByPoint[P, C, T](cards...)
var continuous []T
var other int
for _, cards := range group {
if len(cards) == 3 {
@ -126,10 +128,10 @@ func HandOrderThreeWithOne(count int) HandHandle {
}
// HandOrderThreeWithTwo 三带二顺子
func HandOrderThreeWithTwo(count int) HandHandle {
return func(rule *Rule, cards []Card) bool {
group := GroupByPoint(cards...)
var continuous []Card
func HandOrderThreeWithTwo[P, C generic.Number, T Card[P, C]](count int) HandHandle[P, C, T] {
return func(rule *Rule[P, C, T], cards []T) bool {
group := GroupByPoint[P, C, T](cards...)
var continuous []T
var other int
for _, cards := range group {
if len(cards) == 3 {
@ -148,10 +150,10 @@ func HandOrderThreeWithTwo(count int) HandHandle {
}
// HandOrderFourWithOne 四带一顺子
func HandOrderFourWithOne(count int) HandHandle {
return func(rule *Rule, cards []Card) bool {
group := GroupByPoint(cards...)
var continuous []Card
func HandOrderFourWithOne[P, C generic.Number, T Card[P, C]](count int) HandHandle[P, C, T] {
return func(rule *Rule[P, C, T], cards []T) bool {
group := GroupByPoint[P, C, T](cards...)
var continuous []T
var other int
for _, cards := range group {
if len(cards) == 4 {
@ -168,10 +170,10 @@ func HandOrderFourWithOne(count int) HandHandle {
}
// HandOrderFourWithTwo 四带二顺子
func HandOrderFourWithTwo(count int) HandHandle {
return func(rule *Rule, cards []Card) bool {
group := GroupByPoint(cards...)
var continuous []Card
func HandOrderFourWithTwo[P, C generic.Number, T Card[P, C]](count int) HandHandle[P, C, T] {
return func(rule *Rule[P, C, T], cards []T) bool {
group := GroupByPoint[P, C, T](cards...)
var continuous []T
var other int
for _, cards := range group {
if len(cards) == 4 {
@ -190,10 +192,10 @@ func HandOrderFourWithTwo(count int) HandHandle {
}
// HandOrderFourWithThree 四带三顺子
func HandOrderFourWithThree(count int) HandHandle {
return func(rule *Rule, cards []Card) bool {
group := GroupByPoint(cards...)
var continuous []Card
func HandOrderFourWithThree[P, C generic.Number, T Card[P, C]](count int) HandHandle[P, C, T] {
return func(rule *Rule[P, C, T], cards []T) bool {
group := GroupByPoint[P, C, T](cards...)
var continuous []T
var other int
for _, cards := range group {
if len(cards) == 4 {
@ -212,9 +214,9 @@ func HandOrderFourWithThree(count int) HandHandle {
}
// HandFourWithOne 四带一
func HandFourWithOne() HandHandle {
return func(rule *Rule, cards []Card) bool {
group := GroupByPoint(cards...)
func HandFourWithOne[P, C generic.Number, T Card[P, C]]() HandHandle[P, C, T] {
return func(rule *Rule[P, C, T], cards []T) bool {
group := GroupByPoint[P, C, T](cards...)
var hasFour bool
var count int
for _, cards := range group {
@ -229,9 +231,9 @@ func HandFourWithOne() HandHandle {
}
// HandFourWithTwo 四带二
func HandFourWithTwo() HandHandle {
return func(rule *Rule, cards []Card) bool {
group := GroupByPoint(cards...)
func HandFourWithTwo[P, C generic.Number, T Card[P, C]]() HandHandle[P, C, T] {
return func(rule *Rule[P, C, T], cards []T) bool {
group := GroupByPoint[P, C, T](cards...)
var hasFour bool
var count int
for _, cards := range group {
@ -246,9 +248,9 @@ func HandFourWithTwo() HandHandle {
}
// HandFourWithThree 四带三
func HandFourWithThree() HandHandle {
return func(rule *Rule, cards []Card) bool {
group := GroupByPoint(cards...)
func HandFourWithThree[P, C generic.Number, T Card[P, C]]() HandHandle[P, C, T] {
return func(rule *Rule[P, C, T], cards []T) bool {
group := GroupByPoint[P, C, T](cards...)
var hasFour bool
var count int
for _, cards := range group {
@ -263,9 +265,9 @@ func HandFourWithThree() HandHandle {
}
// HandFourWithTwoPairs 四带两对
func HandFourWithTwoPairs() HandHandle {
return func(rule *Rule, cards []Card) bool {
group := GroupByPoint(cards...)
func HandFourWithTwoPairs[P, C generic.Number, T Card[P, C]]() HandHandle[P, C, T] {
return func(rule *Rule[P, C, T], cards []T) bool {
group := GroupByPoint[P, C, T](cards...)
var hasFour bool
var count int
for _, cards := range group {
@ -286,15 +288,15 @@ func HandFourWithTwoPairs() HandHandle {
}
// HandBomb 炸弹
func HandBomb() HandHandle {
return func(rule *Rule, cards []Card) bool {
func HandBomb[P, C generic.Number, T Card[P, C]]() HandHandle[P, C, T] {
return func(rule *Rule[P, C, T], cards []T) bool {
return len(cards) == 4 && rule.IsPointContinuity(4, cards...)
}
}
// HandStraightPairs 连对
func HandStraightPairs() HandHandle {
return func(rule *Rule, cards []Card) bool {
func HandStraightPairs[P, C generic.Number, T Card[P, C]]() HandHandle[P, C, T] {
return func(rule *Rule[P, C, T], cards []T) bool {
if len(cards) < 6 || len(cards)%2 != 0 {
return false
}
@ -304,8 +306,8 @@ func HandStraightPairs() HandHandle {
// HandPlane 飞机
// - 表示三张点数相同的牌组成的连续的牌
func HandPlane() HandHandle {
return func(rule *Rule, cards []Card) bool {
func HandPlane[P, C generic.Number, T Card[P, C]]() HandHandle[P, C, T] {
return func(rule *Rule[P, C, T], cards []T) bool {
if len(cards) < 6 || len(cards)%3 != 0 {
return false
}
@ -314,9 +316,9 @@ func HandPlane() HandHandle {
}
// HandPlaneWithOne 飞机带单
func HandPlaneWithOne() HandHandle {
return func(rule *Rule, cards []Card) bool {
group := GroupByPoint(cards...)
func HandPlaneWithOne[P, C generic.Number, T Card[P, C]]() HandHandle[P, C, T] {
return func(rule *Rule[P, C, T], cards []T) bool {
group := GroupByPoint[P, C, T](cards...)
if len(group) < 2 {
return false
}
@ -335,20 +337,20 @@ func HandPlaneWithOne() HandHandle {
// HandRocket 王炸
// - 表示一对王牌,即大王和小王
func HandRocket() HandHandle {
return func(rule *Rule, cards []Card) bool {
func HandRocket[P, C generic.Number, T Card[P, C]](pile *CardPile[P, C, T]) HandHandle[P, C, T] {
return func(rule *Rule[P, C, T], cards []T) bool {
if len(cards) != 2 {
return false
}
return IsRocket(cards[0], cards[1])
return IsRocket[P, C, T](pile, cards[0], cards[1])
}
}
// HandFlush 同花
// - 表示所有牌的花色都相同
func HandFlush() HandHandle {
return func(rule *Rule, cards []Card) bool {
return IsFlush(cards...)
func HandFlush[P, C generic.Number, T Card[P, C]]() HandHandle[P, C, T] {
return func(rule *Rule[P, C, T], cards []T) bool {
return IsFlush[P, C, T](cards...)
}
}
@ -356,12 +358,12 @@ func HandFlush() HandHandle {
// - count: 顺子的对子数量,例如当 count = 2 时,可以是 334455、445566、556677、667788、778899
// - lower: 顺子的最小连续数量
// - limit: 顺子的最大连续数量
func HandFlushStraight(count, lower, limit int) HandHandle {
return func(rule *Rule, cards []Card) bool {
func HandFlushStraight[P, C generic.Number, T Card[P, C]](count, lower, limit int) HandHandle[P, C, T] {
return func(rule *Rule[P, C, T], cards []T) bool {
if len(cards) < lower*count || len(cards) > limit*count || len(cards)%count != 0 {
return false
}
if !IsFlush(cards...) {
if !IsFlush[P, C, T](cards...) {
return false
}
return rule.IsPointContinuity(count, cards...)
@ -372,8 +374,8 @@ func HandFlushStraight(count, lower, limit int) HandHandle {
// - 表示三张点数相同的牌
// - 例如333、444、555、666、777、888、999、JJJ、QQQ、KKK、AAA
// - 大小王不能用于豹子,因为他们没有点数
func HandLeopard() HandHandle {
return func(rule *Rule, cards []Card) bool {
func HandLeopard[P, C generic.Number, T Card[P, C]]() HandHandle[P, C, T] {
return func(rule *Rule[P, C, T], cards []T) bool {
if len(cards) == 0 {
return false
}
@ -382,7 +384,7 @@ func HandLeopard() HandHandle {
}
var card = cards[0]
for i := 1; i < len(cards); i++ {
if !card.Equal(cards[1]) {
if !Equal[P, C, T](card, cards[1]) {
return false
}
}
@ -395,9 +397,9 @@ func HandLeopard() HandHandle {
// - 例如334、445、556、667、778、889、99J、TTQ、JJK、QQA、AA2
// - 大小王不能用于二带一,因为他们没有点数
// - 通常用于炸金花玩法中检查对子
func HandTwoWithOne() HandHandle {
return func(rule *Rule, cards []Card) bool {
group := GroupByPoint(cards...)
func HandTwoWithOne[P, C generic.Number, T Card[P, C]]() HandHandle[P, C, T] {
return func(rule *Rule[P, C, T], cards []T) bool {
group := GroupByPoint[P, C, T](cards...)
var hasTwo bool
var count int
for _, cards := range group {

52
game/poker/matcher.go Normal file
View File

@ -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
}

View File

@ -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
}

View File

@ -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
})
}
}

117
game/poker/matcher_test.go Normal file
View File

@ -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()))
}
}

View File

@ -1,13 +1,16 @@
package poker
import "fmt"
import (
"fmt"
"github.com/kercylan98/minotaur/utils/generic"
)
type Option func(rule *Rule)
type Option[P, C generic.Number, T Card[P, C]] func(rule *Rule[P, C, T])
// WithHand 通过绑定特定牌型的方式创建扑克玩法
// - 牌型顺序决定了牌型的优先级
func WithHand(pokerHand string, value int, handle HandHandle) Option {
return func(rule *Rule) {
func WithHand[P, C generic.Number, T Card[P, C]](pokerHand string, value int, handle HandHandle[P, C, T]) Option[P, C, T] {
return func(rule *Rule[P, C, T]) {
if _, exist := rule.pokerHand[pokerHand]; exist {
panic(fmt.Errorf("same poker hand name: %s", pokerHand))
}
@ -24,8 +27,8 @@ func WithHand(pokerHand string, value int, handle HandHandle) Option {
}
// WithHandRestraint 通过绑定特定克制牌型的方式创建扑克玩法
func WithHandRestraint(pokerHand, restraint string) Option {
return func(rule *Rule) {
func WithHandRestraint[P, C generic.Number, T Card[P, C]](pokerHand, restraint string) Option[P, C, T] {
return func(rule *Rule[P, C, T]) {
r, exist := rule.restraint[pokerHand]
if !exist {
r = map[string]struct{}{}
@ -37,8 +40,8 @@ func WithHandRestraint(pokerHand, restraint string) Option {
// WithHandRestraintFull 通过绑定所有克制牌型的方式创建扑克玩法
// - 需要确保在牌型声明之后调用
func WithHandRestraintFull(pokerHand string) Option {
return func(rule *Rule) {
func WithHandRestraintFull[P, C generic.Number, T Card[P, C]](pokerHand string) Option[P, C, T] {
return func(rule *Rule[P, C, T]) {
for hand := range rule.pokerHand {
r, exist := rule.restraint[pokerHand]
if !exist {
@ -51,22 +54,22 @@ func WithHandRestraintFull(pokerHand string) Option {
}
// WithPointValue 通过特定的扑克点数牌值创建扑克玩法
func WithPointValue(pointValues map[Point]int) Option {
return func(rule *Rule) {
func WithPointValue[P, C generic.Number, T Card[P, C]](pointValues map[P]int) Option[P, C, T] {
return func(rule *Rule[P, C, T]) {
rule.pointValue = pointValues
}
}
// WithColorValue 通过特定的扑克花色牌值创建扑克玩法
func WithColorValue(colorValues map[Color]int) Option {
return func(rule *Rule) {
func WithColorValue[P, C generic.Number, T Card[P, C]](colorValues map[C]int) Option[P, C, T] {
return func(rule *Rule[P, C, T]) {
rule.colorValue = colorValues
}
}
// WithPointSort 通过特定的扑克点数顺序创建扑克玩法,顺序必须为连续的
func WithPointSort(pointSort map[Point]int) Option {
return func(rule *Rule) {
func WithPointSort[P, C generic.Number, T Card[P, C]](pointSort map[P]int) Option[P, C, T] {
return func(rule *Rule[P, C, T]) {
for k, v := range pointSort {
rule.pointSort[k] = v
}
@ -74,8 +77,8 @@ func WithPointSort(pointSort map[Point]int) Option {
}
// WithColorSort 通过特定的扑克花色顺序创建扑克玩法,顺序必须为连续的
func WithColorSort(colorSort map[Color]int) Option {
return func(rule *Rule) {
func WithColorSort[P, C generic.Number, T Card[P, C]](colorSort map[C]int) Option[P, C, T] {
return func(rule *Rule[P, C, T]) {
for k, v := range colorSort {
rule.colorSort[k] = v
}
@ -83,10 +86,10 @@ func WithColorSort(colorSort map[Color]int) Option {
}
// WithExcludeContinuityPoint 排除连续的点数
func WithExcludeContinuityPoint(points ...Point) Option {
return func(rule *Rule) {
func WithExcludeContinuityPoint[P, C generic.Number, T Card[P, C]](points ...P) Option[P, C, T] {
return func(rule *Rule[P, C, T]) {
if rule.excludeContinuityPoint == nil {
rule.excludeContinuityPoint = make(map[Point]struct{})
rule.excludeContinuityPoint = make(map[P]struct{})
}
for _, point := range points {
rule.excludeContinuityPoint[point] = struct{}{}

View File

@ -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
}

View File

@ -1,9 +1,14 @@
package poker
import (
"github.com/kercylan98/minotaur/utils/generic"
"math"
)
// IsContainJoker 检查扑克牌是否包含大小王
func IsContainJoker(cards ...Card) bool {
func IsContainJoker[P, C generic.Number, T Card[P, C]](pile *CardPile[P, C, T], cards ...T) bool {
for _, card := range cards {
if card.IsJoker() {
if IsJoker[P, C, T](pile, card) {
return true
}
}
@ -11,8 +16,8 @@ func IsContainJoker(cards ...Card) bool {
}
// GroupByPoint 将扑克牌按照点数分组
func GroupByPoint(cards ...Card) map[Point][]Card {
group := map[Point][]Card{}
func GroupByPoint[P, C generic.Number, T Card[P, C]](cards ...T) map[P][]T {
group := map[P][]T{}
for _, card := range cards {
group[card.GetPoint()] = append(group[card.GetPoint()], card)
}
@ -20,8 +25,8 @@ func GroupByPoint(cards ...Card) map[Point][]Card {
}
// GroupByColor 将扑克牌按照花色分组
func GroupByColor(cards ...Card) map[Color][]Card {
group := map[Color][]Card{}
func GroupByColor[P, C generic.Number, T Card[P, C]](cards ...T) map[C][]T {
group := map[C][]T{}
for _, card := range cards {
group[card.GetColor()] = append(group[card.GetColor()], card)
}
@ -29,12 +34,18 @@ func GroupByColor(cards ...Card) map[Color][]Card {
}
// IsRocket 两张牌能否组成红黑 Joker
func IsRocket(cardA, cardB Card) bool {
return cardA.GetPoint() == PointRedJoker && cardB.GetPoint() == PointBlackJoker || cardA.GetPoint() == PointBlackJoker && cardB.GetPoint() == PointRedJoker
func IsRocket[P, C generic.Number, T Card[P, C]](pile *CardPile[P, C, T], cardA, cardB T) bool {
var num int
for _, joker := range pile.jokers {
if cardA.GetPoint() == joker || cardB.GetPoint() == joker {
num++
}
}
return num == 2
}
// IsFlush 判断是否是同花
func IsFlush(cards ...Card) bool {
func IsFlush[P, C generic.Number, T Card[P, C]](cards ...T) bool {
if len(cards) == 0 {
return false
}
@ -52,8 +63,8 @@ func IsFlush(cards ...Card) bool {
}
// GetCardsPoint 获取一组扑克牌的点数
func GetCardsPoint(cards ...Card) []Point {
var points = make([]Point, len(cards))
func GetCardsPoint[P, C generic.Number, T Card[P, C]](cards ...T) []P {
var points = make([]P, len(cards))
for i, card := range cards {
points[i] = card.GetPoint()
}
@ -61,10 +72,136 @@ func GetCardsPoint(cards ...Card) []Point {
}
// GetCardsColor 获取一组扑克牌的花色
func GetCardsColor(cards ...Card) []Color {
var colors = make([]Color, len(cards))
func GetCardsColor[P, C generic.Number, T Card[P, C]](cards ...T) []C {
var colors = make([]C, len(cards))
for i, card := range cards {
colors[i] = card.GetColor()
}
return colors
}
// IsContain 一组扑克牌是否包含某张牌
func IsContain[P, C generic.Number, T Card[P, C]](cards []T, card T) bool {
for _, c := range cards {
if Equal[P, C, T](c, card) {
return true
}
}
return false
}
// IsContainAll 一组扑克牌是否包含另一组扑克牌
func IsContainAll[P, C generic.Number, T Card[P, C]](cards []T, cards2 []T) bool {
for _, card := range cards2 {
if !IsContain[P, C, T](cards, card) {
return false
}
}
return true
}
// GetPointAndColor 返回扑克牌的点数和花色
func GetPointAndColor[P, C generic.Number, T Card[P, C]](card T) (P, C) {
return card.GetPoint(), card.GetColor()
}
// EqualPoint 比较两张扑克牌的点数是否相同
func EqualPoint[P, C generic.Number, T Card[P, C]](card1 T, card2 T) bool {
return card1.GetPoint() == card2.GetPoint()
}
// EqualColor 比较两张扑克牌的花色是否相同
func EqualColor[P, C generic.Number, T Card[P, C]](card1 T, card2 T) bool {
return card1.GetColor() == card2.GetColor()
}
// Equal 比较两张扑克牌的点数和花色是否相同
func Equal[P, C generic.Number, T Card[P, C]](card1 T, card2 T) bool {
return EqualPoint[P, C, T](card1, card2) && EqualColor[P, C, T](card1, card2)
}
// MaxPoint 返回两张扑克牌中点数较大的一张
func MaxPoint[P, C generic.Number, T Card[P, C]](card1 T, card2 T) T {
if card1.GetPoint() > card2.GetPoint() {
return card1
}
return card2
}
// MinPoint 返回两张扑克牌中点数较小的一张
func MinPoint[P, C generic.Number, T Card[P, C]](card1 T, card2 T) T {
if card1.GetPoint() < card2.GetPoint() {
return card1
}
return card2
}
// MaxColor 返回两张扑克牌中花色较大的一张
func MaxColor[P, C generic.Number, T Card[P, C]](card1 T, card2 T) T {
if card1.GetColor() > card2.GetColor() {
return card1
}
return card2
}
// MinColor 返回两张扑克牌中花色较小的一张
func MinColor[P, C generic.Number, T Card[P, C]](card1 T, card2 T) T {
if card1.GetColor() < card2.GetColor() {
return card1
}
return card2
}
// Max 返回两张扑克牌中点数和花色较大的一张
func Max[P, C generic.Number, T Card[P, C]](card1 T, card2 T) T {
if card1.GetPoint() > card2.GetPoint() {
return card1
} else if card1.GetPoint() < card2.GetPoint() {
return card2
} else if card1.GetColor() > card2.GetColor() {
return card1
}
return card2
}
// Min 返回两张扑克牌中点数和花色较小的一张
func Min[P, C generic.Number, T Card[P, C]](card1 T, card2 T) T {
if card1.GetPoint() < card2.GetPoint() {
return card1
} else if card1.GetPoint() > card2.GetPoint() {
return card2
} else if card1.GetColor() < card2.GetColor() {
return card1
}
return card2
}
// PointDifference 计算两张扑克牌的点数差
func PointDifference[P, C generic.Number, T Card[P, C]](card1 T, card2 T) int {
return int(math.Abs(float64(card1.GetPoint()) - float64(card2.GetPoint())))
}
// ColorDifference 计算两张扑克牌的花色差
func ColorDifference[P, C generic.Number, T Card[P, C]](card1 T, card2 T) int {
return int(math.Abs(float64(card1.GetColor()) - float64(card2.GetColor())))
}
// IsNeighborColor 判断两张扑克牌是否为相邻的花色
func IsNeighborColor[P, C generic.Number, T Card[P, C]](card1 T, card2 T) bool {
return ColorDifference[P, C, T](card1, card2) == 1
}
// IsNeighborPoint 判断两张扑克牌是否为相邻的点数
func IsNeighborPoint[P, C generic.Number, T Card[P, C]](card1 T, card2 T) bool {
return PointDifference[P, C, T](card1, card2) == 1
}
// IsJoker 判断扑克牌是否为大小王
func IsJoker[P, C generic.Number, T Card[P, C]](pile *CardPile[P, C, T], card T) bool {
for _, joker := range pile.jokers {
if card.GetPoint() == joker {
return true
}
}
return false
}

View File

@ -1,16 +1,17 @@
package poker
import (
"github.com/kercylan98/minotaur/utils/generic"
"github.com/kercylan98/minotaur/utils/hash"
"github.com/kercylan98/minotaur/utils/maths"
"sort"
)
func NewRule(options ...Option) *Rule {
poker := &Rule{
pokerHand: map[string]HandHandle{},
pointSort: hash.Copy(defaultPointSort),
colorSort: hash.Copy(defaultColorSort),
func NewRule[P, C generic.Number, T Card[P, C]](options ...Option[P, C, T]) *Rule[P, C, T] {
poker := &Rule[P, C, T]{
pokerHand: map[string]HandHandle[P, C, T]{},
//pointSort: hash.Copy(defaultPointSort),
//colorSort: hash.Copy(defaultColorSort),
pokerHandValue: map[string]int{},
restraint: map[string]map[string]struct{}{},
}
@ -23,19 +24,19 @@ func NewRule(options ...Option) *Rule {
return poker
}
type Rule struct {
pokerHand map[string]HandHandle
type Rule[P, C generic.Number, T Card[P, C]] struct {
pokerHand map[string]HandHandle[P, C, T]
pokerHandValue map[string]int
pointValue map[Point]int
colorValue map[Color]int
pointSort map[Point]int
colorSort map[Color]int
excludeContinuityPoint map[Point]struct{}
pointValue map[P]int
colorValue map[C]int
pointSort map[P]int
colorSort map[C]int
excludeContinuityPoint map[P]struct{}
restraint map[string]map[string]struct{}
}
// GetCardCountWithPointMaximumNumber 获取指定点数的牌的数量
func (slf *Rule) GetCardCountWithPointMaximumNumber(cards []Card, point Point, maximumNumber int) int {
func (slf *Rule[P, C, T]) GetCardCountWithPointMaximumNumber(cards []T, point P, maximumNumber int) int {
count := 0
for _, card := range cards {
if card.GetPoint() == point {
@ -49,7 +50,7 @@ func (slf *Rule) GetCardCountWithPointMaximumNumber(cards []Card, point Point, m
}
// GetCardCountWithColorMaximumNumber 获取指定花色的牌的数量
func (slf *Rule) GetCardCountWithColorMaximumNumber(cards []Card, color Color, maximumNumber int) int {
func (slf *Rule[P, C, T]) GetCardCountWithColorMaximumNumber(cards []T, color C, maximumNumber int) int {
count := 0
for _, card := range cards {
if card.GetColor() == color {
@ -63,10 +64,10 @@ func (slf *Rule) GetCardCountWithColorMaximumNumber(cards []Card, color Color, m
}
// GetCardCountWithMaximumNumber 获取指定牌的数量
func (slf *Rule) GetCardCountWithMaximumNumber(cards []Card, card Card, maximumNumber int) int {
func (slf *Rule[P, C, T]) GetCardCountWithMaximumNumber(cards []T, card T, maximumNumber int) int {
count := 0
for _, c := range cards {
if c.Equal(card) {
if Equal[P, C, T](c, card) {
count++
if count >= maximumNumber {
return count
@ -77,7 +78,7 @@ func (slf *Rule) GetCardCountWithMaximumNumber(cards []Card, card Card, maximumN
}
// GetCardCountWithPoint 获取指定点数的牌的数量
func (slf *Rule) GetCardCountWithPoint(cards []Card, point Point) int {
func (slf *Rule[P, C, T]) GetCardCountWithPoint(cards []T, point P) int {
count := 0
for _, card := range cards {
if card.GetPoint() == point {
@ -88,7 +89,7 @@ func (slf *Rule) GetCardCountWithPoint(cards []Card, point Point) int {
}
// GetCardCountWithColor 获取指定花色的牌的数量
func (slf *Rule) GetCardCountWithColor(cards []Card, color Color) int {
func (slf *Rule[P, C, T]) GetCardCountWithColor(cards []T, color C) int {
count := 0
for _, card := range cards {
if card.GetColor() == color {
@ -99,10 +100,10 @@ func (slf *Rule) GetCardCountWithColor(cards []Card, color Color) int {
}
// GetCardCount 获取指定牌的数量
func (slf *Rule) GetCardCount(cards []Card, card Card) int {
func (slf *Rule[P, C, T]) GetCardCount(cards []T, card T) int {
count := 0
for _, c := range cards {
if c.Equal(card) {
if Equal[P, C, T](c, card) {
count++
}
}
@ -110,7 +111,7 @@ func (slf *Rule) GetCardCount(cards []Card, card Card) int {
}
// PokerHandIsMatch 检查两组扑克牌牌型是否匹配
func (slf *Rule) PokerHandIsMatch(cardsA, cardsB []Card) bool {
func (slf *Rule[P, C, T]) PokerHandIsMatch(cardsA, cardsB []T) bool {
handA, exist := slf.PokerHand(cardsA...)
if !exist {
return false
@ -126,7 +127,7 @@ func (slf *Rule) PokerHandIsMatch(cardsA, cardsB []Card) bool {
}
// PokerHand 获取一组扑克的牌型
func (slf *Rule) PokerHand(cards ...Card) (pokerHand string, hit bool) {
func (slf *Rule[P, C, T]) PokerHand(cards ...T) (pokerHand string, hit bool) {
for phn := range slf.pokerHandValue {
if slf.pokerHand[phn](slf, cards) {
return phn, true
@ -136,11 +137,11 @@ func (slf *Rule) PokerHand(cards ...Card) (pokerHand string, hit bool) {
}
// IsPointContinuity 检查一组扑克牌点数是否连续count 表示了每个点数的数量
func (slf *Rule) IsPointContinuity(count int, cards ...Card) bool {
func (slf *Rule[P, C, T]) IsPointContinuity(count int, cards ...T) bool {
if len(cards) == 0 {
return false
}
group := GroupByPoint(cards...)
group := GroupByPoint[P, C, T](cards...)
var values []int
for point, cards := range group {
if _, exist := slf.excludeContinuityPoint[point]; exist {
@ -155,7 +156,7 @@ func (slf *Rule) IsPointContinuity(count int, cards ...Card) bool {
}
// IsSameColor 检查一组扑克牌是否同花
func (slf *Rule) IsSameColor(cards ...Card) bool {
func (slf *Rule[P, C, T]) IsSameColor(cards ...T) bool {
length := len(cards)
if length == 0 {
return false
@ -173,7 +174,7 @@ func (slf *Rule) IsSameColor(cards ...Card) bool {
}
// IsSamePoint 检查一组扑克牌是否同点
func (slf *Rule) IsSamePoint(cards ...Card) bool {
func (slf *Rule[P, C, T]) IsSamePoint(cards ...T) bool {
length := len(cards)
if length == 0 {
return false
@ -191,7 +192,7 @@ func (slf *Rule) IsSamePoint(cards ...Card) bool {
}
// SortByPointDesc 将扑克牌按照点数从大到小排序
func (slf *Rule) SortByPointDesc(cards []Card) []Card {
func (slf *Rule[P, C, T]) SortByPointDesc(cards []T) []T {
sort.Slice(cards, func(i, j int) bool {
return slf.pointSort[cards[i].GetPoint()] > slf.pointSort[cards[j].GetPoint()]
})
@ -199,7 +200,7 @@ func (slf *Rule) SortByPointDesc(cards []Card) []Card {
}
// SortByPointAsc 将扑克牌按照点数从小到大排序
func (slf *Rule) SortByPointAsc(cards []Card) []Card {
func (slf *Rule[P, C, T]) SortByPointAsc(cards []T) []T {
sort.Slice(cards, func(i, j int) bool {
return slf.pointSort[cards[i].GetPoint()] < slf.pointSort[cards[j].GetPoint()]
})
@ -207,7 +208,7 @@ func (slf *Rule) SortByPointAsc(cards []Card) []Card {
}
// SortByColorDesc 将扑克牌按照花色从大到小排序
func (slf *Rule) SortByColorDesc(cards []Card) []Card {
func (slf *Rule[P, C, T]) SortByColorDesc(cards []T) []T {
sort.Slice(cards, func(i, j int) bool {
return slf.colorSort[cards[i].GetColor()] > slf.colorSort[cards[j].GetColor()]
})
@ -215,7 +216,7 @@ func (slf *Rule) SortByColorDesc(cards []Card) []Card {
}
// SortByColorAsc 将扑克牌按照花色从小到大排序
func (slf *Rule) SortByColorAsc(cards []Card) []Card {
func (slf *Rule[P, C, T]) SortByColorAsc(cards []T) []T {
sort.Slice(cards, func(i, j int) bool {
return slf.colorSort[cards[i].GetColor()] < slf.colorSort[cards[j].GetColor()]
})
@ -223,13 +224,13 @@ func (slf *Rule) SortByColorAsc(cards []Card) []Card {
}
// GetValueWithPokerHand 获取扑克牌的牌型牌值
func (slf *Rule) GetValueWithPokerHand(hand string, cards ...Card) int {
func (slf *Rule[P, C, T]) GetValueWithPokerHand(hand string, cards ...T) int {
hv := slf.pokerHandValue[hand]
return hv * slf.GetValueWithCards(cards...)
}
// GetValueWithCards 获取扑克牌的牌值
func (slf *Rule) GetValueWithCards(cards ...Card) int {
func (slf *Rule[P, C, T]) GetValueWithCards(cards ...T) int {
var value int
for _, card := range cards {
value += slf.GetValueWithPoint(card.GetPoint())
@ -239,16 +240,16 @@ func (slf *Rule) GetValueWithCards(cards ...Card) int {
}
// GetValueWithPoint 获取扑克牌的点数牌值
func (slf *Rule) GetValueWithPoint(point Point) int {
func (slf *Rule[P, C, T]) GetValueWithPoint(point P) int {
return slf.pointValue[point]
}
// GetValueWithColor 获取扑克牌的花色牌值
func (slf *Rule) GetValueWithColor(color Color) int {
func (slf *Rule[P, C, T]) GetValueWithColor(color C) int {
return slf.colorValue[color]
}
// CompareValueWithCards 根据特定的条件表达式比较两组扑克牌的牌值
func (slf *Rule) CompareValueWithCards(cards1 []Card, expression maths.CompareExpression, cards2 []Card) bool {
func (slf *Rule[P, C, T]) CompareValueWithCards(cards1 []T, expression maths.CompareExpression, cards2 []T) bool {
return maths.Compare(slf.GetValueWithCards(cards1...), expression, slf.GetValueWithCards(cards2...))
}

View File

@ -1,9 +1,5 @@
package game
import (
"github.com/kercylan98/minotaur/utils/hash"
)
// Room 房间类似于简版的游戏世界(World),不过没有游戏实体
type Room[PlayerID comparable, P Player[PlayerID]] interface {
// GetGuid 获取房间的唯一标识符
@ -13,7 +9,7 @@ type Room[PlayerID comparable, P Player[PlayerID]] interface {
// GetPlayer 根据玩家id获取玩家
GetPlayer(id PlayerID) P
// GetPlayers 获取房间中的所有玩家
GetPlayers() hash.MapReadonly[PlayerID, P]
GetPlayers() map[PlayerID]P
// GetPlayerCount 获取玩家数量
GetPlayerCount() int
// IsExistPlayer 检查房间中是否存在特定玩家

View File

@ -36,4 +36,12 @@ type RoomSeat[PlayerID comparable, P Player[PlayerID]] interface {
// - 超出范围将返回-1
// - 当没有下一个座位号时将始终返回本身
GetNextSeatVacancy(seat int) int
// GetPrevSeat 获取上一个座位号,空缺的位置将会被跳过
// - 超出范围将返回-1
// - 当没有上一个座位号时将始终返回本身
GetPrevSeat(seat int) int
// GetPrevSeatVacancy 获取上一个座位号,空缺的位置将被保留
// - 超出范围将返回-1
// - 当没有上一个座位号时将始终返回本身
GetPrevSeatVacancy(seat int) int
}

View File

@ -1,9 +1,5 @@
package game
import (
"github.com/kercylan98/minotaur/utils/hash"
)
// World 游戏世界接口定义
type World[PlayerID comparable, P Player[PlayerID]] interface {
// GetGuid 获取世界的唯一标识符
@ -13,15 +9,15 @@ type World[PlayerID comparable, P Player[PlayerID]] interface {
// GetPlayer 根据玩家id获取玩家
GetPlayer(id PlayerID) P
// GetPlayers 获取世界中的所有玩家
GetPlayers() hash.MapReadonly[PlayerID, P]
GetPlayers() map[PlayerID]P
// GetActor 根据唯一标识符获取世界中的游戏对象
GetActor(guid int64) Actor
// GetActors 获取世界中的所有游戏对象
GetActors() hash.MapReadonly[int64, Actor]
GetActors() map[int64]Actor
// GetPlayerActor 获取游戏世界中归属特定玩家的特定游戏对象
GetPlayerActor(id PlayerID, guid int64) Actor
// GetPlayerActors 获取游戏世界中归属特定玩家的所有游戏对象
GetPlayerActors(id PlayerID) hash.MapReadonly[int64, Actor]
GetPlayerActors(id PlayerID) map[int64]Actor
// IsExistPlayer 检查游戏世界中是否存在特定玩家
IsExistPlayer(id PlayerID) bool
// IsExistActor 检查游戏世界中是否存在特定游戏对象

2
go.mod
View File

@ -23,6 +23,7 @@ require (
)
require (
github.com/alphadose/haxmap v1.2.0 // indirect
github.com/bytedance/sonic v1.9.1 // indirect
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 // indirect
github.com/gabriel-vasile/mimetype v1.4.2 // indirect
@ -61,6 +62,7 @@ require (
go.uber.org/multierr v1.11.0 // indirect
golang.org/x/arch v0.3.0 // indirect
golang.org/x/crypto v0.9.0 // indirect
golang.org/x/exp v0.0.0-20221031165847-c99f073a8326 // indirect
golang.org/x/net v0.10.0 // indirect
golang.org/x/sys v0.8.0 // indirect
golang.org/x/text v0.9.0 // indirect

4
go.sum
View File

@ -2,6 +2,8 @@ cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMT
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/RussellLuo/timingwheel v0.0.0-20220218152713-54845bda3108 h1:iPugyBI7oFtbDZXC4dnY093M1kZx6k/95sen92gafbY=
github.com/RussellLuo/timingwheel v0.0.0-20220218152713-54845bda3108/go.mod h1:WAMLHwunr1hi3u7OjGV6/VWG9QbdMhGpEKjROiSFd10=
github.com/alphadose/haxmap v1.2.0 h1:noGrAmCE+gNheZ4KpW+sYj9W5uMcO1UAjbAq9XBOAfM=
github.com/alphadose/haxmap v1.2.0/go.mod h1:rjHw1IAqbxm0S3U5tD16GoKsiAd8FWx5BJ2IYqXwgmM=
github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8=
github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=
github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM=
@ -219,6 +221,8 @@ golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5y
golang.org/x/crypto v0.9.0 h1:LF6fAI+IutBocDJ2OT0Q1g8plpYljMZ4+lty+dsqw3g=
golang.org/x/crypto v0.9.0/go.mod h1:yrmDGqONDYtNj3tH8X9dzUun2m2lzPa9ngI6/RUPGR0=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20221031165847-c99f073a8326 h1:QfTh0HpN6hlw6D3vu8DAwC8pBIwikq0AI1evdm+FksE=
golang.org/x/exp v0.0.0-20221031165847-c99f073a8326/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=

View File

@ -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)
})
}

View File

@ -1,8 +1,7 @@
package report
import (
"github.com/kercylan98/minotaur/utils/asynchronous"
"github.com/kercylan98/minotaur/utils/hash"
"github.com/kercylan98/minotaur/utils/concurrent"
"sync"
)
@ -10,7 +9,7 @@ import (
func NewDataBuried[DataID comparable, Data any](name string, hitLogic HitLogic[Data], options ...DataBuriedOption[DataID, Data]) *DataBuried[DataID, Data] {
buried := &DataBuried[DataID, Data]{
name: name,
data: asynchronous.NewMap[DataID, Data](),
data: concurrent.NewBalanceMap[DataID, Data](),
hitLogic: hitLogic,
}
buried.setData = func(id DataID, data Data) {
@ -29,7 +28,7 @@ func NewDataBuried[DataID comparable, Data any](name string, hitLogic HitLogic[D
// - 数据埋点通常用于统计不同类型的数据,例如用户数据、商城数据等
type DataBuried[DataID comparable, Data any] struct {
name string
data hash.Map[DataID, Data]
data *concurrent.BalanceMap[DataID, Data]
hitLogic HitLogic[Data]
getData func(DataID) Data
setData func(id DataID, data Data)

View File

@ -2,7 +2,7 @@ package server
import (
"github.com/gorilla/websocket"
"github.com/kercylan98/minotaur/utils/synchronization"
"github.com/kercylan98/minotaur/utils/concurrent"
"github.com/panjf2000/gnet"
"github.com/xtaci/kcp-go/v5"
"net"
@ -75,7 +75,7 @@ type Conn struct {
kcp *kcp.UDPSession
data map[any]any
mutex sync.Mutex
packetPool *synchronization.Pool[*connPacket]
packetPool *concurrent.Pool[*connPacket]
packets []*connPacket
}
@ -187,7 +187,7 @@ func (slf *Conn) WriteWithCallback(packet Packet, callback func(err error), mess
// writeLoop 写循环
func (slf *Conn) writeLoop(wait *sync.WaitGroup) {
slf.packetPool = synchronization.NewPool[*connPacket](10*1024,
slf.packetPool = concurrent.NewPool[*connPacket](10*1024,
func() *connPacket {
return &connPacket{}
}, func(data *connPacket) {

View File

@ -4,8 +4,8 @@ import (
"encoding/json"
"fmt"
"github.com/kercylan98/minotaur/server"
"github.com/kercylan98/minotaur/utils/concurrent"
"github.com/kercylan98/minotaur/utils/log"
"github.com/kercylan98/minotaur/utils/synchronization"
"github.com/nats-io/nats.go"
"time"
)
@ -18,7 +18,7 @@ func NewNats(url string, options ...NatsOption) *Nats {
n := &Nats{
url: url,
subject: "MINOTAUR_CROSS",
messagePool: synchronization.NewPool[*Message](1024*100, func() *Message {
messagePool: concurrent.NewPool[*Message](1024*100, func() *Message {
return new(Message)
}, func(data *Message) {
data.ServerId = 0
@ -36,7 +36,7 @@ type Nats struct {
url string
subject string
options []nats.Option
messagePool *synchronization.Pool[*Message]
messagePool *concurrent.Pool[*Message]
}
func (slf *Nats) Init(server *server.Server, packetHandle func(serverId int64, packet []byte)) (err error) {

View File

@ -5,9 +5,9 @@ import (
"fmt"
"github.com/gin-gonic/gin"
"github.com/gorilla/websocket"
"github.com/kercylan98/minotaur/utils/concurrent"
"github.com/kercylan98/minotaur/utils/log"
"github.com/kercylan98/minotaur/utils/super"
"github.com/kercylan98/minotaur/utils/synchronization"
"github.com/kercylan98/minotaur/utils/timer"
"github.com/kercylan98/minotaur/utils/times"
"github.com/panjf2000/ants/v2"
@ -33,7 +33,7 @@ func New(network Network, options ...Option) *Server {
runtime: &runtime{messagePoolSize: DefaultMessageBufferSize, messageChannelSize: DefaultMessageChannelSize},
option: &option{},
network: network,
online: synchronization.NewMap[string, *Conn](),
online: concurrent.NewBalanceMap[string, *Conn](),
closeChannel: make(chan struct{}, 1),
systemSignal: make(chan os.Signal, 1),
}
@ -72,26 +72,26 @@ func New(network Network, options ...Option) *Server {
// Server 网络服务器
type Server struct {
*event // 事件
*runtime // 运行时
*option // 可选项
network Network // 网络类型
addr string // 侦听地址
systemSignal chan os.Signal // 系统信号
online *synchronization.Map[string, *Conn] // 在线连接
ginServer *gin.Engine // HTTP模式下的路由器
httpServer *http.Server // HTTP模式下的服务器
grpcServer *grpc.Server // GRPC模式下的服务器
gServer *gNet // TCP或UDP模式下的服务器
isRunning bool // 是否正在运行
isShutdown atomic.Bool // 是否已关闭
closeChannel chan struct{} // 关闭信号
ants *ants.Pool // 协程池
messagePool *synchronization.Pool[*Message] // 消息池
messageChannel chan *Message // 消息管道
multiple *MultipleServer // 多服务器模式下的服务器
multipleRuntimeErrorChan chan error // 多服务器模式下的运行时错误
runMode RunMode // 运行模式
*event // 事件
*runtime // 运行时
*option // 可选项
network Network // 网络类型
addr string // 侦听地址
systemSignal chan os.Signal // 系统信号
online *concurrent.BalanceMap[string, *Conn] // 在线连接
ginServer *gin.Engine // HTTP模式下的路由器
httpServer *http.Server // HTTP模式下的服务器
grpcServer *grpc.Server // GRPC模式下的服务器
gServer *gNet // TCP或UDP模式下的服务器
isRunning bool // 是否正在运行
isShutdown atomic.Bool // 是否已关闭
closeChannel chan struct{} // 关闭信号
ants *ants.Pool // 协程池
messagePool *concurrent.Pool[*Message] // 消息池
messageChannel chan *Message // 消息管道
multiple *MultipleServer // 多服务器模式下的服务器
multipleRuntimeErrorChan chan error // 多服务器模式下的运行时错误
runMode RunMode // 运行模式
}
// Run 使用特定地址运行服务器
@ -115,7 +115,7 @@ func (slf *Server) Run(addr string) error {
var protoAddr = fmt.Sprintf("%s://%s", slf.network, slf.addr)
var messageInitFinish = make(chan struct{}, 1)
var connectionInitHandle = func(callback func()) {
slf.messagePool = synchronization.NewPool[*Message](slf.messagePoolSize,
slf.messagePool = concurrent.NewPool[*Message](slf.messagePoolSize,
func() *Message {
return &Message{}
},

View File

@ -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
}

View File

@ -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
}
}

View File

@ -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
}

View File

@ -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
}
}

View File

@ -1,4 +1,4 @@
package synchronization
package concurrent
import (
"github.com/kercylan98/minotaur/utils/log"

View File

@ -1,4 +1,4 @@
package synchronization
package concurrent
import (
"github.com/kercylan98/minotaur/utils/slice"

View File

@ -1,4 +1,4 @@
package synchronization
package concurrent
type SliceOption[T any] func(slice *Slice[T])

View File

@ -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)
}

View File

@ -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)
}

View File

@ -197,3 +197,125 @@ func ContainsAny[V any](slice []V, values V) bool {
}
return false
}
// GetIndex 判断数组是否包含某个元素,如果包含则返回索引
func GetIndex[V comparable](slice []V, value V) int {
for i, v := range slice {
if v == value {
return i
}
}
return -1
}
// GetIndexAny 判断数组是否包含某个元素,如果包含则返回索引
func GetIndexAny[V any](slice []V, values V) int {
for i, v := range slice {
if reflect.DeepEqual(v, values) {
return i
}
}
return -1
}
// Combinations 获取给定数组的所有组合,包括重复元素的组合
func Combinations[T any](a []T) [][]T {
n := len(a)
// 去除重复元素,保留唯一元素
uniqueSet := make(map[uintptr]bool)
uniqueSlice := make([]T, 0, n)
for _, val := range a {
ptr := reflect.ValueOf(val).Pointer()
if !uniqueSet[ptr] {
uniqueSet[ptr] = true
uniqueSlice = append(uniqueSlice, val)
}
}
n = len(uniqueSlice) // 去重后的数组长度
totalCombinations := 1 << n // 2的n次方
var result [][]T
for i := 0; i < totalCombinations; i++ {
var currentCombination []T
for j := 0; j < n; j++ {
if (i & (1 << j)) != 0 {
currentCombination = append(currentCombination, uniqueSlice[j])
}
}
result = append(result, currentCombination)
}
return result
}
// LimitedCombinations 获取给定数组的所有组合,且每个组合的成员数量限制在指定范围内
func LimitedCombinations[T any](a []T, minSize, maxSize int) [][]T {
n := len(a)
if n == 0 || minSize <= 0 || maxSize <= 0 || minSize > maxSize {
return nil
}
var result [][]T
var currentCombination []T
var backtrack func(startIndex int, currentSize int)
backtrack = func(startIndex int, currentSize int) {
if currentSize >= minSize && currentSize <= maxSize {
combination := make([]T, len(currentCombination))
copy(combination, currentCombination)
result = append(result, combination)
}
for i := startIndex; i < n; i++ {
currentCombination = append(currentCombination, a[i])
backtrack(i+1, currentSize+1)
currentCombination = currentCombination[:len(currentCombination)-1]
}
}
backtrack(0, 0)
return result
}
// IsIntersectWithCheck 判断两个切片是否有交集
func IsIntersectWithCheck[T any](a, b []T, checkHandle func(a, b T) bool) bool {
for _, a := range a {
for _, b := range b {
if checkHandle(a, b) {
return true
}
}
}
return false
}
// IsIntersect 判断两个切片是否有交集
func IsIntersect[T any](a, b []T) bool {
for _, a := range a {
for _, b := range b {
if reflect.DeepEqual(a, b) {
return true
}
}
}
return false
}
// SubWithCheck 获取移除指定元素后的切片
// - checkHandle 返回 true 表示需要移除
func SubWithCheck[T any](a, b []T, checkHandle func(a, b T) bool) []T {
var result []T
for _, a := range a {
flag := false
for _, b := range b {
if checkHandle(a, b) {
flag = true
break
}
}
if !flag {
result = append(result, a)
}
}
return result
}

14
utils/slice/slice_test.go Normal file
View File

@ -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)
}
}

View File

@ -1,37 +1,43 @@
package sole
import "sync"
var (
global int64
namespace map[any]int64
mutex sync.Mutex
import (
"sync/atomic"
)
func init() {
namespace = map[any]int64{}
var (
global atomic.Int64 // 全局唯一标识符
namespace = map[any]*atomic.Int64{} // 唯一标识符命名空间
)
// RegNameSpace 注册特定命名空间的唯一标识符
func RegNameSpace(name any) {
if namespace == nil {
namespace = map[any]*atomic.Int64{}
}
namespace[name] = new(atomic.Int64)
}
// UnRegNameSpace 解除注销特定命名空间的唯一标识符
func UnRegNameSpace(name any) {
delete(namespace, name)
}
// Get 获取全局唯一标识符
func Get() int64 {
global++
return global
return global.Add(1)
}
// Reset 重置全局唯一标识符
func Reset() {
global.Store(0)
}
// GetWith 获取特定命名空间的唯一标识符
func GetWith(name any) int64 {
namespace[name]++
return namespace[name]
return namespace[name].Add(1)
}
func GetSync() int64 {
mutex.Lock()
defer mutex.Unlock()
global++
return global
}
func GetSyncWith(name any) int64 {
mutex.Lock()
defer mutex.Unlock()
namespace[name]++
return namespace[name]
// ResetWith 重置特定命名空间的唯一标识符
func ResetWith(name any) {
namespace[name].Store(0)
}

20
utils/storage/data.go Normal file
View File

@ -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)
}

View File

@ -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))
}

View File

@ -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
}

View File

@ -1,10 +0,0 @@
package storage
// GlobalDataStorage 全局数据存储器接口
type GlobalDataStorage[T any] interface {
// Load 加载全局数据
// - 当全局数据不存在时,应当返回新的全局数据实例
Load(name string) T
// Save 保存全局数据
Save(name string, data T) error
}

View File

@ -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
}

View File

@ -1,7 +0,0 @@
package storage
import "github.com/kercylan98/minotaur/utils/generic"
type IndexDataItem[I generic.Ordered] interface {
GetIndex() I
}

View File

@ -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)
}

97
utils/storage/set.go Normal file
View File

@ -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
}

View File

@ -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, " "))
}
}

View File

@ -1,26 +1,11 @@
package storage
import "time"
import "github.com/kercylan98/minotaur/utils/generic"
var (
// globalDataSaveHandles 全局数据保存句柄
globalDataSaveHandles []func() error
)
type Storage[PrimaryKey generic.Ordered, Body any] interface {
// Load 加载数据
Load(index PrimaryKey) (Body, error)
// SaveAll 保存所有数据
// - errorHandle 错误处理中如果返回 false 将重试,否则跳过当前保存下一个
func SaveAll(errorHandle func(err error) bool, retryInterval time.Duration) {
var err error
for _, handle := range globalDataSaveHandles {
for {
if err = handle(); err != nil {
if !errorHandle(err) {
time.Sleep(retryInterval)
continue
}
break
}
break
}
}
// Save 保存数据
Save(set *Set[PrimaryKey, Body], index PrimaryKey, data any) error
}

View File

@ -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)
}
}

View File

@ -1 +0,0 @@
{"CreateAt":"2023-07-19T14:49:35.7235348+08:00","TotalCount":10}

View File

@ -1 +0,0 @@
{"ID":"INDEX_001","Value":10}

View File

@ -1,63 +0,0 @@
package storages
import (
"fmt"
"github.com/kercylan98/minotaur/utils/file"
"path/filepath"
)
const (
// GlobalDataFileDefaultSuffix 是 GlobalDataFileStorage 的文件默认后缀
GlobalDataFileDefaultSuffix = "stock"
)
// NewGlobalDataFileStorage 创建一个 GlobalDataFileStoragedir 是文件存储目录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)
}

View File

@ -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
}

View File

@ -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
}
}

View File

@ -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)
})
}

View File

@ -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()))
}
}

View File

@ -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
}

View File

@ -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
}
}

View File

@ -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)
})
}

View File

@ -1,9 +1,8 @@
package stream
import (
"github.com/kercylan98/minotaur/utils/asynchronous"
"github.com/kercylan98/minotaur/utils/concurrent"
"github.com/kercylan98/minotaur/utils/hash"
"github.com/kercylan98/minotaur/utils/synchronization"
"reflect"
)
@ -19,11 +18,6 @@ func WithMapCopy[K comparable, V any](m map[K]V) Map[K, V] {
return hash.Copy(m)
}
// WithHashMap 使用传入的 map 执行链式操作
func WithHashMap[K comparable, V any](m hash.Map[K, V]) Map[K, V] {
return m.Map()
}
// Map 提供了 map 的链式操作
type Map[K comparable, V any] map[K]V
@ -180,14 +174,9 @@ func (slf Map[K, V]) ToSliceStreamWithKey() Slice[K] {
return hash.KeyToSlice(slf)
}
// ToSyncMap 将当前 Map 转换为 synchronization.Map
func (slf Map[K, V]) ToSyncMap() *synchronization.Map[K, V] {
return synchronization.NewMap[K, V](synchronization.WithMapSource(slf))
}
// ToAsyncMap 将当前 Map 转换为 asynchronous.Map
func (slf Map[K, V]) ToAsyncMap() *asynchronous.Map[K, V] {
return asynchronous.NewMap[K, V](asynchronous.WithMapSource(slf))
// ToSyncMap 将当前 Map 转换为 concurrent.BalanceMap
func (slf Map[K, V]) ToSyncMap() *concurrent.BalanceMap[K, V] {
return concurrent.NewBalanceMap[K, V](concurrent.WithBalanceMapSource(slf))
}
// ToMap 将当前 Map 转换为 map

View File

@ -5,6 +5,7 @@ import (
)
var errorMapper = make(map[error]int)
var errorMapperRef = make(map[error]error)
// RegError 通过错误码注册错误,返回错误的引用
func RegError(code int, message string) error {
@ -16,7 +17,21 @@ func RegError(code int, message string) error {
return err
}
// GetErrorCode 通过错误引用获取错误码,如果错误不存在则返回 0
func GetErrorCode(err error) int {
return errorMapper[err]
// RegErrorRef 通过错误码注册错误,返回错误的引用
func RegErrorRef(code int, message string, ref error) error {
if code == 0 {
panic("error code can not be 0")
}
err := errors.New(message)
errorMapper[err] = code
errorMapperRef[ref] = err
return ref
}
// GetErrorCode 通过错误引用获取错误码,如果错误不存在则返回 0
func GetErrorCode(err error) (int, error) {
if ref, exist := errorMapperRef[err]; exist {
err = ref
}
return errorMapper[err], err
}

11
utils/super/unsafe.go Normal file
View File

@ -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))
}

View File

@ -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
}

View File

@ -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
}
}

View File

@ -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
}