github.com/mdempsky/go@v0.0.0-20151201204031-5dd372bd1e70/doc/codewalk/pig.go (about)

     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  }