因为近期才学习了golang,想做一个小的项目进行实战一下;(实力有限,仅供参考)
设计规则
写一个模拟胡牌的方法,具体游戏规则,用户只能通过自摸来胡,没有对手,这个方法需要完成下面几个功能:
1、从一副牌中随机摸牌每次一张
2、判断是否胡牌以及胡牌的类型
3、控制胡牌几率,例如只有用户摸到 25 张以后胡牌几率会大一点
牌库:
万到九万*4 张
饼到九饼*4 张
条到九条*4 张
发中白东南西北*4 张
胡牌规则:普通胡牌,碰碰胡,7小对,一条龙,十三幺,清一色
存储麻将的结构体
一个好的数据结构体,在之后的游戏规则中会十分的方便;
这里想到了两种创建存放麻将数据
一、使用结构体struct
以下是麻将的定义:
// 定义牌的常量
const (
MaxCardValue = 9
MaxCardCount = 4
)
// 牌的类型
const (
CardTypeWan = iota // 万
CardTypeBing // 饼
CardTypeTiao // 条
CardTypeZi // 字牌(发中白东南西北)
)
// 牌的结构体
type Card struct {
Type int // 牌类型(万、饼、条、字牌)
Value int // 牌值(1-9)
}
以下是玩家手牌和胡牌类型的定义:
// 玩家结构体
type Player struct {
HandCards []*Card // 手牌
UniversalCard *Card // 万能牌
Quaternion [] int // 杠牌
}
// 胡牌类型
const (
HuTypeNormal = "普通胡牌"
HuTypeDuiDui = "对对胡"
HuTypeQiXiaoDui = "7小对"
HuTypeYiTiaoLong = "一条龙"
HuTypeShiSanYao = "十三幺"
HuTypeQingYiSe = "清一色"
)
那么一个基本的数据结构体就创建成功了
现在进行麻将牌库的创建和抽牌
// 创建一副新牌
func createDeck() []*Card {
deck := make([]*Card, 0)
// 添加普通牌
for i := 1; i <= MaxCardValue; i++ {
for j := 0; j < MaxCardCount; j++ {
deck = append(deck, &Card{Type: CardTypeWan, Value: i})
deck = append(deck, &Card{Type: CardTypeBing, Value: i})
deck = append(deck, &Card{Type: CardTypeTiao, Value: i})
}
}
// 添加字牌
zis := []int{1, 2, 3, 4, 5, 6, 7}
for _, z := range zis {
for j := 0; j < MaxCardCount; j++ {
deck = append(deck, &Card{Type: CardTypeZi, Value: z})
}
}
return deck
}
// 从牌库中随机抽一张牌
func drawCard(deck []*Card) *Card {
if len(deck) == 0 {
return nil
}
rand.Seed(time.Now().UnixNano())
idx := rand.Intn(len(deck))
card := deck[idx]
deck = append(deck[:idx], deck[idx+1:]...)
return card
}
以上就是使用struct创建基础牌库的方式,但是使用这种方式创建,在之后的排序查询和规则的创建是很麻烦的;
二、使用数组直接创建牌库的方式
/*
// [ 1- 9]壹万~玖万的个数
// [11-19]壹筒~玖筒的个数
// [21-29]壹条~玖条的个数
// [31-37]东南西北中发白的个数
*/
var CARD = []int{
1,1,1,1,
2,2,2,2,
3,3,3,3,
4,4,4,4,
5,5,5,5,
6,6,6,6,
7,7,7,7,
8,8,8,8,
9,9,9,9,
11,11,11,11,
12,12,12,12,
13,13,13,13,
14,14,14,14,
15,15,15,15,
16,16,16,16,
17,17,17,17,
18,18,18,18,
19,19,19,19,
21,21,21,21,
22,22,22,22,
23,23,23,23,
24,24,24,24,
25,25,25,25,
26,26,26,26,
27,27,27,27,
28,28,28,28,
29,29,29,29,
31,31,31,31,
32,32,32,32,
33,33,33,33,
34,34,34,34,
35,35,35,35,
36,36,36,36,
37,37,37,37,
};
胡牌类型
麻将的胡牌类型为普通胡牌和特定格式胡牌,不管什么胡牌方式在手牌中都必须存在一个对子
普通胡牌:(11 -> 对子,123 -> 顺子,111->刻子)
11、123、123、123、123。
11、123、123、123、111
11、123、123、111、111。
11、123、111、111、111。
11、111、111、111、111。
和牌的特殊牌型
(七小对)。
11、11、11、11、11、11、11
(十三幺)。
1万、9万、1条、9条、1筒、9筒、东、南、西、北、中、发、白、+(前面任意一张牌形成一对)
(一条龙)。
1、2、3、4、5、6、7、8、9、111(123)、11
普通胡牌的设计
胡牌规则中要注意的就是普通胡牌,因为在胡牌中可以不止有一对,会存在假对;
1万、2万、2万、3万、3万、4万、1筒、2筒、3筒、6筒、6筒、6筒、中、中
2万、3万、这种就为假对,所以我们在设计胡牌类型中一定要考虑这种问题;
解决方案:
1、将手牌中所有的对牌位置找出来存放数组arr中
2、循环arr数组,在手牌中剔除一个对牌
3、再剔除手牌中所有的 111 刻牌
4、看剩下的牌中是否满足 123 类型的顺牌
关于:用户摸到 25 张以后胡牌几率会大一点
设计思路:
1、手牌库中添加玩家打出的牌(视为玩家不需要的牌数组 outArr)
2、改变摸牌方式,第3回合在牌库中摸牌,第4次在摸牌从 outArr 中摸牌
3、第4次摸牌,需要在牌库中检测是否存在(由于牌库为顺序存储,可以使用二分查找法)
文件夹列表
.
├── main.go
└── src
├── mahjong
│ └── mahjong.go
├── mycard
│ └── mycard.go
└── rule
└── rule.go
Linux下golang配置
$ tail ~/.bashrc
export GOROOT=/usr/local/go
export GOPATH=/home/yonghu/go_test
export PATH=$PATH:$GOROOT/bin:$GOPATH/bin
main.go
package main
import (
"fmt"
"mahjong"
"mycard"
"rule"
)
type Start struct {
// 创建牌库
Card_Lib *mahjong.Mahjong
// 手牌
My_Card *mycard.MyCard
}
// 抽取13张基础卡牌
func (this *Start) InitCard() {
for i := 0; i < 13; i++ {
this.SetMyCard()
fmt.Println()
}
fmt.Println()
}
// 摸牌
func (this *Start) SetMyCard() int {
var card int
if this.Card_Lib.Out_Number%3 == 0 && this.Card_Lib.Out_Number < 25 && this.Card_Lib.Out_Number > 3 {
card = this.Card_Lib.RandomOutCard()
} else {
card = this.Card_Lib.RandomCard()
}
if card == -1 {
fmt.Println("没有牌了!!!")
return -1
} else {
this.My_Card.SetCard(card)
}
return card
}
// 打出
func (this *Start) OutMyCard() {
this.My_Card.CatAllCard()
var outCard int
fmt.Printf("您要打出的牌:")
for {
fmt.Scan(&outCard)
if outCard <= this.My_Card.Card_Number && outCard >= 1 {
break
} else {
fmt.Printf("输入错误,请重新输入:")
}
}
out := this.My_Card.OutCard(outCard - 1)
this.Card_Lib.OutCardArr = append(this.Card_Lib.OutCardArr, out)
this.Card_Lib.Out_Number++
}
// 胡牌检测
func (this *Start) IsHu() bool {
sorted := make([]int, this.My_Card.Card_Number)
sorted = append(this.My_Card.Card_Arr[:this.My_Card.Card_Number])
// 没有一个对子,没有胡牌
if rule.FindPairPos(sorted) == nil {
return false
}
if rule.IsShiSanYao(sorted) { // 十三幺
return true
} else if rule.IsQiXiaoDui(sorted) { // 小七对
if rule.IsQingYiSe(sorted) {
fmt.Println("恭喜你胡牌:", rule.HuTypeQingYiSe, rule.HuTypeQiXiaoDui)
return true
}
fmt.Println("恭喜你胡牌:", rule.HuTypeQiXiaoDui)
return true
} else if rule.IsNormal(sorted) { // 普通胡牌
if rule.IsQingYiSe(sorted) {
fmt.Println("恭喜你胡牌:", rule.HuTypeQingYiSe, rule.HuTypeNormal)
return true
}
fmt.Println("恭喜你胡牌:", rule.HuTypeNormal)
return true
} else if rule.IsDuiDui(sorted) { // 对对胡
if rule.IsQingYiSe(sorted) {
fmt.Println("恭喜你胡牌:", rule.HuTypeQingYiSe, rule.HuTypeDuiDui)
return true
}
fmt.Println("恭喜你胡牌:", rule.HuTypeDuiDui)
return true
}
return false
}
func main() {
start := &Start{}
start.Card_Lib = mahjong.GetSingleton()
start.My_Card = mycard.GetSingleton()
// 初始摸13张
start.InitCard()
// 开始循环摸牌
for i := 0; ; i++ {
if start.SetMyCard() == -1 {
return
}
fmt.Println()
// 检测有没有杠牌
if start.My_Card.RemoveQuaternion() {
continue
}
// 检测胡牌
if start.IsHu() {
start.My_Card.CatAllCard()
fmt.Println("对局结束")
break
}
start.OutMyCard()
}
}
mahjong.go
package mahjong
// 麻将包
import (
"fmt"
"math/rand"
)
func init() {
fmt.Println("牌库启动")
}
const ALLCARDNUMBER int = 136
/*
// [ 1- 9]壹万~玖万的个数
// [11-19]壹筒~玖筒的个数
// [21-29]壹条~玖条的个数
// [31-37]东南西北中发白的个数
*/
var CARD = []int{
1, 1, 1, 1,
2, 2, 2, 2,
3, 3, 3, 3,
4, 4, 4, 4,
5, 5, 5, 5,
6, 6, 6, 6,
7, 7, 7, 7,
8, 8, 8, 8,
9, 9, 9, 9,
11, 11, 11, 11,
12, 12, 12, 12,
13, 13, 13, 13,
14, 14, 14, 14,
15, 15, 15, 15,
16, 16, 16, 16,
17, 17, 17, 17,
18, 18, 18, 18,
19, 19, 19, 19,
21, 21, 21, 21,
22, 22, 22, 22,
23, 23, 23, 23,
24, 24, 24, 24,
25, 25, 25, 25,
26, 26, 26, 26,
27, 27, 27, 27,
28, 28, 28, 28,
29, 29, 29, 29,
31, 31, 31, 31,
32, 32, 32, 32,
33, 33, 33, 33,
34, 34, 34, 34,
35, 35, 35, 35,
36, 36, 36, 36,
37, 37, 37, 37,
}
var instance = &Mahjong{}
// 牌库单例
func GetSingleton() *Mahjong {
instance.Sum_Number = ALLCARDNUMBER
instance.Card = make([]int, instance.Sum_Number)
instance.Out_Number = 0
instance.OutCardArr = make([]int, instance.Out_Number)
copy(instance.Card, CARD)
return instance
}
type Mahjong struct {
Sum_Number int
Card []int
Out_Number int
OutCardArr []int
}
// 检查牌库是否存在这张牌
func (this *Mahjong) FindKey(card_value int) int {
nums := this.Card
low, high := 0, len(nums)-1
mid := 0
for low <= high {
mid = low + (high-low)/2
if nums[mid] == card_value {
return mid
} else if nums[mid] > card_value {
high = mid - 1
} else if nums[mid] < card_value {
low = mid + 1
}
}
return -1
}
// 从打出的牌中抽取一张
func (this *Mahjong) RandomOutCard() int {
if this.Sum_Number == 0 {
return -1
}
var ret_key int
for i := 0; i < len(this.OutCardArr); i++ {
card_rand := rand.Intn(this.Out_Number - 1)
card_value := this.OutCardArr[card_rand]
ret_key = this.FindKey(card_value)
if ret_key == -1 {
continue
} else {
break
}
}
// 已打出的牌,牌库中没有了,直接重新获取一张牌
if ret_key == -1 {
return this.RandomCard()
}
ret := this.Card[ret_key]
this.Card = append(this.Card[:ret_key], this.Card[ret_key+1:]...)
this.Sum_Number--
fmt.Println("抽到的牌为:", this.Card[ret_key], ", 剩余总数量:", this.Sum_Number)
return ret
}
// 牌库随机抽取一张牌
func (this *Mahjong) RandomCard() int {
card_rand := rand.Intn(this.Sum_Number - 1)
if this.Sum_Number == 0 {
return -1
}
this.Sum_Number--
fmt.Println("抽到的牌为:", this.Card[card_rand], ", 剩余总数量:", this.Sum_Number)
ret := this.Card[card_rand]
this.Card = append(this.Card[:card_rand], this.Card[card_rand+1:]...)
return ret
}
mycard.go
package mycard
// 玩家手牌包
import (
"fmt"
"sort"
)
func init() {
fmt.Println("我的卡牌")
}
const (
MAXCARD int = 38
MAXHANDCARD int = 18
)
var instance = &MyCard{}
func GetSingleton() *MyCard {
instance.Card_Number = 0
instance.Card_Arr = make([]int, MAXHANDCARD)
return instance
}
/*
// [ 1- 9]壹万~玖万的个数
// [11-19]壹筒~玖筒的个数
// [21-29]壹条~玖条的个数
// [31-37]东南西北中发白的个数
*/
type MyCard struct {
Card_Number int // 注意***所有边界都由 card_Number 来控制
Card_Arr []int // 手牌
Card_Quaternion []int // 杠牌
Universal_Card int // 万能牌
}
// 插入麻将
func (this *MyCard) SetCard(card int) {
this.Card_Arr[this.Card_Number] = card
this.Card_Number++
fmt.Print("摸到的麻将:")
this.CatCard(card)
// 使用浅拷贝,将赋值过的数组进行排序
s := this.Card_Arr[:this.Card_Number]
sort.Ints(s)
}
func (this *MyCard) CatCard(value int) {
if value < 10 {
fmt.Printf(" %d万", value)
} else if value < 20 {
fmt.Printf(" %d筒", value%10)
} else if value < 30 {
fmt.Printf(" %d条", value%20)
} else if value == 31 {
fmt.Printf(" 东")
} else if value == 32 {
fmt.Printf(" 南")
} else if value == 33 {
fmt.Printf(" 西")
} else if value == 34 {
fmt.Printf(" 北")
} else if value == 35 {
fmt.Printf(" 中")
} else if value == 36 {
fmt.Printf(" 发")
} else if value == 37 {
fmt.Printf(" 白")
}
}
// 打印牌组
func (this *MyCard) CatAllCard() {
fmt.Printf("我的牌组:")
for i := 0; i < this.Card_Number; i++ {
value := this.Card_Arr[i]
this.CatCard(value)
}
fmt.Println()
fmt.Print("我的牌号:")
for i := 1; i <= this.Card_Number; i++ {
fmt.Printf("%4d", i)
}
fmt.Print("\n我的杠牌: ")
for _, value := range this.Card_Quaternion {
this.CatCard(value)
}
fmt.Println()
}
// 找出杠,并且检测是否要打出
func (this *MyCard) RemoveQuaternion() bool {
for i := 0; i < this.Card_Number-3; i++ {
if this.Card_Arr[i] == this.Card_Arr[i+3] {
fmt.Print("你有一张杠:")
this.CatCard(this.Card_Arr[i])
fmt.Print("\n是否杠?\n输入0不杠,输入1杠;\n请输入:")
var outCard int
for {
fmt.Scan(&outCard)
if outCard == 0 || outCard == 1 {
break
}
fmt.Print("输入错误,请重新输入:")
}
if outCard == 0 {
i += 3
continue
} else if outCard == 1 {
this.Card_Quaternion = append(this.Card_Quaternion, this.Card_Arr[i])
this.Card_Arr[i] = MAXCARD
this.Card_Arr[i+1] = MAXCARD
this.Card_Arr[i+2] = MAXCARD
this.Card_Arr[i+3] = MAXCARD
// 将4对排序后移,用card_Number 作为边界
s := this.Card_Arr[:this.Card_Number]
sort.Ints(s)
this.Card_Number = this.Card_Number - 4
return true
}
}
}
return false
}
func (this *MyCard) OutCard(set int) int {
var out int
fmt.Printf("打出的牌为:")
this.CatCard(this.Card_Arr[set])
fmt.Println()
out = this.Card_Arr[set]
// 将打出的牌改为最大,再将手牌数量减少
this.Card_Arr[set] = this.Card_Arr[this.Card_Number-1]
this.Card_Number--
// 使用浅拷贝,将赋值过的数组进行排序
s := this.Card_Arr[:this.Card_Number]
sort.Ints(s)
fmt.Println()
return out
}
rule.go
package rule
// 规则包
import(
"fmt"
)
const (
// 胡牌类型
HuTypeNormal = "普通胡牌"
HuTypeDuiDui = "对对胡"
HuTypeQiXiaoDui = "7小对"
HuTypeYiTiaoLong = "一条龙"
HuTypeShiSanYao = "十三么"
HuTypeQingYiSe = "清一色"
)
func init() {
fmt.Println("")
}
/*
此包函数参数必须进行排序处理后传入
*/
// FindPairPos 找出所有对牌的位置
func FindPairPos(sortedCards []int) []int {
var pos = []int{}
length := len(sortedCards) - 1
for i := 0; i < length; i++ {
if sortedCards[i] == sortedCards[i+1] {
pos = append(pos, i)
i++
}
}
return pos
}
// RemovePair 从已排序的牌中,移除一对
func RemovePair(sortedCards []int, pos int) []int {
remainCards := make([]int, 0, len(sortedCards)-2)
remainCards = append(remainCards, sortedCards[:pos]...)
remainCards = append(remainCards, sortedCards[pos+2:]...)
return remainCards
}
// IsTriplet 剔除所有 111 刻子
func RemoveTriplet(arr []int) [] int{
length := len(arr)
var retArr [] int
copy(retArr, arr)
for i := 0; i < length-2; i++ {
if arr[i] == arr[i+1] && arr[i] == arr[i+2] {
retArr = append(retArr, i)
i+=2
}
}
for i := len(retArr)-1; i>=0; i-- {
arr = append(arr[:retArr[i]],arr[retArr[i]+3:] ...)
}
return arr
}
// 判断顺子
func RemoveSequence(arr [] int) (int, int){
for i:= 1; i<len(arr); i++{
if arr[0] == arr [i] -1 {
for j:=i+1; j<len(arr); j++ {
if arr[i] == arr[j]-1 {
return i,j
}
}
}
}
return 0,0
}
// IsSequence 判断剩余是否全为顺子
func IsSequence(sortedCards []int) bool {
length := len(sortedCards)
arr := make([]int, length)
copy(arr, sortedCards)
if length % 3 != 0 && arr[length - 1] < 30{
return false
}
for {
if len(arr) == 0 {
return true
}
i, j := RemoveSequence(arr)
if i == 0 {
return false
}
arr = append(arr[:j], arr[j+1:]...)
arr = append(arr[:i], arr[i+1:]...)
arr = append(arr[1:])
}
}
// ================================================
/* 普通胡牌规则,只有一个 对牌
for 循环剔除一个对牌
检测是否为 123、111、三张牌型
*/
func IsNormal(sortedCards []int) bool{
length := len(sortedCards)
pos := FindPairPos(sortedCards)
for _, value := range pos {
arr := make([]int, length)
copy(arr, sortedCards)
arr = RemovePair(arr, value)
arr = RemoveTriplet(arr)
if IsSequence(arr) {
return true
}
}
return false
}
// 对对胡
func IsDuiDui(sortedCards []int) bool{
length := len(sortedCards)
arr := make([]int, length)
copy(arr, sortedCards)
// 处理掉单个,13幺只有一个对
pos := FindPairPos(arr)
for _, value := range pos {
arr := make([]int, length)
copy(arr, sortedCards)
// 剔除一个对子
arr = RemovePair(arr, value)
arr = RemoveTriplet(arr)
if len(arr) == 0 {
return true
}
}
return false
}
// 13幺, 已经排序
func IsShiSanYao(sortedCards []int) bool{
length := len(sortedCards)
arr := make([]int, length)
copy(arr, sortedCards)
// 处理掉单个,13幺只有一个对
pos := FindPairPos(arr)
if len(pos) == 0 || length != 14 {
return false
}
arr = append(arr[:pos[0]],arr[pos[0]+1:]...)
// 验证 万 条 筒
if !(arr[0] == 1 && arr[1] == 9){
return false
}
if !(arr[2] == 11 && arr[3] == 19){
return false
}
if !(arr[4] == 21 && arr[5] == 29){
return false
}
if arr[6] != 31{
return false
}
for i:=6; i<12; i++ {
if arr[i]+1 != arr[i+1]{
return false
}
}
fmt.Println("恭喜你胡牌:",HuTypeShiSanYao)
return true
}
// 七小对
func IsQiXiaoDui(sortedCards []int) bool{
length := len(sortedCards)
if length != 14 {
return false
}
arr := make([]int, length)
copy(arr, sortedCards)
pos := FindPairPos(arr)
if len(pos) == 7 && length == 14 {
return true
}
return false
}
// 清一色
func IsQingYiSe(sortedCards []int) bool{
maxLength := sortedCards[len(sortedCards)-1]
minLength := sortedCards[0]
if(maxLength - minLength) < 10 && maxLength < 30 {
return true
}
return false
}