多線程,通道,讀寫鎖(單寫多讀),隨機(洗牌),是本文涉及的主要知識點。算法
先看一下作出來的效果,由於是實驗程序,跟真實的鬥地主仍是有差距,理解萬歲!多線程
[發牌員]:洗牌咯。 刷刷刷... [發牌員]:牌洗好了。 [發牌員]:開始發牌。 [發牌員]:每一個人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。app
哈哈這個程序的精髓是,因爲時(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) }
解析:code
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 }
解析:get
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的多線程特性,不具有可玩性,期待更多的同窗請見諒。
若是須要源碼的話,請到公衆號回覆「鬥地主」獲取哦。
版權申明:本文爲博主原創文章,轉載請保留原文連接及做者。
若是您喜歡我寫的文章,請關注公衆號支持一下,謝謝哈哈哈。