专栏首页dongfangerGolang多线程简单斗地主

Golang多线程简单斗地主

多线程,通道,读写锁(单写多读),随机(洗牌),是本文涉及的主要知识点。

先看一下做出来的效果,因为是实验程序,跟真实的斗地主还是有差距,理解万岁!

[发牌员]:洗牌咯。
刷刷刷...
[发牌员]:牌洗好了。
[发牌员]:开始发牌。
[发牌员]:每个人17张牌。
[发牌员]:抢地主。
[fang]:哈哈,我是地主!
fang的牌是[♣9 ♦9 ♥A ♠9 ♣6 ♣5 ♦3 ♣10 ♥5 ♣8 ♠Q ♠A ♠8 ♦4 ♥4 ♦K ♥7 ♣A ♠K ♥3],共20张。
dong的牌是[大王 ♦8 ♠5 小王 ♠6 ♣Q ♠10 ♣7 ♠3 ♦A ♦Q ♥J ♣K ♥6 ♥9 ♥Q ♣2],共17张。
er的牌是[♣A ♠K ♥3 ♥2 ♠4 ♦2 ♦5 ♥K ♦10 ♠2 ♥8 ♦6 ♣4 ♦J ♣3 ♣J ♠7],共17张。
[fang]:我开始出牌了。
[er]:我开始出牌了。
[dong]:我开始出牌了。
赢家是er。

基本流程是洗牌->发牌->抢地主->打牌->gg。

哈哈这个程序的精髓是,由于时(lan)间(de)有(xie)限(le),打牌是哪个线程抢到了就出牌,直到牌出完了,就赢了。(多线程写斗地主,是我大学操作系统课程的实验项目,当时是完整实现了斗地主算法的,用的是C++和MFC,可以在界面上交互打牌)

边看代码变讲。

主函数

func main() {
	// 洗牌
	cards := shuffle()
	// 发牌
	dealCards := deal(cards)
	// 抢地主
	fmt.Println("[发牌员]:抢地主。")
	go player(order[0], dealCards[0])
	go player(order[1], dealCards[1])
	go player(order[2], dealCards[2])
	// Winner
	winner := <-winner
	fmt.Printf("赢家是%s。\n", winner)
}

解析:

1.main里面是打牌的步骤,洗牌,发牌,抢地主,打牌,gg。
2.用go player(),开了3个线程,也就是3个玩家。
3.发牌的时候,是留了3张底牌的,存在通道“bottom”里面,抢地主的时候,3个线程就去取,谁先取到谁就是地主。
4.打牌打到最后,会往另外一个通道“winner”里面写值,谁先打完,就把自己的name存进去。
5.3个玩家在打牌的时候,main是阻塞的,等待从通道“winner”读取值,有玩家打完了,通道“winner”有值了,就激活。

洗牌函数

func shuffle() []string {
	fmt.Println("[发牌员]:洗牌咯。")
	fmt.Println("刷刷刷...")
	cards := cards()
	rand.Seed(time.Now().UnixNano())
	rand.Shuffle(len(cards), func(i, j int) {
		cards[i], cards[j] = cards[j], cards[i]
	})
	fmt.Println("[发牌员]:牌洗好了。")

	return cards
}

解析:

1.rand默认是假的随机,因为不管运行多少次都是一样的,需要设置种子,time.Now().UnixNano(),让每次随机结果都不同。
2.rand.Shuffle()洗牌,随机交换2个牌的位置。

发牌函数

func deal(cards []string) [][]string {
	fmt.Println("[发牌员]:开始发牌。")
	var dealCards [][]string
	dealCards = append(dealCards, cards[0:17])
	dealCards = append(dealCards, cards[17:34])
	dealCards = append(dealCards, cards[34:51])
	fmt.Println("[发牌员]:每个人17张牌。")

	go leaveBottom(cards[51:54])

	return dealCards
}

解析:

1.因为已经洗了牌了,直接先切3份牌出来,每份17张。
2.留了3张底牌,放到通道“bottom”中。
3.如果这里不再开线程,会发生死锁!因为main本身也是个线程,直接存通道的话,会把main阻塞,直到有线程把通道的值读出去;而main阻塞后,是无法继续执行后面的代码的,也就无法再起3个玩家线程来读值了,就会发生死锁。
4.所以leaveBottom()起了一个单独的线程。

