mirror of
https://github.com/golang/go.git
synced 2025-11-07 04:01:00 +00:00
A codewalk through a simple program that illustrates several aspects of Go functions: function objects, higher-order functions, variadic functions, tail recursion, etc. The example program simulates the game of Pig, a dice game with simple rules but a nontrivial solution.
R=adg, rsc, iant2, r CC=golang-dev https://golang.org/cl/4306045
This commit is contained in:
parent
776fd72579
commit
4ffee801ce
2 changed files with 239 additions and 0 deletions
124
doc/codewalk/pig.go
Normal file
124
doc/codewalk/pig.go
Normal file
|
|
@ -0,0 +1,124 @@
|
|||
// 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"
|
||||
"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)
|
||||
if action != roll && action != stay {
|
||||
panic(fmt.Sprintf("Player %d is cheating", currentPlayer))
|
||||
}
|
||||
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]))
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue