Pop Out Code

    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. package main
    5. import (
    6. "fmt"
    7. "math/rand"
    8. )
    9. const (
    10. win = 100 // The winning score in a game of Pig
    11. gamesPerSeries = 10 // The number of games per series to simulate
    12. )
    13. // A score includes scores accumulated in previous turns for each player,
    14. // as well as the points scored by the current player in this turn.
    15. type score struct {
    16. player, opponent, thisTurn int
    17. }
    18. // An action transitions stochastically to a resulting score.
    19. type action func(current score) (result score, turnIsOver bool)
    20. // roll returns the (result, turnIsOver) outcome of simulating a die roll.
    21. // If the roll value is 1, then thisTurn score is abandoned, and the players'
    22. // roles swap. Otherwise, the roll value is added to thisTurn.
    23. func roll(s score) (score, bool) {
    24. outcome := rand.Intn(6) + 1 // A random int in [1, 6]
    25. if outcome == 1 {
    26. return score{s.opponent, s.player, 0}, true
    27. }
    28. return score{s.player, s.opponent, outcome + s.thisTurn}, false
    29. }
    30. // stay returns the (result, turnIsOver) outcome of staying.
    31. // thisTurn score is added to the player's score, and the players' roles swap.
    32. func stay(s score) (score, bool) {
    33. return score{s.opponent, s.player + s.thisTurn, 0}, true
    34. }
    35. // A strategy chooses an action for any given score.
    36. type strategy func(score) action
    37. // stayAtK returns a strategy that rolls until thisTurn is at least k, then stays.
    38. func stayAtK(k int) strategy {
    39. return func(s score) action {
    40. if s.thisTurn >= k {
    41. return stay
    42. }
    43. return roll
    44. }
    45. }
    46. // play simulates a Pig game and returns the winner (0 or 1).
    47. func play(strategy0, strategy1 strategy) int {
    48. strategies := []strategy{strategy0, strategy1}
    49. var s score
    50. var turnIsOver bool
    51. currentPlayer := rand.Intn(2) // Randomly decide who plays first
    52. for s.player+s.thisTurn < win {
    53. action := strategies[currentPlayer](s)
    54. s, turnIsOver = action(s)
    55. if turnIsOver {
    56. currentPlayer = (currentPlayer + 1) % 2
    57. }
    58. }
    59. return currentPlayer
    60. }
    61. // roundRobin simulates a series of games between every pair of strategies.
    62. func roundRobin(strategies []strategy) ([]int, int) {
    63. wins := make([]int, len(strategies))
    64. for i := 0; i < len(strategies); i++ {
    65. for j := i + 1; j < len(strategies); j++ {
    66. for k := 0; k < gamesPerSeries; k++ {
    67. winner := play(strategies[i], strategies[j])
    68. if winner == 0 {
    69. wins[i]++
    70. } else {
    71. wins[j]++
    72. }
    73. }
    74. }
    75. }
    76. gamesPerStrategy := gamesPerSeries * (len(strategies) - 1) // no self play
    77. return wins, gamesPerStrategy
    78. }
    79. // ratioString takes a list of integer values and returns a string that lists
    80. // each value and its percentage of the sum of all values.
    81. // e.g., ratios(1, 2, 3) = "1/6 (16.7%), 2/6 (33.3%), 3/6 (50.0%)"
    82. func ratioString(vals ...int) string {
    83. total := 0
    84. for _, val := range vals {
    85. total += val
    86. }
    87. s := ""
    88. for _, val := range vals {
    89. if s != "" {
    90. s += ", "
    91. }
    92. pct := 100 * float64(val) / float64(total)
    93. s += fmt.Sprintf("%d/%d (%0.1f%%)", val, total, pct)
    94. }
    95. return s
    96. }
    97. func main() {
    98. strategies := make([]strategy, win)
    99. for k := range strategies {
    100. strategies[k] = stayAtK(k + 1)
    101. }
    102. wins, games := roundRobin(strategies)
    103. for k := range strategies {
    104. fmt.Printf("Wins, losses staying at k =% 4d: %s\n",
    105. k+1, ratioString(wins[k], games-wins[k]))
    106. }
    107. }


    はじめに

    Goは、第一級関数、高階関数、ユーザー定義関数型、関数リテラル、クロージャ、および複数の戻り値をサポートしています。

    この豊富な機能セットは、強く型付けされた言語における関数型プログラミングスタイルをサポートします。

    このコードウォークでは、サイコロゲーム「Pig」をシミュレートするシンプルなプログラムを見て、基本的な戦略を評価します。

    doc/codewalk/pig.go


    ゲームの概要

    Pigは、6面のサイコロを使った2人用のゲームです。各ターンで、振るか留まるかを選択できます。

    • 1が出た場合、そのターンのポイントはすべて失われ、プレイは相手に移ります。他のいずれかの出目は、その値をターンスコアに加えます。
    • 留まる場合、ターンスコアは合計スコアに加算され、プレイは相手に移ります。
      最初に合計100ポイントに達した人が勝ちです。

      score型は、現在のプレイヤーと対戦相手のスコアに加え、現在のターン中に蓄積されたポイントを保存します。

    doc/codewalk/pig.go:17,21


    ユーザー定義関数型

    Goでは、関数は他の値と同様に渡すことができます。関数の型シグネチャは、その引数と戻り値の型を説明します。

    action型は、scoreを引数に取り、結果のscoreと現在のターンが終了したかどうかを返す関数です。

    ターンが終了した場合、結果のscore内のplayeropponentフィールドは入れ替える必要があります。なぜなら、今は他のプレイヤーのターンだからです。

    doc/codewalk/pig.go:23,24


    複数の戻り値

    Goの関数は複数の値を返すことができます。

    関数rollstayはそれぞれ一対の値を返します。また、action型シグネチャに一致します。これらのaction関数は、Pigのルールを定義します。

    doc/codewalk/pig.go:26,41


    高階関数

    関数は他の関数を引数として使用したり、戻り値として返したりできます。

    strategyは、scoreを入力として受け取り、実行するactionを返す関数です。

    (覚えておいてください、actionはそれ自体が関数です。)

    doc/codewalk/pig.go:43,44


    関数リテラルとクロージャ

    Goでは、匿名関数を宣言できます。この例のように。関数リテラルはクロージャです:それらは宣言された関数のスコープを継承します。

    Pigにおける基本的な戦略の1つは、ターン中に少なくともkポイントを蓄積するまで振り続け、その後留まることです。引数kは、この関数リテラルによって囲まれており、strategy型シグネチャに一致します。

    doc/codewalk/pig.go:48,53


    ゲームのシミュレーション

    actionを呼び出してscoreを更新することで、Pigのゲームをシミュレートします。1人のプレイヤーが100ポイントに達するまで、各actionは現在のプレイヤーに関連付けられたstrategy関数を呼び出すことで選択されます。

    doc/codewalk/pig.go:56,70


    トーナメントのシミュレーション

    roundRobin関数はトーナメントをシミュレートし、勝利を集計します。各戦略は、他の各戦略とgamesPerSeries回対戦します。

    doc/codewalk/pig.go:72,89


    可変引数関数の宣言

    ratioStringのような可変引数関数は、可変数の引数を取ります。これらの引数は、関数内でスライスとして利用可能です。

    doc/codewalk/pig.go:91,94


    シミュレーション結果

    main関数は100の基本戦略を定義し、ラウンドロビン方式のトーナメントをシミュレートし、その後各戦略の勝敗記録を印刷します。

    これらの戦略の中で、25で留まることが最も良いですが、Pigの最適戦略ははるかに複雑です。

    doc/codewalk/pig.go:110,121