多執行緒,通道,讀寫鎖(單寫多讀),隨機(洗牌),是本文涉及的主要知識點。
先看一下做出來的效果,因為是實驗程式,跟真實的鬥地主還是有差距,理解萬歲!
[發牌員]:洗牌咯。
刷刷刷...
[發牌員]:牌洗好了。
[發牌員]:開始發牌。
[發牌員]:每個人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的多執行緒特性,不具備可玩性,期待更多的同學請見諒。
如果需要原始碼的話,請到公眾號回覆「鬥地主」獲取哦。
版權申明:本文為博主原創文章,轉載請保留原文連結及作者。
如果您喜歡我寫的文章,請關注公眾號支援一下,謝謝哈哈哈。