麻将小游戏设计

因为近期才学习了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
}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值