Desk牌桌

type Desk struct {
	mutex     sync.RWMutex
	playCards []string
}

func (d *Desk) write(card string) {
	d.mutex.Lock()
	defer d.mutex.Unlock()
	d.playCards = append(d.playCards, card)
}

func (d *Desk) read() []string {
	d.mutex.RLock()
	defer d.mutex.RUnlock()
	return d.playCards
}

解析:

1.定义了结构Desk,包括读写锁和牌桌上打的牌。
2.定义了write()和read()2个函数,3个线程可以同时读,但只能一次写,也就是单写多读锁。

player函数

func player(name string, hands []string) {
	landlord := <-bottom
	if len(landlord) > 0 {
		fmt.Printf("[%s]:哈哈,我是地主!\n", name)
		hands = append(hands, landlord...)
		desk.write(name)
	}
	fmt.Printf("%s的牌是%s,共%d张。\n", name, hands, len(hands))

	time.Sleep(time.Second)

	i := 0
	for true {
		playCards := desk.read()
		if playCards[len(playCards)-1] == name {
			if i == 1 {
				fmt.Printf("[%s]:我开始出牌了。\n", name)
			}
			desk.write(hands[i])
			desk.write(order[(getOrderID(name)+1)%3])
			i += 1
			if i == len(hands) {
				winner <- name
				break
			}
		}
	}
}

解析:

1.玩家函数,第一个参数是名字,第二个参数是手上拿的牌。
2.3个线程都有这样一段代码。
3.首先从通道“bottom”读取值,也就是抢地主。
4.抢到地主的玩家,会把底牌放到自己的手牌中,并且把自己的名字写到牌桌上(根据名字来看该谁出牌),地主先出牌。
5.for true {}循环不停的出牌,从第一张到最后一张,先从牌桌上看是不是自己的名字,是自己的名字才轮到出牌。
6.牌出完了,就把自己的名字写到通道“winner”,游戏结束。

本文的程序只是为了实验go的多线程特性,不具备可玩性,期待更多的同学请见谅。 如果需要源码的话,请到公众号回复「斗地主」获取哦。

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • JMeter100个线程竟然只模拟出1个并发

    线程组,是说到 JMeter 会第一时间想到的东西,也是我认为 JMeter 最难理解的知识点。因为项目让你做个压测,首先就是要考虑并发,用 JMeter 就是...

    dongfanger
  • Web测试转App测试不看不知道

    Web通常指的是互联网应用系统,比如税务电子化征管档案系统、金融数据平台、餐饮商家管理后台等等,其实质是C/S的程序。

    dongfanger
  • 学了Java才搞懂JMeter测试计划

    界面上,展示了一个表格,共两列,第一列是 Name,第二列是 Value,分别对应变量名和变量值。

    dongfanger
  • 小文件数过多导致distcp迁移报错

    DistCp(分布式拷贝)是用于大规模集群内部和集群之间拷贝的工具。 它使用Map/Reduce实现文件分发,错误处理和恢复,以及报告生成。 它把文件和目录的列...

    袁宋
  • 关于服务器性能的一些思考

    平常的工作中,在衡量服务器的性能时,经常会涉及到几个指标,load、cpu、mem、qps、rt,其中load、cpu、mem来衡量机器性能,qps、rt来衡量...

    用户1058143
  • Jmeter之接口测试使用流程

      采样器:较常用HTTP请求、JDBC Request、SOAP/XML -RPC RRequest

    小老鼠
  • Android RecycleView添加head配置封装的实例

    这个是把RecycleView的适配器给封装了,直接调用就可以了,还添加了可以添加head头部功能,很赞的,今天记下来,下次直接用

    砸漏
  • MVVM + data-binding 快速入门

    用户1127566
  • Spring详解(五)------AOP

      这章我们接着讲 Spring 的核心概念---AOP,这也是 Spring 框架中最为核心的一个概念。   PS:本篇博客源码下载链接:http://pan...

    IT可乐
  • Android 实现通知消息水平播放、无限循环效果

    这个效果也很常见,实现的方法也有很多,我是使用RecyclerView来实现的,觉得还是挺不错的,就写下来分享给大家。

    砸漏

扫码关注云+社区

领取腾讯云代金券