1 // Copyright 2011 The Go Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 package main 6 7 import ( 8 "fmt" 9 "math/rand" 10 ) 11 12 const ( 13 win = 100 // The winning score in a game of Pig 14 gamesPerSeries = 10 // The number of games per series to simulate 15 ) 16 17 // A score includes scores accumulated in previous turns for each player, 18 // as well as the points scored by the current player in this turn. 19 type score struct { 20 player, opponent, thisTurn int 21 } 22 23 // An action transitions stochastically to a resulting score. 24 type action func(current score) (result score, turnIsOver bool) 25 26 // roll returns the (result, turnIsOver) outcome of simulating a die roll. 27 // If the roll value is 1, then thisTurn score is abandoned, and the players' 28 // roles swap. Otherwise, the roll value is added to thisTurn. 29 func roll(s score) (score, bool) { 30 outcome := rand.Intn(6) + 1 // A random int in [1, 6] 31 if outcome == 1 { 32 return score{s.opponent, s.player, 0}, true 33 } 34 return score{s.player, s.opponent, outcome + s.thisTurn}, false 35 } 36 37 // stay returns the (result, turnIsOver) outcome of staying. 38 // thisTurn score is added to the player's score, and the players' roles swap. 39 func stay(s score) (score, bool) { 40 return score{s.opponent, s.player + s.thisTurn, 0}, true 41 } 42 43 // A strategy chooses an action for any given score. 44 type strategy func(score) action 45 46 // stayAtK returns a strategy that rolls until thisTurn is at least k, then stays. 47 func stayAtK(k int) strategy { 48 return func(s score) action { 49 if s.thisTurn >= k { 50 return stay 51 } 52 return roll 53 } 54 } 55 56 // play simulates a Pig game and returns the winner (0 or 1). 57 func play(strategy0, strategy1 strategy) int { 58 strategies := []strategy{strategy0, strategy1} 59 var s score 60 var turnIsOver bool 61 currentPlayer := rand.Intn(2) // Randomly decide who plays first 62 for s.player+s.thisTurn < win { 63 action := strategies[currentPlayer](s) 64 s, turnIsOver = action(s) 65 if turnIsOver { 66 currentPlayer = (currentPlayer + 1) % 2 67 } 68 } 69 return currentPlayer 70 } 71 72 // roundRobin simulates a series of games between every pair of strategies. 73 func roundRobin(strategies []strategy) ([]int, int) { 74 wins := make([]int, len(strategies)) 75 for i := 0; i < len(strategies); i++ { 76 for j := i + 1; j < len(strategies); j++ { 77 for k := 0; k < gamesPerSeries; k++ { 78 winner := play(strategies[i], strategies[j]) 79 if winner == 0 { 80 wins[i]++ 81 } else { 82 wins[j]++ 83 } 84 } 85 } 86 } 87 gamesPerStrategy := gamesPerSeries * (len(strategies) - 1) // no self play 88 return wins, gamesPerStrategy 89 } 90 91 // ratioString takes a list of integer values and returns a string that lists 92 // each value and its percentage of the sum of all values. 93 // e.g., ratios(1, 2, 3) = "1/6 (16.7%), 2/6 (33.3%), 3/6 (50.0%)" 94 func ratioString(vals ...int) string { 95 total := 0 96 for _, val := range vals { 97 total += val 98 } 99 s := "" 100 for _, val := range vals { 101 if s != "" { 102 s += ", " 103 } 104 pct := 100 * float64(val) / float64(total) 105 s += fmt.Sprintf("%d/%d (%0.1f%%)", val, total, pct) 106 } 107 return s 108 } 109 110 func main() { 111 strategies := make([]strategy, win) 112 for k := range strategies { 113 strategies[k] = stayAtK(k + 1) 114 } 115 wins, games := roundRobin(strategies) 116 117 for k := range strategies { 118 fmt.Printf("Wins, losses staying at k =% 4d: %s\n", 119 k+1, ratioString(wins[k], games-wins[k])) 120 } 121 } 122