├── README.md ├── computer └── card_computer.go ├── enum └── enum_car_type.go ├── main.go ├── model └── model_card_show.go └── util └── common_util.go /README.md: -------------------------------------------------------------------------------- 1 | #### 牌面协议 2 | A:黑桃 B:红桃 C:梅花 D:方片 3 | 4 | |扑克原始值|映射值| 5 | |----|----| 6 | |3-10|3-10数字| 7 | |J|11| 8 | |Q|12| 9 | |K|13| 10 | |A|14| 11 | |2|15| 12 | |小王|Q88| 13 | |大王|K99| 14 | 15 | #### 构造一副牌 16 | ``` 17 | computer.CreateNew() 18 | ``` 19 | #### 洗牌 20 | ``` 21 | initValues := card.CreateNew() 22 | computer.Shuffer(initValues) 23 | ``` 24 | #### 按序发牌 25 | ``` 26 | initValues := card.CreateNew() 27 | computer.Shuffle(initValues) 28 | fmt.Println("玩家1:", computer.Dispacther(0, initValues)) 29 | fmt.Println("玩家2:", computer.Dispacther(1, initValues)) 30 | fmt.Println("玩家3:", computer.Dispacther(2, initValues)) 31 | fmt.Println("底牌:", computer.Dispacther(3, initValues)) 32 | ``` 33 | 34 | #### 出牌类型枚举 35 | ``` 36 | type CardTypeStatus int 37 | 38 | const ( 39 | _CardTypeStatus = iota 40 | SINGLE //单根 41 | DOUBLE //对子 42 | THREE //三不带 43 | THREE_AND_ONE //三带一 44 | BOMB //炸弹 45 | FOUR_TWO //四带二 46 | PLANE //飞机 47 | PLANE_EMPTY //三不带飞机 48 | DOUBLE_ALONE //连对 49 | SINGLE_ALONE //顺子 50 | KING_BOMB //王炸 51 | ERROR_TYPE //非法类型 52 | 53 | ) 54 | ``` 55 | #### 计算模型 56 | ``` 57 | type CardShow struct { 58 | ShowValue []string //牌面数组 59 | CardMap map[int]int //牌面计算结果 60 | MaxCount int //同值牌出现的最大次数 61 | MaxValues []int //同值牌出现的次数列表 62 | CompareValue int //用于比较大小的值 63 | CardTypeStatus enum.CardTypeStatus //牌面类型 64 | } 65 | 66 | ``` 67 | -------------------------------------------------------------------------------- /computer/card_computer.go: -------------------------------------------------------------------------------- 1 | package computer 2 | 3 | import ( 4 | "doudizhu/enum" 5 | "doudizhu/model" 6 | "doudizhu/util" 7 | "fmt" 8 | "math/rand" 9 | "sort" 10 | "strconv" 11 | "time" 12 | ) 13 | 14 | /** 15 | 构造一副牌 16 | */ 17 | func CreateNew() []string { 18 | numbers := make([]string, 54) 19 | start := 0 20 | for i := 3; i <= 16; i++ { 21 | fmt.Println(start) 22 | if i == 16 { 23 | fmt.Println("end start : " + strconv.Itoa(start)) 24 | numbers[start] = "Q88" 25 | numbers[start+1] = "K99" 26 | } else { 27 | numbers[start] = "A" + strconv.Itoa(i) 28 | numbers[start+1] = "B" + strconv.Itoa(i) 29 | numbers[start+2] = "C" + strconv.Itoa(i) 30 | numbers[start+3] = "D" + strconv.Itoa(i) 31 | start += 4 32 | } 33 | } 34 | return numbers 35 | } 36 | 37 | /** 38 | 洗牌 39 | */ 40 | func Shuffle(vals []string) { 41 | r := rand.New(rand.NewSource(time.Now().Unix())) 42 | for len(vals) > 0 { 43 | n := len(vals) 44 | randIndex := r.Intn(n) 45 | vals[n-1], vals[randIndex] = vals[randIndex], vals[n-1] 46 | vals = vals[:n-1] 47 | } 48 | } 49 | 50 | /** 51 | *发牌 52 | *order==0 玩家1次序 53 | *order==1 玩家2次序 54 | *order==2 玩家3次序 55 | *order==3 底牌次序 56 | */ 57 | func Dispacther(order int, vals []string) []string { 58 | var playCards []string 59 | if order < 0 || order > 3 { 60 | return []string{} 61 | } else { 62 | size := 17 63 | if order == 3 { 64 | size = 3 65 | } 66 | for i := 0; i < size; i++ { 67 | playCards = append(playCards, vals[order*17+i]) 68 | } 69 | } 70 | return playCards 71 | } 72 | 73 | /** 74 | * 根据牌面数量判断牌面类型 75 | */ 76 | func ParseCardsInSize(plays []string) model.CardShow { 77 | cardShow := model.CardShow{ 78 | ShowValue: plays, 79 | ShowTime: util.GetNowTime(), 80 | } 81 | switch len(plays) { 82 | case 1: 83 | cardShow.CardTypeStatus = enum.SINGLE 84 | cardShow.CompareValue = GetCardValue(plays[0]) 85 | cardShow.MaxCount = 1 86 | cardShow.MaxValues = []int{cardShow.CompareValue} 87 | fmt.Printf("根%d", GetCardValue(plays[0])) 88 | break 89 | case 2: 90 | if plays[0] == "Q88" && plays[1] == "K99" { 91 | cardShow.CardTypeStatus = enum.KING_BOMB 92 | cardShow.CompareValue = GetCardValue(plays[0]) 93 | cardShow.MaxCount = 2 94 | cardShow.MaxValues = []int{cardShow.CompareValue} 95 | fmt.Println("王炸") 96 | } else { 97 | ParseCardsType(plays, &cardShow) 98 | } 99 | break 100 | } 101 | if len(plays) > 2 { 102 | ParseCardsType(plays, &cardShow) 103 | } else { 104 | cardShow.CardTypeStatus = enum.ERROR_TYPE 105 | } 106 | return cardShow 107 | } 108 | 109 | /** 110 | * 获取牌面类型 111 | */ 112 | func ParseCardsType(cards []string, cardShow *model.CardShow) { 113 | mapCard, maxCount, maxValues := ComputerValueTimes(cards) 114 | cardShow.MaxCount = maxCount 115 | cardShow.MaxValues = maxValues 116 | cardShow.CardMap = mapCard 117 | cardShow.CompareValue = maxValues[len(maxValues)-1] 118 | switch maxCount { 119 | case 4: 120 | if maxCount == len(cards) { 121 | cardShow.CardTypeStatus = enum.KING_BOMB 122 | fmt.Println("炸弹") 123 | } else if len(cards) == 6 { 124 | cardShow.CardTypeStatus = enum.FOUR_TWO 125 | fmt.Println("四带二") 126 | } else { 127 | cardShow.CardTypeStatus = enum.ERROR_TYPE 128 | fmt.Println("不合法出牌") 129 | } 130 | break 131 | case 3: 132 | alive := len(cards) - len(maxValues)*maxCount 133 | if len(maxValues) == alive { 134 | if len(maxValues) == 1 { 135 | cardShow.CardTypeStatus = enum.THREE_AND_ONE 136 | fmt.Println("三带一") 137 | } else if len(maxValues) > 1 { 138 | if IsContinuity(mapCard, false) { 139 | cardShow.CardTypeStatus = enum.PLANE 140 | fmt.Printf("飞机%d", len(maxValues)) 141 | } else { 142 | cardShow.CardTypeStatus = enum.ERROR_TYPE 143 | fmt.Println("非法飞机") 144 | } 145 | } 146 | } else if alive == 0 { 147 | if len(maxValues) > 1 { 148 | if IsContinuity(mapCard, false) { 149 | cardShow.CardTypeStatus = enum.PLANE_EMPTY 150 | fmt.Printf("三不带飞机%d", len(maxValues)) 151 | } else { 152 | cardShow.CardTypeStatus = enum.ERROR_TYPE 153 | fmt.Println("非法三不带飞机") 154 | } 155 | 156 | } else { 157 | cardShow.CardTypeStatus = enum.THREE 158 | fmt.Println("三不带") 159 | } 160 | } else { 161 | cardShow.CardTypeStatus = enum.ERROR_TYPE 162 | fmt.Println("不合法飞机或三带一") 163 | } 164 | break 165 | case 2: 166 | if len(maxValues) == (len(cards) / 2) { 167 | if len(maxValues) > 1 { 168 | if IsContinuity(mapCard, false) && len(maxValues) > 2 { 169 | cardShow.CardTypeStatus = enum.DOUBLE_ALONE 170 | fmt.Printf("%d连队", len(maxValues)) 171 | } else { 172 | cardShow.CardTypeStatus = enum.ERROR_TYPE 173 | fmt.Println("非法连对") 174 | } 175 | } else if len(maxValues) == 1 { 176 | cardShow.CardTypeStatus = enum.DOUBLE 177 | fmt.Printf("对%d", GetCardValue(cards[0])) 178 | } 179 | } else { 180 | cardShow.CardTypeStatus = enum.ERROR_TYPE 181 | fmt.Println("不合法出牌") 182 | } 183 | break 184 | case 1: 185 | if IsContinuity(mapCard, true) && len(cards) >= 5 { 186 | cardShow.CardTypeStatus = enum.SINGLE_ALONE 187 | fmt.Printf("%d顺子", len(mapCard)) 188 | } else { 189 | cardShow.CardTypeStatus = enum.ERROR_TYPE 190 | fmt.Println("非法顺子") 191 | } 192 | break 193 | } 194 | } 195 | 196 | /** 197 | * 获取顺序的key值数组 198 | */ 199 | func GetOrderKeys(cardMap map[int]int, isSingle bool) []int { 200 | var keys []int 201 | for key, value := range cardMap { 202 | if (!isSingle && value > 1) || isSingle { 203 | keys = append(keys, key) 204 | } 205 | } 206 | sort.Ints(keys) 207 | return keys 208 | } 209 | 210 | /** 211 | * 计算牌面值是否连续 212 | */ 213 | func IsContinuity(cardMap map[int]int, isSingle bool) bool { 214 | keys := GetOrderKeys(cardMap, isSingle) 215 | lastKey := 0 216 | for i := 0; i < len(keys); i++ { 217 | if (lastKey > 0 && (keys[i]-lastKey) != 1) || keys[i] == 15 { 218 | return false 219 | } 220 | lastKey = keys[i] 221 | } 222 | if lastKey > 0 { 223 | return true 224 | } else { 225 | return false 226 | } 227 | } 228 | 229 | /** 230 | * 计算每张牌面出现的次数 231 | * mapCard 标记结果 232 | * MaxCount 出现最多的次数 233 | * MaxValues 出现次数最多的所有值 234 | */ 235 | func ComputerValueTimes(cards []string) (mapCard map[int]int, MaxCount int, MaxValues []int) { 236 | newMap := make(map[int]int) 237 | if len(cards) == 0 { 238 | return newMap, 0, nil 239 | } 240 | for _, value := range cards { 241 | cardValue := GetCardValue(value) 242 | if newMap[cardValue] != 0 { 243 | newMap[cardValue]++ 244 | } else { 245 | newMap[cardValue] = 1 246 | } 247 | } 248 | var allCount []int //所有的次数 249 | var maxCount int //出现最多的次数 250 | for _, value := range newMap { 251 | allCount = append(allCount, value) 252 | } 253 | maxCount = allCount[0] 254 | for i := 0; i < len(allCount); i++ { 255 | if maxCount < allCount[i] { 256 | maxCount = allCount[i] 257 | } 258 | } 259 | var maxValue []int 260 | for key, value := range newMap { 261 | if value == maxCount { 262 | maxValue = append(maxValue, key) 263 | } 264 | } 265 | sort.Ints(maxValue) 266 | return newMap, maxCount, maxValue 267 | } 268 | 269 | /** 270 | * 获取牌面值 271 | */ 272 | func GetCardValue(card string) int { 273 | stringValue := util.Substring(card, 1, len(card)) 274 | value, err := strconv.Atoi(stringValue) 275 | if err == nil { 276 | return value 277 | } 278 | return -1 279 | } 280 | -------------------------------------------------------------------------------- /enum/enum_car_type.go: -------------------------------------------------------------------------------- 1 | package enum 2 | 3 | type CardTypeStatus int 4 | 5 | const ( 6 | _CardTypeStatus = iota 7 | SINGLE //单根 8 | DOUBLE //对子 9 | THREE //三不带 10 | THREE_AND_ONE //三带一 11 | BOMB //炸弹 12 | FOUR_TWO //四带二 13 | PLANE //飞机 14 | PLANE_EMPTY //三不带飞机 15 | DOUBLE_ALONE //连对 16 | SINGLE_ALONE //顺子 17 | KING_BOMB //王炸 18 | ERROR_TYPE //非法类型 19 | 20 | ) 21 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "doudizhu/computer" 5 | "fmt" 6 | ) 7 | 8 | func main() { 9 | initValues := computer.CreateNew() 10 | computer.Shuffle(initValues) 11 | dispacherV := computer.Dispacther(2, initValues) 12 | fmt.Println(dispacherV) 13 | cardsA := []string{"A3", "B3", "C3", "A4", "B4", "C4", "A5", "B5", "A5", "A6", "B6", "A6", "A11", "A7", "B12", "B7"} 14 | ashowMode := computer.ParseCardsInSize(cardsA) 15 | cardsB := []string{"A4", "B4", "C4", "A5", "B5", "C5", "A6", "B6", "A6", "A7", "B7", "A7", "A11", "A10", "B12", "13"} 16 | bshowMode := computer.ParseCardsInSize(cardsB) 17 | fmt.Println("\nA玩家:", ashowMode.CompareValue) 18 | fmt.Println("B玩家:", bshowMode.CompareValue) 19 | } 20 | -------------------------------------------------------------------------------- /model/model_card_show.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | import ( 4 | "doudizhu/enum" 5 | "time" 6 | ) 7 | 8 | type CardShow struct { 9 | Id int //出牌Id 10 | ShowTime time.Time 11 | ShowValue []string //牌面数组 12 | CardMap map[int]int //牌面计算结果 13 | MaxCount int //同值牌出现的最大次数 14 | MaxValues []int //同值牌出现的次数列表 15 | CompareValue int //用于比较大小的值 16 | CardTypeStatus enum.CardTypeStatus //牌面类型 17 | } 18 | -------------------------------------------------------------------------------- /util/common_util.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import "time" 4 | 5 | /** 6 | * 获取当前时区时间 7 | */ 8 | func GetNowTime() time.Time { 9 | l, _ := time.LoadLocation("Asia/Shanghai") 10 | return time.Now().In(l) 11 | } 12 | 13 | /** 14 | *截取字符串 15 | */ 16 | func Substring(source string, start int, end int) string { 17 | var r = []rune(source) 18 | length := len(r) 19 | 20 | if start < 0 || end > length || start > end { 21 | return "" 22 | } 23 | 24 | if start == 0 && end == length { 25 | return source 26 | } 27 | 28 | return string(r[start:end]) 29 | } 30 | --------------------------------------------------------------------------------