diff --git a/game/builtin/poker.go b/game/builtin/poker.go new file mode 100644 index 0000000..de8fa47 --- /dev/null +++ b/game/builtin/poker.go @@ -0,0 +1,12 @@ +package builtin + +func NewPoker() { + +} + +type Poker struct { +} + +func (slf *Poker) Generate() { + +} diff --git a/game/builtin/poker_card.go b/game/builtin/poker_card.go new file mode 100644 index 0000000..0380820 --- /dev/null +++ b/game/builtin/poker_card.go @@ -0,0 +1,42 @@ +package builtin + +import "fmt" + +// NewPokerCard 创建一张扑克牌 +// - 当 point 为 PokerPointBlackJoker 或 PokerPointRedJoker 时,color 将没有效果 +func NewPokerCard(point PokerPoint, color PokerColor) PokerCard { + if point == PokerPointRedJoker || point == PokerPointBlackJoker { + color = PokerColorNone + } + card := PokerCard{ + point: point, + color: color, + } + return card +} + +// PokerCard 扑克牌 +type PokerCard struct { + point PokerPoint + color PokerColor +} + +// GetPoint 返回扑克牌的点数 +func (slf PokerCard) GetPoint() PokerPoint { + return slf.point +} + +// GetColor 返回扑克牌的花色 +func (slf PokerCard) GetColor() PokerColor { + return slf.color +} + +// GetPointAndColor 返回扑克牌的点数和花色 +func (slf PokerCard) GetPointAndColor() (PokerPoint, PokerColor) { + return slf.point, slf.color +} + +// String 将扑克牌转换为字符串形式 +func (slf PokerCard) String() string { + return fmt.Sprintf("(%s %s)", slf.point, slf.color) +} diff --git a/game/builtin/poker_card_pile.go b/game/builtin/poker_card_pile.go new file mode 100644 index 0000000..a44a9ee --- /dev/null +++ b/game/builtin/poker_card_pile.go @@ -0,0 +1,144 @@ +package builtin + +import ( + "fmt" + "github.com/kercylan98/minotaur/utils/hash" + "github.com/kercylan98/minotaur/utils/random" + "github.com/kercylan98/minotaur/utils/slice" + "sort" +) + +// NewPokerCardPile 返回一个新的牌堆,其中 size 表示了该牌堆由多少副牌组成 +// - 在不洗牌的情况下,默认牌堆顶部到底部为从大到小排列 +func NewPokerCardPile(size int, options ...PokerCardPileOption) *PokerCardPile { + pile := &PokerCardPile{ + size: size, + pile: make([]PokerCard, 0, size*54), + } + pile.shuffleHandle = func(cards []PokerCard) []PokerCard { + sort.Slice(cards, func(i, j int) bool { + return random.Float64() >= 0.5 + }) + return cards + } + for _, option := range options { + option(pile) + } + pile.Reset() + return pile +} + +// PokerCardPile 扑克牌堆 +type PokerCardPile struct { + pile []PokerCard + size int + shuffleHandle func(cards []PokerCard) []PokerCard + excludeColor map[PokerColor]struct{} + excludePoint map[PokerPoint]struct{} + excludeCard map[PokerPoint]map[PokerColor]struct{} +} + +// Reset 重置牌堆的扑克牌数量及顺序 +func (slf *PokerCardPile) Reset() { + var cards = make([]PokerCard, 0, 54) + if !slf.IsExclude(PokerPointRedJoker, PokerColorNone) { + cards = append(cards, NewPokerCard(PokerPointRedJoker, PokerColorNone)) + } + if !slf.IsExclude(PokerPointBlackJoker, PokerColorNone) { + cards = append(cards, NewPokerCard(PokerPointBlackJoker, PokerColorNone)) + } + for point := PokerPointK; point >= PokerPointA; point-- { + for color := PokerColorSpade; color <= PokerColorDiamond; color++ { + if !slf.IsExclude(point, color) { + cards = append(cards, NewPokerCard(point, color)) + } + } + } + slf.pile = slf.pile[0:0] + for i := 0; i < slf.size; i++ { + slf.pile = append(slf.pile, cards...) + } +} + +// IsExclude 检查特定点数和花色是否被排除在外 +func (slf *PokerCardPile) IsExclude(point PokerPoint, color PokerColor) bool { + return hash.Exist(slf.excludePoint, point) || hash.Exist(slf.excludeColor, color) || hash.Exist(slf.excludeCard[point], color) +} + +// IsExcludeWithCard 检查特定扑克牌是否被排除在外 +func (slf *PokerCardPile) IsExcludeWithCard(card PokerCard) bool { + point, color := card.GetPointAndColor() + return hash.Exist(slf.excludePoint, point) || hash.Exist(slf.excludeColor, color) || hash.Exist(slf.excludeCard[point], color) +} + +// Shuffle 洗牌 +func (slf *PokerCardPile) Shuffle() { + before := slf.Count() + cards := slf.shuffleHandle(slf.Cards()) + if len(cards) != before { + panic("the count after shuffling does not match the count before shuffling") + } + slf.pile = cards +} + +// Cards 获取当前牌堆的所有扑克牌 +func (slf *PokerCardPile) Cards() []PokerCard { + return slf.pile +} + +// IsFree 返回牌堆是否没有扑克牌了 +func (slf *PokerCardPile) IsFree() bool { + return len(slf.pile) == 0 +} + +// Count 获取牌堆剩余牌量 +func (slf *PokerCardPile) Count() int { + return len(slf.pile) +} + +// Pull 从牌堆特定位置抽出一张牌 +func (slf *PokerCardPile) Pull(index int) PokerCard { + 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)) + } + pc := slf.pile[index] + slice.Del(&slf.pile, index) + return pc +} + +// PullTop 从牌堆顶部抽出一张牌 +func (slf *PokerCardPile) PullTop() PokerCard { + if slf.IsFree() { + panic("empty poker cards pile") + } + pc := slf.pile[0] + slice.Del(&slf.pile, 0) + return pc +} + +// PullBottom 从牌堆底部抽出一张牌 +func (slf *PokerCardPile) PullBottom() PokerCard { + if slf.IsFree() { + panic("empty poker cards pile") + } + i := len(slf.pile) - 1 + pc := slf.pile[i] + slice.Del(&slf.pile, i) + return pc +} + +// Push 将扑克牌插入到牌堆特定位置 +func (slf *PokerCardPile) Push(index int, card PokerCard) { + slice.Insert(&slf.pile, index, card) + return +} + +// PushTop 将扑克牌插入到牌堆顶部 +func (slf *PokerCardPile) PushTop(card PokerCard) { + slf.pile = append([]PokerCard{card}, slf.pile...) +} + +// PushBottom 将扑克牌插入到牌堆底部 +func (slf *PokerCardPile) PushBottom(card PokerCard) { + slf.pile = append(slf.pile, card) +} diff --git a/game/builtin/poker_card_pile_options.go b/game/builtin/poker_card_pile_options.go new file mode 100644 index 0000000..124e5d6 --- /dev/null +++ b/game/builtin/poker_card_pile_options.go @@ -0,0 +1,54 @@ +package builtin + +type PokerCardPileOption func(pile *PokerCardPile) + +// WithPokerCardPileShuffle 通过特定的洗牌算法创建牌堆 +// - 需要保证洗牌后的牌堆剩余扑克数量与之前相同,否则将会引发 panic +func WithPokerCardPileShuffle(shuffleHandle func(pile []PokerCard) []PokerCard) PokerCardPileOption { + return func(pile *PokerCardPile) { + if shuffleHandle == nil { + return + } + pile.shuffleHandle = shuffleHandle + } +} + +// WithPokerCardPileExcludeColor 通过排除特定花色的方式创建牌堆 +func WithPokerCardPileExcludeColor(colors ...PokerColor) PokerCardPileOption { + return func(pile *PokerCardPile) { + if pile.excludeColor == nil { + pile.excludeColor = map[PokerColor]struct{}{} + } + for _, color := range colors { + pile.excludeColor[color] = struct{}{} + } + } +} + +// WithPokerCardPileExcludePoint 通过排除特定点数的方式创建牌堆 +func WithPokerCardPileExcludePoint(points ...PokerPoint) PokerCardPileOption { + return func(pile *PokerCardPile) { + if pile.excludePoint == nil { + pile.excludePoint = map[PokerPoint]struct{}{} + } + for _, point := range points { + pile.excludePoint[point] = struct{}{} + } + } +} + +// WithPokerCardPileExcludeCard 通过排除特定扑克牌的方式创建牌堆 +func WithPokerCardPileExcludeCard(cards ...PokerCard) PokerCardPileOption { + return func(pile *PokerCardPile) { + if pile.excludeCard == nil { + pile.excludeCard = map[PokerPoint]map[PokerColor]struct{}{} + } + for _, card := range cards { + cs, exist := pile.excludeCard[card.GetPoint()] + if !exist { + cs = map[PokerColor]struct{}{} + } + cs[card.GetColor()] = struct{}{} + } + } +} diff --git a/game/builtin/poker_card_pile_test.go b/game/builtin/poker_card_pile_test.go new file mode 100644 index 0000000..a9210b6 --- /dev/null +++ b/game/builtin/poker_card_pile_test.go @@ -0,0 +1,13 @@ +package builtin + +import ( + "fmt" + "testing" +) + +func TestNewPokerCardPile(t *testing.T) { + pile := NewPokerCardPile(2) + _ = pile.PullTop() + pile.Reset() + fmt.Println(pile) +} diff --git a/game/builtin/poker_color.go b/game/builtin/poker_color.go new file mode 100644 index 0000000..3e30c51 --- /dev/null +++ b/game/builtin/poker_color.go @@ -0,0 +1,35 @@ +package builtin + +const ( + PokerColorNone PokerColor = 0 // 无花色,通常为大小王 + PokerColorSpade PokerColor = 1 // 黑桃 + PokerColorHeart PokerColor = 2 // 红桃 + PokerColorClub PokerColor = 3 // 梅花 + PokerColorDiamond PokerColor = 4 // 方片 +) + +// PokerColor 扑克牌花色 +type PokerColor int + +// InBounds 扑克牌花色是否在界限内 +// - 将检查花色是否在黑桃、红桃、梅花、方片之间 +func (slf PokerColor) InBounds() bool { + return slf <= PokerColorSpade && slf >= PokerColorDiamond +} + +func (slf PokerColor) String() string { + var str string + switch slf { + case PokerColorSpade: + str = "Spade" + case PokerColorHeart: + str = "Heart" + case PokerColorClub: + str = "Club" + case PokerColorDiamond: + str = "Diamond" + default: + str = "None" + } + return str +} diff --git a/game/builtin/poker_point.go b/game/builtin/poker_point.go new file mode 100644 index 0000000..a3096f0 --- /dev/null +++ b/game/builtin/poker_point.go @@ -0,0 +1,45 @@ +package builtin + +import "strconv" + +const ( + PokerPointA PokerPoint = 1 + PokerPoint2 PokerPoint = 2 + PokerPoint3 PokerPoint = 3 + PokerPoint4 PokerPoint = 4 + PokerPoint5 PokerPoint = 5 + PokerPoint6 PokerPoint = 6 + PokerPoint7 PokerPoint = 7 + PokerPoint8 PokerPoint = 8 + PokerPoint9 PokerPoint = 9 + PokerPoint10 PokerPoint = 10 + PokerPointJ PokerPoint = 11 + PokerPointQ PokerPoint = 12 + PokerPointK PokerPoint = 13 + PokerPointBlackJoker PokerPoint = 14 + PokerPointRedJoker PokerPoint = 15 +) + +// PokerPoint 扑克点数 +type PokerPoint int + +func (slf PokerPoint) String() string { + var str string + switch slf { + case PokerPointA: + str = "A" + case PokerPointJ: + str = "J" + case PokerPointQ: + str = "Q" + case PokerPointK: + str = "K" + case PokerPointBlackJoker: + str = "B" + case PokerPointRedJoker: + str = "R" + default: + str = strconv.Itoa(int(slf)) + } + return str +} diff --git a/game/poker.go b/game/poker.go new file mode 100644 index 0000000..727d33f --- /dev/null +++ b/game/poker.go @@ -0,0 +1,5 @@ +package game + +// Poker 扑克牌游戏通用玩法接口定义 +type Poker interface { +} diff --git a/utils/geometry/position.go b/utils/geometry/position.go index 19a4ce2..51563b2 100644 --- a/utils/geometry/position.go +++ b/utils/geometry/position.go @@ -137,7 +137,6 @@ func PointToPos[V generic.SignedNumber](width V, xy Point[V]) V { // PosToCoordinate 通过宽度将一个二维数组的顺序位置转换为xy坐标 func PosToCoordinate[V generic.SignedNumber](width, pos V) (x, y V) { - x = V(math.Mod(float64(pos), float64(width))) y = pos / width return x, y diff --git a/utils/hash/hash.go b/utils/hash/hash.go new file mode 100644 index 0000000..4c52d52 --- /dev/null +++ b/utils/hash/hash.go @@ -0,0 +1,17 @@ +package hash + +// Exist 检查特定 key 是否存在 +func Exist[K comparable, V any](m map[K]V, key K) bool { + _, exist := m[key] + return exist +} + +// AllExist 检查多个 key 是否存在 +func AllExist[K comparable, V any](m map[K]V, keys ...K) bool { + for key := range m { + if _, exist := m[key]; !exist { + return false + } + } + return true +} diff --git a/utils/maths/math.go b/utils/maths/math.go index 9864792..0434643 100644 --- a/utils/maths/math.go +++ b/utils/maths/math.go @@ -109,3 +109,15 @@ func Clamp[V generic.Number](value, min, max V) V { func Tolerance[V generic.Number](value1, value2, tolerance V) bool { return V(math.Abs(float64(value1-value2))) <= tolerance } + +// Merge 通过一个参考值合并两个数字 +func Merge[V generic.SignedNumber](refer, a, b V) V { + return b*refer + a +} + +// UnMerge 通过一个参考值取消合并的两个数字 +func UnMerge[V generic.SignedNumber](refer, num V) (a, b V) { + a = V(math.Mod(float64(num), float64(refer))) + b = num / refer + return a, b +}