Home | History | Annotate | Download | only in codewalk
      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