扑克牌堆实现

This commit is contained in:
kercylan98 2023-06-21 19:28:31 +08:00
parent f414ffe28c
commit 52d707486a
11 changed files with 379 additions and 1 deletions

12
game/builtin/poker.go Normal file
View File

@ -0,0 +1,12 @@
package builtin
func NewPoker() {
}
type Poker struct {
}
func (slf *Poker) Generate() {
}

View File

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

View File

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

View File

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

View File

@ -0,0 +1,13 @@
package builtin
import (
"fmt"
"testing"
)
func TestNewPokerCardPile(t *testing.T) {
pile := NewPokerCardPile(2)
_ = pile.PullTop()
pile.Reset()
fmt.Println(pile)
}

View File

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

View File

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

5
game/poker.go Normal file
View File

@ -0,0 +1,5 @@
package game
// Poker 扑克牌游戏通用玩法接口定义
type Poker interface {
}

View File

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

17
utils/hash/hash.go Normal file
View File

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

View File

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