mirror of
https://github.com/golang/go
synced 2024-11-21 19:04:44 -07: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
115
doc/codewalk/functions.xml
Normal file
115
doc/codewalk/functions.xml
Normal file
@ -0,0 +1,115 @@
|
|||||||
|
<codewalk title="First-Class Functions in Go">
|
||||||
|
|
||||||
|
<step title="Introduction" src="doc/codewalk/pig.go">
|
||||||
|
Go supports first class functions, higher-order functions, user-defined
|
||||||
|
function types, function literals, closures, and multiple return values.
|
||||||
|
<br/><br/>
|
||||||
|
|
||||||
|
This rich feature set supports a functional programming style in a strongly
|
||||||
|
typed language.
|
||||||
|
<br/><br/>
|
||||||
|
|
||||||
|
In this codewalk we will look at a simple program that simulates a dice game
|
||||||
|
called <a href="http://en.wikipedia.org/wiki/Pig_(dice)">Pig</a> and evaluates
|
||||||
|
basic strategies.
|
||||||
|
</step>
|
||||||
|
|
||||||
|
<step title="Game overview" src="doc/codewalk/pig.go:/\/\/ A score/,/thisTurn int\n}/">
|
||||||
|
Pig is a two-player game played with a 6-sided die. Each turn, you may roll or stay.
|
||||||
|
<ul>
|
||||||
|
<li> If you roll a 1, you lose all points for your turn and play passes to
|
||||||
|
your opponent. Any other roll adds its value to your turn score. </li>
|
||||||
|
<li> If you stay, your turn score is added to your total score, and play passes
|
||||||
|
to your opponent. </li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
The first person to reach 100 total points wins.
|
||||||
|
<br/><br/>
|
||||||
|
|
||||||
|
The <code>score</code> type stores the scores of the current and opposing
|
||||||
|
players, in addition to the points accumulated during the current turn.
|
||||||
|
</step>
|
||||||
|
|
||||||
|
<step title="User-defined function types" src="doc/codewalk/pig.go:/\/\/ An action/,/bool\)/">
|
||||||
|
In Go, functions can be passed around just like any other value. A function's
|
||||||
|
type signature describes the types of its arguments and return values.
|
||||||
|
<br/><br/>
|
||||||
|
|
||||||
|
The <code>action</code> type is a function that takes a <code>score</code>
|
||||||
|
and returns the resulting <code>score</code> and whether the current turn is
|
||||||
|
over.
|
||||||
|
<br/><br/>
|
||||||
|
|
||||||
|
If the turn is over, the <code>player</code> and <code>opponent</code> fields
|
||||||
|
in the resulting <code>score</code> should be swapped, as it is now the other player's
|
||||||
|
turn.
|
||||||
|
</step>
|
||||||
|
|
||||||
|
<step title="Multiple return values" src="doc/codewalk/pig.go:/\/\/ roll returns/,/stay.*true\n}/">
|
||||||
|
Go functions can return multiple values.
|
||||||
|
<br/><br/>
|
||||||
|
|
||||||
|
The functions <code>roll</code> and <code>stay</code> each return a pair of
|
||||||
|
values. They also match the <code>action</code> type signature. These
|
||||||
|
<code>action</code> functions define the rules of Pig.
|
||||||
|
</step>
|
||||||
|
|
||||||
|
<step title="Higher-order functions" src="doc/codewalk/pig.go:/\/\/ A strategy/,/action\n/">
|
||||||
|
A function can use other functions as arguments and return values.
|
||||||
|
<br/><br/>
|
||||||
|
|
||||||
|
A <code>strategy</code> is a function that takes a <code>score</code> as input
|
||||||
|
and returns an <code>action</code> to perform. <br/>
|
||||||
|
(Remember, an <code>action</code> is itself a function.)
|
||||||
|
</step>
|
||||||
|
|
||||||
|
<step title="Function literals and closures" src="doc/codewalk/pig.go:/return func/,/return roll\n\t}/">
|
||||||
|
Anonymous functions can be declared in Go, as in this example. Function
|
||||||
|
literals are closures: they inherit the scope of the function in which they
|
||||||
|
are declared.
|
||||||
|
<br/><br/>
|
||||||
|
|
||||||
|
One basic strategy in Pig is to continue rolling until you have accumulated at
|
||||||
|
least k points in a turn, and then stay. The argument <code>k</code> is
|
||||||
|
enclosed by this function literal, which matches the <code>strategy</code> type
|
||||||
|
signature.
|
||||||
|
</step>
|
||||||
|
|
||||||
|
<step title="Simulating games" src="doc/codewalk/pig.go:/\/\/ play/,/currentPlayer\n}/">
|
||||||
|
We simulate a game of Pig by calling an <code>action</code> to update the
|
||||||
|
<code>score</code> until one player reaches 100 points. Each
|
||||||
|
<code>action</code> is selected by calling the <code>strategy</code> function
|
||||||
|
associated with the current player.
|
||||||
|
</step>
|
||||||
|
|
||||||
|
<step title="Comparing functions" src="doc/codewalk/pig.go:/if action/,/currentPlayer\)\)\n\t\t}/">
|
||||||
|
Functions can be compared for equality in Go. From the
|
||||||
|
<a href="http://golang.org/doc/go_spec.html#Comparison_operators">language specification</a>:
|
||||||
|
Function values are equal if they refer to the same function or if both are <code>nil</code>.
|
||||||
|
<br/><br/>
|
||||||
|
|
||||||
|
We enforce that a <code>strategy</code> function can only return a legal
|
||||||
|
<code>action</code>: either <code>roll</code> or <code>stay</code>.
|
||||||
|
</step>
|
||||||
|
|
||||||
|
<step title="Simulating a tournament" src="doc/codewalk/pig.go:/\/\/ roundRobin/,/gamesPerStrategy\n}/">
|
||||||
|
The <code>roundRobin</code> function simulates a tournament and tallies wins.
|
||||||
|
Each strategy plays each other strategy <code>gamesPerSeries</code> times.
|
||||||
|
</step>
|
||||||
|
|
||||||
|
<step title="Variadic function declarations" src="doc/codewalk/pig.go:/\/\/ ratioS/,/string {/">
|
||||||
|
Variadic functions like <code>ratioString</code> take a variable number of
|
||||||
|
arguments. These arguments are available as a slice inside the function.
|
||||||
|
</step>
|
||||||
|
|
||||||
|
<step title="Simulation results" src="doc/codewalk/pig.go:/func main/,/\n}/">
|
||||||
|
The <code>main</code> function defines 100 basic strategies, simulates a round
|
||||||
|
robin tournament, and then prints the win/loss record of each strategy.
|
||||||
|
<br/><br/>
|
||||||
|
|
||||||
|
Among these strategies, staying at 25 is best, but the <a
|
||||||
|
href="http://www.google.com/search?q=optimal+play+pig">optimal strategy for
|
||||||
|
Pig</a> is much more complex.
|
||||||
|
</step>
|
||||||
|
|
||||||
|
</codewalk>
|
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…
Reference in New Issue
Block a user