四川麻将胡牌规则,参考腾讯麻将“血流成河”规则
详细代码如下:
package main
import (
"bytes"
"fmt"
"strings"
)
func main() {
var (
tile = []string{
"56756744422222m", // 超过4张牌,记为4张
"675456234m32155p4s", // 没有缺一门,可以打一张后听牌
"112233m11224455p", // 七对
"11223355667799s", // 清七对
"1122335566s3333m", // 龙七对
"11m111222555666p", // 碰碰胡
"11122255566677p", // 清碰
"567567444222m3m3m", // 清一色
"1111333345667899m", // 两个杠的牌
"111133335555777799m", // 清十八罗汉
"22244455667m3p3p", // 计算听牌
"22244455667m43p3p", // 打一张,计算听哪些牌
"2224445666789m", // 计算听牌
"12224445666789m", // 打一张,计算听哪些牌
}
)
for _, t := range tile {
tt := StrToTile(&t) // 字符串转为tile34,更新字符串
fmt.Printf("当前牌: [%s]\n", t)
fmt.Println(MahjongSolution(tt))
fmt.Println("-------------------------------")
}
}
const TileMax = 34
func StrToTile(s *string) []int {
tile := make([]int, TileMax)
if s == nil || len(*s) < 2 {
*s = "" // 至少2个字符1张牌,不满足则置为空
return tile
}
var (
ok = false
i = len(*s) - 1
si = -1
ni int
)
for ; i >= 0; i-- {
switch c := (*s)[i]; c {
case 'm':
si = 0 // [0,8]m = [1,9]万
case 'p':
si = 9 // [9,18]p = [1,9]筒
case 's':
si = 18 // [18,26]s = [1,9]条
case 'z':
si = 27 // [27,33]z = [1,7] = 东,南,西,北,白,发,中
case '1', '2', '3', '4', '5', '6', '7', '8', '9':
if si >= 0 {
if ni = int(c-'1') + si; ni < TileMax && tile[ni] < 4 {
tile[ni]++
ok = true
}
}
}
}
if ok {
*s = TileToStr(tile) // 有牌时序列化并排序
} else {
*s = "" // 无牌时置为空
}
return tile
}
func TileToStr(tile []int) string {
var (
i, j int
lt = len(tile) // 支持lt<34的不完全入参
tmp strings.Builder
)
ok := false // 拼接万牌
for i = 0; i < 9 && i < lt; i++ {
for j = tile[i]; j > 0; j-- {
tmp.WriteByte(byte(i + '1'))
ok = true
}
}
if ok {
tmp.WriteByte('m')
}
ok = false // 拼接筒牌
for i = 9; i < 18 && i < lt; i++ {
for j = tile[i]; j > 0; j-- {
tmp.WriteByte(byte(i - 9 + '1'))
ok = true
}
}
if ok {
tmp.WriteByte('p')
}
ok = false // 拼接条牌
for i = 18; i < 27 && i < lt; i++ {
for j = tile[i]; j > 0; j-- {
tmp.WriteByte(byte(i - 18 + '1'))
ok = true
}
}
if ok {
tmp.WriteByte('s')
}
ok = false // 拼接字牌
for i = 27; i < TileMax && i < lt; i++ {
for j = tile[i]; j > 0; j-- {
tmp.WriteByte(byte(i - 27 + '1'))
ok = true
}
}
if ok {
tmp.WriteByte('z')
}
return tmp.String()
}
func NumToTile(v int) []byte {
if v < 0 || v >= TileMax {
return []byte{'0', '-'} // 不合法数据
}
if v >= 27 {
return []byte{byte(v - 27 + '1'), 'z'}
}
if v >= 18 {
return []byte{byte(v - 18 + '1'), 's'}
}
if v >= 9 {
return []byte{byte(v - 9 + '1'), 'p'}
}
return []byte{byte(v + '1'), 'm'}
}
type MahjongResult struct {
IsSame bool // 是否清一色
Type int // 牌类型
Jiang int // 将牌
NumKe int // 刻子数量
NumShun int // 顺子数量
NumGang int // 杠数量
ArrayKe []int // 刻子数组
ArrayShun []int // 顺子数组
ArrayGang []int // 杠数组
}
func (mr *MahjongResult) String() string {
b := bytes.NewBufferString("{")
nv := NumToTile(mr.Jiang)
if nv[1] == '-' {
return "" // 将不合法
}
if mr.IsSame {
b.WriteString("清一色,")
}
switch mr.Type {
case 1:
b.WriteString("七对}")
return b.String()
case 2:
b.WriteString("龙七对}")
return b.String()
}
b.WriteByte(nv[0])
b.WriteByte(nv[0])
b.WriteByte(nv[1])
b.WriteByte(',')
for _, v := range mr.ArrayKe {
nv = NumToTile(v)
b.WriteByte(nv[0])
b.WriteByte(nv[0])
b.WriteByte(nv[0])
b.WriteByte(nv[1])
b.WriteByte(',')
}
for _, v := range mr.ArrayShun {
nv = NumToTile(v)
b.WriteByte(nv[0])
b.WriteByte(nv[0] + 1)
b.WriteByte(nv[0] + 2)
b.WriteByte(nv[1])
b.WriteByte(',')
}
for _, v := range mr.ArrayGang {
nv = NumToTile(v)
b.WriteByte(nv[0])
b.WriteByte(nv[0])
b.WriteByte(nv[0])
b.WriteByte(nv[0])
b.WriteByte(nv[1])
b.WriteByte(',')
}
// 移除最后1个','号,并将结果括起来
b.Truncate(b.Len() - 1)
b.WriteByte('}')
return b.String()
}
func MahjongWin(tile []int) (res []*MahjongResult) {
color, cnt := 0, [5]int{}
for i, v := range tile {
if v <= 0 || v > 4 {
tile[i] = 0 // 纠正数量错误的牌
continue
}
nv := NumToTile(i)
cnt[v]++ // 记录对应数量牌的个数
cnt[0] += v // 记录牌总数
switch nv[1] {
case 's':
color |= 4 // 存在条
case 'p':
color |= 2 // 存在筒
case 'm':
color |= 1 // 存在万
case 'z', '-':
return nil // 四川麻将没有字牌
}
}
isSame := color == 1 || color == 2 || color == 4 // 清一色
if cnt[0] == 14 {
if cnt[2] == 7 {
return []*MahjongResult{{
IsSame: isSame,
Type: 1, // 七对
}}
}
if cnt[2] == 5 && cnt[4] == 1 {
// 注意,4张的牌不能杠出和碰出
return []*MahjongResult{{
IsSame: isSame,
Type: 2, // 龙七对
}}
}
}
var (
appNum = []func(tp []int, k, s, g *[]int){
func(tp []int, k, s, g *[]int) {
for j := 0; j < TileMax; j++ {
if tp[j] >= 3 {
tp[j] -= 3 // 取刻子
*k = append(*k, j)
}
}
},
func(tp []int, k, s, g *[]int) {
var a, b, c int
for a = 0; a < 3; a++ {
for b = 0; b < 7; {
c = 9*a + b
if tp[c] >= 1 && tp[c+1] >= 1 && tp[c+2] >= 1 {
tp[c]--
tp[c+1]--
tp[c+2]-- // 取顺子
*s = append(*s, c)
} else {
b++
}
}
}
},
func(tp []int, k, s, g *[]int) {
for j := 0; j < TileMax; j++ {
if tp[j] >= 4 {
tp[j] = 0 // 取杠牌
*g = append(*g, j)
}
}
},
}
tp = make([]int, len(tile))
mp = make(map[string]struct{})
)
for i := 0; i < TileMax; i++ {
if tile[i] < 2 {
continue // 跳过不能做雀头的牌
}
for _, an := range [][]int{
{0, 1}, // 刻子,顺子
{1, 0}, // 顺子,刻子
{2, 0, 1}, // 杠,刻子,顺子
{2, 1, 0}, // 杠,顺子,刻子
} {
copy(tp, tile)
tp[i] -= 2 // 取雀头
var keNum, shunNum, gangNum []int
for _, anv := range an {
appNum[anv](tp, &keNum, &shunNum, &gangNum)
}
ok := true
for _, vt := range tp {
if vt != 0 {
ok = false
break
}
}
if ok { // 胡牌,记录牌型,结果去重
key := fmt.Sprintf("%d%v%v%v", i, keNum, shunNum, gangNum)
if _, ok = mp[key]; !ok {
res = append(res, &MahjongResult{
IsSame: isSame,
Type: 3,
NumKe: len(keNum),
NumShun: len(shunNum),
NumGang: len(gangNum),
Jiang: i,
ArrayKe: keNum,
ArrayShun: shunNum,
ArrayGang: gangNum,
})
mp[key] = struct{}{}
}
}
}
}
return
}
func MahjongSolution(tile []int) string {
status := MahjongWin(tile)
if len(status) > 0 {
return fmt.Sprint(status)
}
ting := func() (res []string) {
for i, v := range tile {
if v < 4 {
tile[i]++
status = MahjongWin(tile)
tile[i]--
if len(status) > 0 {
nv := NumToTile(i) // 假设得到这张牌可以胡牌则听这张牌
res = append(res, fmt.Sprintf("%v听%c%c", status, nv[0], nv[1]))
}
}
}
return
}
resp := ting() // 当前听哪些牌
for i, v := range tile {
if v > 0 {
nv := NumToTile(i)
tile[i]--
status = MahjongWin(tile)
if len(status) > 0 { // 假设打出这张牌,直接胡牌(正常打牌不会有这种情况)
resp = append(resp, fmt.Sprintf("打%c%c, %v", nv[0], nv[1], status))
}
res := ting()
tile[i]++
for _, rv := range res { // 假设打出这张牌,能听哪些牌
resp = append(resp, fmt.Sprintf("打%c%c, %s", nv[0], nv[1], rv))
}
}
}
return strings.Join(resp, "\n")
}
结果如下:
当前牌: [2222444556677m]
[{清一色,44m,222m,234m,567m,567m} {清一色,77m,222m,234m,456m,456m}]听3m
打4m, [{清一色,44m,567m,567m,2222m} {清一色,77m,456m,456m,2222m}]
打7m, [{清一色,44m,456m,567m,2222m}]
-------------------------------
当前牌: [234455667m12355p4s]
打4s, [{55p,234m,456m,567m,123p}]
-------------------------------
当前牌: [112233m11224455p]
[{七对}]
-------------------------------
当前牌: [11223355667799s]
[{清一色,七对}]
-------------------------------
当前牌: [3333m1122335566s]
[{龙七对}]
-------------------------------
当前牌: [11m111222555666p]
[{11m,111p,222p,555p,666p}]
-------------------------------
当前牌: [11122255566677p]
[{清一色,77p,111p,222p,555p,666p}]
-------------------------------
当前牌: [22233444556677m]
[{清一色,33m,222m,444m,567m,567m}]
-------------------------------
当前牌: [1111333345667899m]
[{清一色,99m,456m,678m,1111m,3333m}]
-------------------------------
当前牌: [111133335555777799m]
[{清一色,99m,1111m,3333m,5555m,7777m}]
-------------------------------
当前牌: [22244455667m33p]
[{33p,222m,444m,456m,567m}]听4m
[{33p,222m,444m,567m,567m}]听7m
[{44m,222m,333p,456m,567m}]听3p
-------------------------------
当前牌: [22244455667m334p]
打3p, [{44m,222m,456m,567m,234p}]听2p
打3p, [{44m,222m,456m,567m,345p}]听5p
打4p, [{33p,222m,444m,456m,567m}]听4m
打4p, [{33p,222m,444m,567m,567m}]听7m
打4p, [{44m,222m,333p,456m,567m}]听3p
-------------------------------
当前牌: [2224445666789m]
[{清一色,44m,222m,666m,345m,789m}]听3m
[{清一色,66m,222m,444m,456m,789m}]听4m
[{清一色,55m,222m,444m,666m,789m}]听5m
[{清一色,44m,222m,666m,456m,789m}]听6m
[{清一色,66m,222m,444m,567m,789m}]听7m
-------------------------------
当前牌: [12224445666789m]
打1m, [{清一色,44m,222m,666m,345m,789m}]听3m
打1m, [{清一色,66m,222m,444m,456m,789m}]听4m
打1m, [{清一色,55m,222m,444m,666m,789m}]听5m
打1m, [{清一色,44m,222m,666m,456m,789m}]听6m
打1m, [{清一色,66m,222m,444m,567m,789m}]听7m
打5m, [{清一色,11m,222m,444m,666m,789m}]听1m
打5m, [{清一色,22m,444m,666m,123m,789m}]听3m
-------------------------------
文章评论