Pig the dice game/Player: Difference between revisions
m (__FORCETOC__) |
(Improvements) |
||
Line 23: | Line 23: | ||
=={{header|Go}}== |
=={{header|Go}}== |
||
<lang go> |
|||
{{novice example|Go}} |
|||
package pig |
|||
import ( |
|||
"fmt" |
|||
"math/rand" |
|||
"time" |
|||
) |
|||
type ( |
|||
This version includes a couple of |
|||
PlayerID int |
|||
different strategies and can |
|||
MessageID int |
|||
pit one against the other to |
|||
StrategyID int |
|||
see which one comes out ahead |
|||
more often. |
|||
PigGameData struct { |
|||
I'm sorry it's so long, but there was no time to make it shorter. |
|||
player PlayerID |
|||
turnCount int |
|||
turnRollCount int |
|||
turnScore int |
|||
lastRoll int |
|||
scores [2]int |
|||
verbose bool |
|||
} |
|||
) |
|||
const ( |
|||
<lang go> |
|||
// Status messages |
|||
package main |
|||
gameOver = iota |
|||
piggedOut |
|||
import ( |
|||
rolls |
|||
"fmt" |
|||
pointSpending |
|||
"math/rand" |
|||
holds |
|||
"time" |
|||
turn |
|||
gameOverSummary |
|||
// Players |
|||
player1 = PlayerID(0) |
|||
player2 = PlayerID(1) |
|||
noPlayer = PlayerID(-1) |
|||
// Max score |
|||
maxScore = 100 |
|||
// Strategies |
|||
scoreChaseStrat = iota |
|||
rollCountStrat |
|||
) |
) |
||
// Returns "s" if n != 1 |
|||
// Set up random number generator. |
|||
func |
func pluralS(n int) string { |
||
if n != 1 { |
|||
rand.Seed(time.Now().Unix()) |
|||
return "s" |
|||
time.Sleep(time.Duration(rand.Intn(3)+1) * time.Second) |
|||
} |
|||
return "" |
|||
} |
} |
||
// Creates an intializes a new PigGameData structure, returns a *PigGameData |
|||
type PigGameData struct { |
|||
func New() *PigGameData { |
|||
Player int |
|||
return &PigGameData{0, 0, 0, 0, 0, [2]int{0, 0}, false} |
|||
TurnCount int |
|||
TurnRollCount int |
|||
TurnScore int |
|||
Scores [2]int |
|||
Printing bool |
|||
} |
} |
||
// Create a status message for a given message ID |
|||
// Run the simulation. |
|||
func (pg *PigGameData) statusMessage(id MessageID) string { |
|||
func main() { |
|||
var msg string |
|||
pg := new(PigGameData) |
|||
switch id { |
|||
pg.Printing = true |
|||
case gameOver: |
|||
strategies := [2](func(*PigGameData) bool){scoreChaseStrat, rollCountStrat} |
|||
msg = fmt.Sprintf("Game is over after %d turns", pg.turnCount) |
|||
for !pg.IsGameOver() { |
|||
case piggedOut: |
|||
pg.Play(strategies[pg.Player]) |
|||
msg = fmt.Sprintf(" Pigged out after %d roll%s", pg.turnRollCount, pluralS(pg.turnRollCount)) |
|||
} |
|||
case rolls: |
|||
fmt.Printf("game over after %d turns\n player 1 %d\n player 2 %d\n", |
|||
msg = fmt.Sprintf(" Rolls %d", pg.lastRoll) |
|||
pg.TurnCount, pg.GetPlayerScore(0), pg.GetPlayerScore(1)) |
|||
case pointSpending: |
|||
msg = fmt.Sprintf(" %d point%s pending", pg.turnScore, pluralS(pg.turnScore)) |
|||
case holds: |
|||
msg = fmt.Sprintf(" Holds after %d turns, adding %d points for a total of %d", pg.turnRollCount, pg.turnScore, pg.PlayerScore(noPlayer)) |
|||
case turn: |
|||
msg = fmt.Sprintf("Player %d's turn:", pg.player+1) |
|||
case gameOverSummary: |
|||
msg = fmt.Sprintf("Game over after %d turns\n player 1 %d\n player 2 %d\n", pg.turnCount, pg.PlayerScore(player1), pg.PlayerScore(player2)) |
|||
} |
|||
return msg |
|||
} |
} |
||
// Print a status message, if pg.Verbose is true |
|||
func (pg *PigGameData) Play(hs (func(*PigGameData) bool)) (keepPlaying bool) { |
|||
func (pg *PigGameData) PrintStatus(id MessageID) { |
|||
if pg.IsGameOver() { |
|||
if pg.verbose { |
|||
pg.Print(fmt.Sprintf("game is over after %d turns", pg.TurnCount)) |
|||
fmt.Println(pg.statusMessage(id)) |
|||
return false |
|||
} |
|||
} |
|||
} |
|||
// Play a given strategy |
|||
if pg.TurnCount == 0 { |
|||
func (pg *PigGameData) Play(id StrategyID) (keepPlaying bool) { |
|||
pg.Player = 1 |
|||
if pg.GameOver() { |
|||
pg.PrintStatus(gameOver) |
|||
} |
|||
return false |
|||
} |
|||
if pg.turnCount == 0 { |
|||
roll := rand.Intn(6) + 1 |
|||
pg.player = player2 |
|||
pg.Print(fmt.Sprintf(" rolls %d", roll)) |
|||
pg.NextPlayer() |
|||
pg.TurnRollCount++ |
|||
} |
|||
if roll == 1 { |
|||
pg.Print(fmt.Sprintf(" pigged out after %d roll%s", |
|||
pg.TurnRollCount, plural(pg.TurnRollCount))) |
|||
pg.NextPlayer() |
|||
} else { |
|||
pg.TurnScore += roll |
|||
pg.Print(fmt.Sprintf(" %d point%s pending", pg.TurnScore, plural(pg.TurnScore))) |
|||
if hs(pg) { |
|||
pg.Hold() |
|||
pg.NextPlayer() |
|||
} |
|||
} |
|||
return true |
|||
} |
|||
pg.lastRoll = rand.Intn(6) + 1 |
|||
func (pg *PigGameData) Init() { |
|||
pg.PrintStatus(rolls) |
|||
pg.Player = 0 |
|||
pg.turnRollCount++ |
|||
pg.TurnCount = 0 |
|||
if pg.lastRoll == 1 { |
|||
pg.PrintStatus(piggedOut) |
|||
pg.TurnScore = 0 |
|||
pg.NextPlayer() |
|||
pg.Scores = [2]int{0, 0} |
|||
} else { |
|||
pg.Printing = false |
|||
pg.turnScore += pg.lastRoll |
|||
pg.PrintStatus(pointSpending) |
|||
success := false |
|||
switch id { |
|||
case scoreChaseStrat: |
|||
success = pg.scoreChaseStrategy() |
|||
case rollCountStrat: |
|||
success = pg.rollCountStrategy() |
|||
} |
|||
if success { |
|||
pg.Hold() |
|||
pg.NextPlayer() |
|||
} |
|||
} |
|||
return true |
|||
} |
} |
||
// Get the score for a given player |
|||
func (pg *PigGameData) Print(msg string) { |
|||
func (pg *PigGameData) PlayerScore(id PlayerID) int { |
|||
if pg.Printing { |
|||
if id == noPlayer { |
|||
fmt.Println(msg) |
|||
return pg.scores[pg.player] |
|||
} |
|||
} |
|||
return pg.scores[id] |
|||
} |
} |
||
// Check if the game is over |
|||
func (pg *PigGameData) GetPlayerScore(player int) int { |
|||
func (pg *PigGameData) GameOver() bool { |
|||
if player == -1 { |
|||
return pg.scores[player1] >= maxScore || pg.scores[player2] >= maxScore |
|||
player = pg.Player |
|||
} |
|||
return pg.Scores[player] |
|||
} |
} |
||
// Returns the Player ID if there is a winner, or -1 |
|||
func (pg *PigGameData) IsGameOver() bool { |
|||
func (pg *PigGameData) Winner() PlayerID { |
|||
return pg.Scores[0] >= 100 || pg.Scores[1] >= 100 |
|||
for index, score := range pg.scores { |
|||
if score >= maxScore { |
|||
return PlayerID(index) |
|||
} |
|||
} |
|||
return noPlayer |
|||
} |
} |
||
// Get the ID of the other player |
|||
func (pg *PigGameData) Winner() int { |
|||
func (pg *PigGameData) otherPlayer() PlayerID { |
|||
for i, s := range pg.Scores { |
|||
// 0 becomes 1, 1 becomes 0 |
|||
if s >= 100 { |
|||
return 1 - pg.player |
|||
return i |
|||
} |
|||
} |
|||
return -1 |
|||
} |
} |
||
func (pg *PigGameData) Hold() { |
func (pg *PigGameData) Hold() { |
||
pg.scores[pg.player] += pg.turnScore |
|||
pg.PrintStatus(holds) |
|||
pg.Print(fmt.Sprintf(" holds after %d turns, adding %d points for a total of %d", |
|||
pg.turnRollCount, pg.turnScore = 0, 0 |
|||
pg.TurnRollCount, pg.TurnScore, pg.GetPlayerScore(-1))) |
|||
pg.TurnRollCount = 0 |
|||
pg.TurnScore = 0 |
|||
} |
} |
||
func (pg *PigGameData) NextPlayer() { |
func (pg *PigGameData) NextPlayer() { |
||
pg.turnCount++ |
|||
pg.turnRollCount, pg.turnScore = 0, 0 |
|||
pg.TurnRollCount = 0 |
|||
pg.player = pg.otherPlayer() |
|||
pg.TurnScore = 0 |
|||
pg.PrintStatus(turn) |
|||
pg.Player = (pg.Player + 1) % 2 |
|||
pg.Print(fmt.Sprintf("player %d's turn:", pg.Player+1)) |
|||
} |
} |
||
func |
func (pg *PigGameData) rollCountStrategy() bool { |
||
return pg.turnRollCount >= 3 |
|||
//fmt.Printf(" roll %d of 3\n", pg.TurnRollCount) |
|||
return pg.TurnRollCount >= 3 |
|||
} |
} |
||
func |
func (pg *PigGameData) scoreChaseStrategy() bool { |
||
myScore := pg.PlayerScore(pg.player) |
|||
otherScore := pg.PlayerScore(pg.otherPlayer()) |
|||
myScore, hisScore := pg.GetPlayerScore(myNumber), pg.GetPlayerScore(hisNumber) |
|||
myPendingScore := pg.turnScore + myScore |
|||
return myPendingScore >= maxScore || myPendingScore > otherScore || pg.turnRollCount >= 5 |
|||
//fmt.Printf(" %d vs %d\n", myPendingScore, hisScore) |
|||
return myPendingScore >= 100 || myPendingScore > hisScore || pg.TurnRollCount >= 5 |
|||
} |
} |
||
// Run the simulation |
|||
func plural(n int) string { |
|||
func main() { |
|||
if n != 1 { |
|||
// Seed the random number generator |
|||
return "s" |
|||
rand.Seed(time.Now().UnixNano()) |
|||
} |
|||
return "" |
|||
// Start a new game |
|||
pg := New() |
|||
pg.verbose = true |
|||
strategies := [2]StrategyID{scoreChaseStrat, rollCountStrat} |
|||
// Play until game over |
|||
for !pg.GameOver() { |
|||
pg.Play(strategies[pg.player]) |
|||
} |
|||
pg.PrintStatus(gameOverSummary) |
|||
} |
} |
||
</lang> |
</lang> |
||
Line 175: | Line 221: | ||
<pre> |
<pre> |
||
Player 1's turn: |
|||
Rolls 4 |
|||
4 points pending |
4 points pending |
||
Holds after 1 turns, adding 4 points for a total of 4 |
|||
Player 2's turn: |
|||
Rolls 4 |
|||
4 points pending |
4 points pending |
||
Rolls 1 |
|||
Pigged out after 2 rolls |
|||
Player 1's turn: |
|||
Rolls 6 |
|||
6 points pending |
|||
Holds after 1 turns, adding 6 points for a total of 10 |
|||
Player 2's turn: |
|||
Rolls 6 |
|||
6 points pending |
|||
Rolls 3 |
|||
9 points pending |
|||
Rolls 4 |
|||
13 points pending |
|||
Holds after 3 turns, adding 13 points for a total of 13 |
|||
Player 1's turn: |
|||
Rolls 4 |
|||
4 points pending |
4 points pending |
||
Holds after 1 turns, adding 4 points for a total of 14 |
|||
rolls 1 |
|||
Player 2's turn: |
|||
pigged out after 2 rolls |
|||
Rolls 4 |
|||
player 1's turn: |
|||
4 points pending |
|||
Rolls 6 |
|||
10 points pending |
|||
Rolls 1 |
|||
Pigged out after 3 rolls |
|||
Player 1's turn: |
|||
Rolls 4 |
|||
4 points pending |
|||
Holds after 1 turns, adding 4 points for a total of 18 |
|||
Player 2's turn: |
|||
Rolls 3 |
|||
3 points pending |
3 points pending |
||
Rolls 4 |
|||
holds after 1 turns, adding 3 points for a total of 12 |
|||
7 points pending |
|||
player 2's turn: |
|||
Rolls 2 |
|||
9 points pending |
|||
Holds after 3 turns, adding 9 points for a total of 22 |
|||
Player 1's turn: |
|||
Rolls 2 |
|||
2 points pending |
2 points pending |
||
Rolls 1 |
|||
Pigged out after 2 rolls |
|||
Player 2's turn: |
|||
Rolls 1 |
|||
Pigged out after 1 roll |
|||
Player 1's turn: |
|||
Rolls 4 |
|||
4 points pending |
|||
Rolls 1 |
|||
Pigged out after 2 rolls |
|||
Player 2's turn: |
|||
Rolls 5 |
|||
5 points pending |
5 points pending |
||
Rolls 1 |
|||
Pigged out after 2 rolls |
|||
Player 1's turn: |
|||
Rolls 5 |
|||
5 points pending |
5 points pending |
||
Holds after 1 turns, adding 5 points for a total of 23 |
|||
Player 2's turn: |
|||
. |
|||
Rolls 5 |
|||
. |
|||
5 points pending |
|||
. |
|||
Rolls 4 |
|||
player 1's turn: |
|||
9 points pending |
|||
Rolls 4 |
|||
13 points pending |
|||
Holds after 3 turns, adding 13 points for a total of 35 |
|||
Player 1's turn: |
|||
Rolls 1 |
|||
Pigged out after 1 roll |
|||
Player 2's turn: |
|||
Rolls 3 |
|||
3 points pending |
|||
Rolls 3 |
|||
6 points pending |
|||
Rolls 2 |
|||
8 points pending |
|||
Holds after 3 turns, adding 8 points for a total of 43 |
|||
Player 1's turn: |
|||
Rolls 1 |
|||
Pigged out after 1 roll |
|||
Player 2's turn: |
|||
Rolls 6 |
|||
6 points pending |
|||
Rolls 4 |
|||
10 points pending |
|||
Rolls 1 |
|||
Pigged out after 3 rolls |
|||
Player 1's turn: |
|||
Rolls 4 |
|||
4 points pending |
|||
Rolls 1 |
|||
Pigged out after 2 rolls |
|||
Player 2's turn: |
|||
Rolls 2 |
|||
2 points pending |
2 points pending |
||
Rolls 4 |
|||
6 points pending |
|||
Rolls 2 |
|||
8 points pending |
8 points pending |
||
Holds after 3 turns, adding 8 points for a total of 51 |
|||
Player 1's turn: |
|||
Rolls 1 |
|||
Pigged out after 1 roll |
|||
3 points pending |
|||
Player 2's turn: |
|||
rolls 1 |
|||
Rolls 4 |
|||
pigged out after 2 rolls |
|||
4 points pending |
|||
player 1's turn: |
|||
Rolls 2 |
|||
6 points pending |
|||
Rolls 3 |
|||
9 points pending |
|||
Holds after 3 turns, adding 9 points for a total of 60 |
|||
Player 1's turn: |
|||
Rolls 2 |
|||
2 points pending |
2 points pending |
||
Rolls 6 |
|||
holds after 1 turns, adding 2 points for a total of 88 |
|||
8 points pending |
|||
player 2's turn: |
|||
Rolls 3 |
|||
11 points pending |
|||
Rolls 6 |
|||
17 points pending |
|||
Rolls 4 |
|||
21 points pending |
|||
Holds after 5 turns, adding 21 points for a total of 44 |
|||
Player 2's turn: |
|||
Rolls 4 |
|||
4 points pending |
|||
Rolls 2 |
|||
6 points pending |
|||
Rolls 3 |
|||
9 points pending |
9 points pending |
||
Holds after 3 turns, adding 9 points for a total of 69 |
|||
Player 1's turn: |
|||
Rolls 6 |
|||
6 points pending |
|||
Rolls 5 |
|||
11 points pending |
|||
Rolls 6 |
|||
17 points pending |
|||
Rolls 5 |
|||
22 points pending |
|||
Rolls 4 |
|||
26 points pending |
|||
Holds after 5 turns, adding 26 points for a total of 70 |
|||
Player 2's turn: |
|||
Rolls 5 |
|||
5 points pending |
|||
Rolls 4 |
|||
9 points pending |
9 points pending |
||
Rolls 2 |
|||
holds after 2 turns, adding 9 points for a total of 97 |
|||
11 points pending |
|||
player 2's turn: |
|||
Holds after 3 turns, adding 11 points for a total of 80 |
|||
rolls 2 |
|||
Player 1's turn: |
|||
Rolls 6 |
|||
6 points pending |
|||
Rolls 6 |
|||
12 points pending |
|||
Holds after 2 turns, adding 12 points for a total of 82 |
|||
Player 2's turn: |
|||
Rolls 2 |
|||
2 points pending |
2 points pending |
||
Rolls 3 |
|||
5 points pending |
|||
Rolls 3 |
|||
8 points pending |
8 points pending |
||
Holds after 3 turns, adding 8 points for a total of 88 |
|||
rolls 6 |
|||
Player 1's turn: |
|||
14 points pending |
|||
Rolls 5 |
|||
holds after 3 turns, adding 14 points for a total of 108 |
|||
5 points pending |
|||
player 1's turn: |
|||
Rolls 2 |
|||
game over after 41 turns |
|||
7 points pending |
|||
player 1 97 |
|||
Holds after 2 turns, adding 7 points for a total of 89 |
|||
player 2 108 |
|||
Player 2's turn: |
|||
Rolls 3 |
|||
3 points pending |
|||
Rolls 2 |
|||
5 points pending |
|||
Rolls 5 |
|||
10 points pending |
|||
Holds after 3 turns, adding 10 points for a total of 98 |
|||
Player 1's turn: |
|||
Rolls 6 |
|||
6 points pending |
|||
Rolls 3 |
|||
9 points pending |
|||
Rolls 6 |
|||
15 points pending |
|||
Holds after 3 turns, adding 15 points for a total of 104 |
|||
Player 2's turn: |
|||
Game over after 32 turns |
|||
player 1 104 |
|||
player 2 98 |
|||
</pre> |
</pre> |
||
Revision as of 23:57, 28 November 2012
The task is to create a dice simulator and scorer of Pig the dice game and add to it the ability to play the game to at least one strategy.
- State here the play strategies involved.
- Show play during a game here.
As a stretch goal:
- Simulate playing the game a number of times with two players of given strategies and report here summary statistics such as, but not restricted to, the influence of going first or which strategy seems stronger.
- Game Rules
The game of Pig is a multiplayer game played with a single six-sided die. The object of the game is to reach 100 points or more. Play is taken in turns. On each person's turn that person has the option of either
- Rolling the dice: where a roll of two to six is added to their score for that turn and the player's turn continues as the player is given the same choice again; or a roll of 1 loses the player's total points for that turn and their turn finishes with play passing to the next player.
- Holding: The player's score for that round is added to their total and becomes safe from the effects of throwing a one. The player's turn finishes with play passing to the next player.
- Reference
Go
<lang go> package pig
import ( "fmt" "math/rand" "time" )
type ( PlayerID int MessageID int StrategyID int
PigGameData struct { player PlayerID turnCount int turnRollCount int turnScore int lastRoll int scores [2]int verbose bool } )
const ( // Status messages gameOver = iota piggedOut rolls pointSpending holds turn gameOverSummary // Players player1 = PlayerID(0) player2 = PlayerID(1) noPlayer = PlayerID(-1) // Max score maxScore = 100 // Strategies scoreChaseStrat = iota rollCountStrat )
// Returns "s" if n != 1 func pluralS(n int) string { if n != 1 { return "s" } return "" }
// Creates an intializes a new PigGameData structure, returns a *PigGameData func New() *PigGameData { return &PigGameData{0, 0, 0, 0, 0, [2]int{0, 0}, false} }
// Create a status message for a given message ID func (pg *PigGameData) statusMessage(id MessageID) string { var msg string switch id { case gameOver: msg = fmt.Sprintf("Game is over after %d turns", pg.turnCount) case piggedOut: msg = fmt.Sprintf(" Pigged out after %d roll%s", pg.turnRollCount, pluralS(pg.turnRollCount)) case rolls: msg = fmt.Sprintf(" Rolls %d", pg.lastRoll) case pointSpending: msg = fmt.Sprintf(" %d point%s pending", pg.turnScore, pluralS(pg.turnScore)) case holds: msg = fmt.Sprintf(" Holds after %d turns, adding %d points for a total of %d", pg.turnRollCount, pg.turnScore, pg.PlayerScore(noPlayer)) case turn: msg = fmt.Sprintf("Player %d's turn:", pg.player+1) case gameOverSummary: msg = fmt.Sprintf("Game over after %d turns\n player 1 %d\n player 2 %d\n", pg.turnCount, pg.PlayerScore(player1), pg.PlayerScore(player2)) } return msg }
// Print a status message, if pg.Verbose is true func (pg *PigGameData) PrintStatus(id MessageID) { if pg.verbose { fmt.Println(pg.statusMessage(id)) } }
// Play a given strategy func (pg *PigGameData) Play(id StrategyID) (keepPlaying bool) { if pg.GameOver() { pg.PrintStatus(gameOver) return false }
if pg.turnCount == 0 { pg.player = player2 pg.NextPlayer() }
pg.lastRoll = rand.Intn(6) + 1 pg.PrintStatus(rolls) pg.turnRollCount++ if pg.lastRoll == 1 { pg.PrintStatus(piggedOut) pg.NextPlayer() } else { pg.turnScore += pg.lastRoll pg.PrintStatus(pointSpending) success := false switch id { case scoreChaseStrat: success = pg.scoreChaseStrategy() case rollCountStrat: success = pg.rollCountStrategy() } if success { pg.Hold() pg.NextPlayer() } } return true }
// Get the score for a given player func (pg *PigGameData) PlayerScore(id PlayerID) int { if id == noPlayer { return pg.scores[pg.player] } return pg.scores[id] }
// Check if the game is over func (pg *PigGameData) GameOver() bool { return pg.scores[player1] >= maxScore || pg.scores[player2] >= maxScore }
// Returns the Player ID if there is a winner, or -1 func (pg *PigGameData) Winner() PlayerID { for index, score := range pg.scores { if score >= maxScore { return PlayerID(index) } } return noPlayer }
// Get the ID of the other player func (pg *PigGameData) otherPlayer() PlayerID { // 0 becomes 1, 1 becomes 0 return 1 - pg.player }
func (pg *PigGameData) Hold() { pg.scores[pg.player] += pg.turnScore pg.PrintStatus(holds) pg.turnRollCount, pg.turnScore = 0, 0 }
func (pg *PigGameData) NextPlayer() { pg.turnCount++ pg.turnRollCount, pg.turnScore = 0, 0 pg.player = pg.otherPlayer() pg.PrintStatus(turn) }
func (pg *PigGameData) rollCountStrategy() bool { return pg.turnRollCount >= 3 }
func (pg *PigGameData) scoreChaseStrategy() bool { myScore := pg.PlayerScore(pg.player) otherScore := pg.PlayerScore(pg.otherPlayer()) myPendingScore := pg.turnScore + myScore return myPendingScore >= maxScore || myPendingScore > otherScore || pg.turnRollCount >= 5 }
// Run the simulation func main() { // Seed the random number generator rand.Seed(time.Now().UnixNano())
// Start a new game pg := New() pg.verbose = true strategies := [2]StrategyID{scoreChaseStrat, rollCountStrat}
// Play until game over for !pg.GameOver() { pg.Play(strategies[pg.player]) } pg.PrintStatus(gameOverSummary) } </lang>
Sample run, player one just tries to keep ahead, while player two always tries to take three rolls, no more.
Player 1's turn: Rolls 4 4 points pending Holds after 1 turns, adding 4 points for a total of 4 Player 2's turn: Rolls 4 4 points pending Rolls 1 Pigged out after 2 rolls Player 1's turn: Rolls 6 6 points pending Holds after 1 turns, adding 6 points for a total of 10 Player 2's turn: Rolls 6 6 points pending Rolls 3 9 points pending Rolls 4 13 points pending Holds after 3 turns, adding 13 points for a total of 13 Player 1's turn: Rolls 4 4 points pending Holds after 1 turns, adding 4 points for a total of 14 Player 2's turn: Rolls 4 4 points pending Rolls 6 10 points pending Rolls 1 Pigged out after 3 rolls Player 1's turn: Rolls 4 4 points pending Holds after 1 turns, adding 4 points for a total of 18 Player 2's turn: Rolls 3 3 points pending Rolls 4 7 points pending Rolls 2 9 points pending Holds after 3 turns, adding 9 points for a total of 22 Player 1's turn: Rolls 2 2 points pending Rolls 1 Pigged out after 2 rolls Player 2's turn: Rolls 1 Pigged out after 1 roll Player 1's turn: Rolls 4 4 points pending Rolls 1 Pigged out after 2 rolls Player 2's turn: Rolls 5 5 points pending Rolls 1 Pigged out after 2 rolls Player 1's turn: Rolls 5 5 points pending Holds after 1 turns, adding 5 points for a total of 23 Player 2's turn: Rolls 5 5 points pending Rolls 4 9 points pending Rolls 4 13 points pending Holds after 3 turns, adding 13 points for a total of 35 Player 1's turn: Rolls 1 Pigged out after 1 roll Player 2's turn: Rolls 3 3 points pending Rolls 3 6 points pending Rolls 2 8 points pending Holds after 3 turns, adding 8 points for a total of 43 Player 1's turn: Rolls 1 Pigged out after 1 roll Player 2's turn: Rolls 6 6 points pending Rolls 4 10 points pending Rolls 1 Pigged out after 3 rolls Player 1's turn: Rolls 4 4 points pending Rolls 1 Pigged out after 2 rolls Player 2's turn: Rolls 2 2 points pending Rolls 4 6 points pending Rolls 2 8 points pending Holds after 3 turns, adding 8 points for a total of 51 Player 1's turn: Rolls 1 Pigged out after 1 roll Player 2's turn: Rolls 4 4 points pending Rolls 2 6 points pending Rolls 3 9 points pending Holds after 3 turns, adding 9 points for a total of 60 Player 1's turn: Rolls 2 2 points pending Rolls 6 8 points pending Rolls 3 11 points pending Rolls 6 17 points pending Rolls 4 21 points pending Holds after 5 turns, adding 21 points for a total of 44 Player 2's turn: Rolls 4 4 points pending Rolls 2 6 points pending Rolls 3 9 points pending Holds after 3 turns, adding 9 points for a total of 69 Player 1's turn: Rolls 6 6 points pending Rolls 5 11 points pending Rolls 6 17 points pending Rolls 5 22 points pending Rolls 4 26 points pending Holds after 5 turns, adding 26 points for a total of 70 Player 2's turn: Rolls 5 5 points pending Rolls 4 9 points pending Rolls 2 11 points pending Holds after 3 turns, adding 11 points for a total of 80 Player 1's turn: Rolls 6 6 points pending Rolls 6 12 points pending Holds after 2 turns, adding 12 points for a total of 82 Player 2's turn: Rolls 2 2 points pending Rolls 3 5 points pending Rolls 3 8 points pending Holds after 3 turns, adding 8 points for a total of 88 Player 1's turn: Rolls 5 5 points pending Rolls 2 7 points pending Holds after 2 turns, adding 7 points for a total of 89 Player 2's turn: Rolls 3 3 points pending Rolls 2 5 points pending Rolls 5 10 points pending Holds after 3 turns, adding 10 points for a total of 98 Player 1's turn: Rolls 6 6 points pending Rolls 3 9 points pending Rolls 6 15 points pending Holds after 3 turns, adding 15 points for a total of 104 Player 2's turn: Game over after 32 turns player 1 104 player 2 98
J
This is a partial implementation of the current task.
This is a routine to estimate the value of rolling, given the current total of rolls which the player is building (left argument) and the current total of rolls which are a permanent part of the player's score (right argument).
If the expected value is positive, it's probably in the best interest of the player to take the roll. That said, a more sophisticated strategy might play cautiously when a player is sufficiently ahead of the other player(s).
<lang j>pigval=:4 :0
(+/%#)(-x),}.(1+i.6)<.100-y+x
)</lang>
Examples:
<lang j> 10 pigval 90 _1.66667</lang>
If we have 10 points from our current rolls and have 90 permanent points, rolling again is a bad idea.
<lang j> 0 5 10 15 20 pigval"0/60 65 70 75 80 85 90 95 100
3.33333 3.33333 3.33333 3.33333 3.33333 3.33333 3.33333 3.16667 0 2.5 2.5 2.5 2.5 2.5 2.5 2.33333 _0.833333 _5 1.66667 1.66667 1.66667 1.66667 1.66667 1.5 _1.66667 _5.83333 _10
0.833333 0.833333 0.833333 0.833333 0.666667 _2.5 _6.66667 _10.8333 _15
0 0 0 _0.166667 _3.33333 _7.5 _11.6667 _15.8333 _20</lang>
If we have 70 permanent points (or less) we should probably re-roll when our uncommitted rolls total to less than 20.
<lang j> (1+i.19) ([,:1+i:~) +/ 0 < pigval"0/~ 1+i.100
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
98 97 96 95 93 92 91 90 89 87 86 85 84 82 81 80 78 77 75</lang>
This is a table of decision points. First row represents sum of our current uncommitted rolls. Second row represents the maximum permanent score where you should roll again with that number of uncommitted points, if we are using this estimation mechanism to choose our actions. Note that the first four columns here should have some obvious validity -- for example, if we have 96 permanent points and we have rolled 4 uncommitted points, we have won the game and we gain nothing from rerolling... Note also that this decision mechanism says we should never reroll if we have at least 20 uncommitted points.
Python
There are now three player strategies:
- A random player RandPlay that rolls randomly.
- The RollTo20 player that rolls if that rounds score is less than 20.
- The Desparat player that plays like RollTo20 until any player gets within 20 of winning whereupon it desperately keeps rolling.
Details of the RollTo20 and Desparat strategy came from a paper referenced from here.
Player instances are passed full (single) game statistics and so can be more complex in their behaviour.
Notice how Pythons Counter class from the standard library is used to collate the winning statistics near the end of the program without much additional code.
<lang python>#!/usr/bin/python3
See: http://en.wikipedia.org/wiki/Pig_(dice)
This program scores, throws the dice, and plays for an N player game of Pig.
from random import randint from collections import namedtuple import random from pprint import pprint as pp from collections import Counter
playercount = 2
maxscore = 100
maxgames = 100000
Game = namedtuple('Game', 'players, maxscore, rounds')
Round = namedtuple('Round', 'who, start, scores, safe')
class Player():
def __init__(self, player_index): self.player_index = player_index
def __repr__(self): return '%s(%i)' % (self.__class__.__name__, self.player_index)
def __call__(self, safescore, scores, game): 'Returns boolean True to roll again' pass
class RandPlay(Player):
def __call__(self, safe, scores, game): 'Returns random boolean choice of whether to roll again' return bool(random.randint(0, 1))
class RollTo20(Player):
def __call__(self, safe, scores, game): 'Roll again if this rounds score < 20' return (((sum(scores) + safe[self.player_index]) < maxscore) # Haven't won yet and(sum(scores) < 20)) # Not at 20 this round
class Desparat(Player):
def __call__(self, safe, scores, game): 'Roll again if this rounds score < 20 or someone is within 20 of winning' return (((sum(scores) + safe[self.player_index]) < maxscore) # Haven't won yet and( (sum(scores) < 20) # Not at 20 this round or max(safe) >= (maxscore - 20))) # Someone's close
def game__str__(self):
'Pretty printer for Game class' return ("Game(players=%r, maxscore=%i,\n rounds=[\n %s\n ])" % (self.players, self.maxscore, ',\n '.join(repr(round) for round in self.rounds)))
Game.__str__ = game__str__
def winningorder(players, safescores):
'Return (players in winning order, their scores)' return tuple(zip(*sorted(zip(players, safescores), key=lambda x: x[1], reverse=True)))
def playpig(game):
Plays the game of pig returning the players in winning order and their scores whilst updating argument game with the details of play. players, maxscore, rounds = game playercount = len(players) safescore = [0] * playercount # Safe scores for each player player = 0 # Who plays this round scores=[] # Individual scores this round
while max(safescore) < maxscore: startscore = safescore[player] rolling = players[player](safescore, scores, game) if rolling: rolled = randint(1, 6) scores.append(rolled) if rolled == 1: # Bust! round = Round(who=players[player], start=startscore, scores=scores, safe=safescore[player]) rounds.append(round) scores, player = [], (player + 1) % playercount else: # Stick safescore[player] += sum(scores) round = Round(who=players[player], start=startscore, scores=scores, safe=safescore[player]) rounds.append(round) if safescore[player] >= maxscore: break scores, player = [], (player + 1) % playercount
# return players in winning order and all scores return winningorder(players, safescore)
if __name__ == '__main__':
game = Game(players=tuple(RandPlay(i) for i in range(playercount)), maxscore=20, rounds=[]) print('ONE GAME') print('Winning order: %r; Respective scores: %r\n' % playpig(game)) print(game) game = Game(players=tuple(RandPlay(i) for i in range(playercount)), maxscore=maxscore, rounds=[]) algos = (RollTo20, RandPlay, Desparat) print('\n\nMULTIPLE STATISTICS using %r\n for %i GAMES' % (', '.join(p.__name__ for p in algos), maxgames,)) winners = Counter(repr(playpig(game._replace(players=tuple(random.choice(algos)(i) for i in range(playercount)), rounds=[]))[0]) for i in range(maxgames)) print(' Players(position) winning on left; occurrences on right:\n %s' % ',\n '.join(str(w) for w in winners.most_common()))</lang>
- Output:
First is shown the game data for a single game with reduced maxscore then statistics on multiple games.
Desparat beats RollTo20 beats RandPlay on average. It doesn't matter if they play first or not when playing against another strategies. When both players use the same strategies there may be an advantage in going first.
ONE GAME Winner: RandPlay(0); Scores: [24, 12] Game(players=(RandPlay(0), RandPlay(1)), maxscore=20, rounds=[ Round(who=RandPlay(0), start=0, scores=[], safe=0), Round(who=RandPlay(1), start=0, scores=[6, 2], safe=8), Round(who=RandPlay(0), start=0, scores=[], safe=0), Round(who=RandPlay(1), start=8, scores=[], safe=8), Round(who=RandPlay(0), start=0, scores=[], safe=0), Round(who=RandPlay(1), start=8, scores=[4], safe=12), Round(who=RandPlay(0), start=0, scores=[4, 5, 6, 4, 5], safe=24) ]) MULTIPLE STATISTICS using 'RollTo20, RandPlay, Desparat' for 100000 GAMES Players(position) winning on left; occurrences on right: ('(Desparat(1), RandPlay(0))', 11152), ('(RollTo20(1), RandPlay(0))', 11114), ('(Desparat(0), RandPlay(1))', 11072), ('(RollTo20(0), RandPlay(1))', 11007), ('(Desparat(0), RollTo20(1))', 6405), ('(RollTo20(0), RollTo20(1))', 6013), ('(Desparat(0), Desparat(1))', 5820), ('(Desparat(1), RollTo20(0))', 5772), ('(RandPlay(0), RandPlay(1))', 5667), ('(RandPlay(1), RandPlay(0))', 5481), ('(RollTo20(0), Desparat(1))', 5385), ('(Desparat(1), Desparat(0))', 5235), ('(RollTo20(1), RollTo20(0))', 5090), ('(RollTo20(1), Desparat(0))', 4625), ('(RandPlay(0), Desparat(1))', 59), ('(RandPlay(1), RollTo20(0))', 37), ('(RandPlay(1), Desparat(0))', 35), ('(RandPlay(0), RollTo20(1))', 31)
Note: ('(RollTo20(1), RandPlay(0))', 25063) means that the algorithm RollTo20 playing as the second player, (1) wins against algorithm RandPlay of the first player, (0) and wins 25063 times. (Zero based indexing so the first player is player(0)).