| // Copyright 2011 The Go Authors. All rights reserved. | |
| // Use of this source code is governed by a BSD-style | |
| // license that can be found in the LICENSE file. | |
| package main | |
| import ( | |
| "fmt" | |
| "math/rand" | |
| ) | |
| const ( | |
| win = 100 // The winning score in a game of Pig | |
| gamesPerSeries = 10 // The number of games per series to simulate | |
| ) | |
| // A score includes scores accumulated in previous turns for each player, | |
| // as well as the points scored by the current player in this turn. | |
| type score struct { | |
| player, opponent, thisTurn int | |
| } | |
| // An action transitions stochastically to a resulting score. | |
| type action func(current score) (result score, turnIsOver bool) | |
| // roll returns the (result, turnIsOver) outcome of simulating a die roll. | |
| // If the roll value is 1, then thisTurn score is abandoned, and the players' | |
| // roles swap. Otherwise, the roll value is added to thisTurn. | |
| func roll(s score) (score, bool) { | |
| outcome := rand.Intn(6) + 1 // A random int in [1, 6] | |
| if outcome == 1 { | |
| return score{s.opponent, s.player, 0}, true | |
| } | |
| return score{s.player, s.opponent, outcome + s.thisTurn}, false | |
| } | |
| // stay returns the (result, turnIsOver) outcome of staying. | |
| // thisTurn score is added to the player's score, and the players' roles swap. | |
| func stay(s score) (score, bool) { | |
| return score{s.opponent, s.player + s.thisTurn, 0}, true | |
| } | |
| // A strategy chooses an action for any given score. | |
| type strategy func(score) action | |
| // stayAtK returns a strategy that rolls until thisTurn is at least k, then stays. | |
| func stayAtK(k int) strategy { | |
| return func(s score) action { | |
| if s.thisTurn >= k { | |
| return stay | |
| } | |
| return roll | |
| } | |
| } | |
| // play simulates a Pig game and returns the winner (0 or 1). | |
| func play(strategy0, strategy1 strategy) int { | |
| strategies := []strategy{strategy0, strategy1} | |
| var s score | |
| var turnIsOver bool | |
| currentPlayer := rand.Intn(2) // Randomly decide who plays first | |
| for s.player+s.thisTurn < win { | |
| action := strategies[currentPlayer](s) | |
| s, turnIsOver = action(s) | |
| if turnIsOver { | |
| currentPlayer = (currentPlayer + 1) % 2 | |
| } | |
| } | |
| return currentPlayer | |
| } | |
| // roundRobin simulates a series of games between every pair of strategies. | |
| func roundRobin(strategies []strategy) ([]int, int) { | |
| wins := make([]int, len(strategies)) | |
| for i := 0; i < len(strategies); i++ { | |
| for j := i + 1; j < len(strategies); j++ { | |
| for k := 0; k < gamesPerSeries; k++ { | |
| winner := play(strategies[i], strategies[j]) | |
| if winner == 0 { | |
| wins[i]++ | |
| } else { | |
| wins[j]++ | |
| } | |
| } | |
| } | |
| } | |
| gamesPerStrategy := gamesPerSeries * (len(strategies) - 1) // no self play | |
| return wins, gamesPerStrategy | |
| } | |
| // ratioString takes a list of integer values and returns a string that lists | |
| // each value and its percentage of the sum of all values. | |
| // e.g., ratios(1, 2, 3) = "1/6 (16.7%), 2/6 (33.3%), 3/6 (50.0%)" | |
| func ratioString(vals ...int) string { | |
| total := 0 | |
| for _, val := range vals { | |
| total += val | |
| } | |
| s := "" | |
| for _, val := range vals { | |
| if s != "" { | |
| s += ", " | |
| } | |
| pct := 100 * float64(val) / float64(total) | |
| s += fmt.Sprintf("%d/%d (%0.1f%%)", val, total, pct) | |
| } | |
| return s | |
| } | |
| func main() { | |
| strategies := make([]strategy, win) | |
| for k := range strategies { | |
| strategies[k] = stayAtK(k + 1) | |
| } | |
| wins, games := roundRobin(strategies) | |
| for k := range strategies { | |
| fmt.Printf("Wins, losses staying at k =% 4d: %s\n", | |
| k+1, ratioString(wins[k], games-wins[k])) | |
| } | |
| } |