Pig the dice game/Player: Difference between revisions

From Rosetta Code
Content added Content deleted
m (→‎{{header|Tcl}}: note requirements)
Line 724: Line 724:


=={{header|Tcl}}==
=={{header|Tcl}}==
<table><tr><td>{{works with|Tcl|8.6}}</td><td>or alternatively with Tcl 8.5 and</td><td>{{libheader|TclOO}}</td></tr></table><!-- dirty trick! -->
First the structure of the game (from [[Pig the dice game#Tcl|the parent page]]):
First the structure of the game (from [[Pig the dice game#Tcl|the parent page]]):
<lang tcl>package require TclOO
<lang tcl>package require TclOO

Revision as of 17:49, 1 March 2013

Task
Pig the dice game/Player
You are encouraged to solve this task according to the task description, using any language you may know.

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

  1. 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.
  2. 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:

  1. A random player RandPlay that rolls randomly.
  2. The RollTo20 player that rolls if that rounds score is less than 20.
  3. 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)).

REXX

The strategy for a computer player is to roll again if the total score (including the current inning) has not won, and to roll again if the inning score is less than a quarter of the score needed to win.

The (somewhat aggressive) "quarter" strategy was chosen to give the advantage to a human (it was presumed that this dice game would be played with a CBLF). <lang rexx>/*REXX program plays pig (the dice game) with at least one human player.*/ signal on syntax; signal on novalue /*handle REXX program errors. */ sw=80-1 /*the LINESIZE bif would be nicer*/ parse arg hp cp win die _ . '(' names ")" /*obtain optional arguments.*/ if _\== then call err 'too many arguments were specified: ' _ @nhp = 'number of human players'  ; hp = scrutinize( hp, @nhp ,0, 0) @ncp = 'number of computer players'; cp = scrutinize( cp, @ncp ,0, 0) @sn2w = 'score needed to win'  ; win = scrutinize(win, @sn2w ,1,100) @nsid = 'number of sides in die'  ; die = scrutinize(die, @nsid ,2, 6) if hp==0 & cp==0 then cp=2 /*if both counts are zero, 2 HALs*/ if hp==1 & cp==0 then cp=1 /*if one human, then use 1 HAL.*/ L=0 /*maximum length of a player name*/

      do i=1  for hp+cp               /*get the player's names, maybe. */
      if i>hp  then @='HAL_'i"_the_computer"    /*use this for default.*/
               else @='player_'i                /* "    "   "     "    */
      name.i = translate( word( strip( word(names,i)) @, 1),,'_')
      L=max(L, length(name.i))        /*use L for nice name formatting.*/
      end   /*i*/                     /*underscores are changed─�blanks*/

hpn=hp; if hpn==0 then hpn='no' /*use normal English for display.*/ cpn=cp; if cpn==0 then cpn="no" /* " " " " " */ say 'Pig (the dice game) is being played with'

        if cpn\==0  then  say  right(cpn,9)  'computer player's(cp)
        if hpn\==0  then  say  right(hpn,9)  'human player's(hp)

say 'and the' @sn2w "is: " win ' (or greater).' !.=; dieNames='ace duece trey square nickle boxcar' /*die face names.*/

                                      /*note: snake eyes is for 2 aces.*/
               do i=1  for 6;  !.i=' ['word(dieNames,i)"] ";  end   /*i*/

s.=0 /*set all player's scores to zero*/ @=copies('─',9) /*an eyecatcher (for prompting). */ @jra='just rolled a '; @ati=', and the inning' /*nice literals to have.*/ /*──────────────────────────────────────────────────let's play some pig.*/

  do game=1;  in.=0                   /*set each inning's score to zero*/
  say;      say copies('█',sw)        /*display a fence for da eyeballs*/
       do k=1  for hp+cp              /*display the scores (as a recap)*/
       say 'The score for' left(name.k,L) "is " right(s.k,length(win))'.'
       end  /*k*/
  say copies('█',sw)                  /*display a fence for da eyeballs*/
    do j=1  for hp+cp                 /*let each player roll their dice*/
    say;  say copies('─',sw);         /*display a fence for da eyeballs*/
    it=word('You It', 1 + (j>hp))     /*pronoun choice:   You  or  It  */
    say name.j',  your total score (so far) in this pig game is:' s.j"."
      do  until stopped               /*keep prompting/rolling 'til not*/
      r=random(1,die); !=space(r !.r) /*for color, use a die-face name.*/
      in.j=in.j+r
      if r==1  then  do;  say it @jra ! || @ati "is a bust."; leave;  end
                          say it @jra ! || @ati "total is:"   in.j
      stopped=what2do(j)              /*determine|ask  to stop rolling.*/
      if j>hp & stopped then say ' and' name.j "elected to stop rolling."
      end   /*until stopped*/
    if r\==1     then s.j=s.j+in.j    /*if not a bust, then add inning.*/
    if s.j>=win  then leave game      /*we have a winner, so game ends.*/
    end     /*j*/                     /*that's the end of the players. */
  end       /*game*/

say; say; say; say; say center(name.j "won! ",sw,'═'); say; say; exit exit /*stick a fork in it, we're done.*/ /*──────────────────────────────────S subroutine────────────────────────*/ s: if arg(1)==1 then return arg(3); return word(arg(2) 's',1) /*plural?*/ /*──────────────────────────────────SCRUTINIZE subroutine───────────────*/ scrutinize: parse arg ?,what,minimum /*? is the number, or maybe not. */ if ?== |  ?==',' then return arg(4) if \datatype(?,'N') then call err what "isn't numeric: "  ?;  ?=?/1 if \datatype(?,'W') then call err what "isn't an integer: " ? if ?==0 & minimum>0 then call err what "can't be zero." if ?<minimum then call err what "can't be less than" minimum': ' ? return ? /*──────────────────────────────────what2do subroutine──────────────────*/ what2do: parse arg who /*"who" is a human or a computer.*/ if (j>hp & r+in.j>=win) then return 1 /*an easy choice for HAL.*/ if (j>hp & in.j>=win%4) then return 1 /*a simple stategy for HAL.*/ if j>hp then return 0 /*HAL says, keep truckin'! */ say @ name.who', what do you want to do? (a QUIT will stop the game),' say @ 'press ENTER to roll again, or anything else to STOP rolling.' pull action; action=space(action) /*remove any superfluous blanks. */ if \abbrev('QUIT',action,1) then return action\== say; say; say; say center(' quitting. ',sw,'─'); say; say; say; exit /*───────────────────────────────error handling subroutines and others.─*/ err: say; say; say center(' error! ',max(40,linesize()%2),"*"); say

              do j=1 for arg(); say arg(j); say; end; say; exit 13

novalue: syntax: call err 'REXX program' condition('C') "error",,

            condition('D'),'REXX source statement (line' sigl"):",,
            sourceline(sigl)</lang>

To play this game with two computer players (simulate), use the following arguments:

  0  2

Optionally, you may use (for instance):

 
  0  2  (  HAL  R2D2

to specify names for the (two) computer players.

Tcl

Works with: Tcl version 8.6
or alternatively with Tcl 8.5 and
Library: TclOO

First the structure of the game (from the parent page): <lang tcl>package require TclOO

oo::class create Player {

   variable me
   constructor {name} {

set me $name

   }
   method name {} {

return $me

   }
   method wantToRoll {safeScore roundScore} {}
   method rolled {who what} {

if {$who ne [self]} { #puts "[$who name] rolled a $what" }

   }
   method turnend {who score} {

if {$who ne [self]} { #puts "End of turn for [$who name] on $score" }

   }
   method winner {who score} {

if {$who ne [self]} { #puts "[$who name] is a winner, on $score" }

   }

}

oo::class create HumanPlayer {

   variable me
   superclass Player
   method wantToRoll {safeScore roundScore} {

while 1 { puts -nonewline "$me (on $safeScore+$roundScore) do you want to roll? (Y/n)" flush stdout if {[gets stdin line] < 0} { # EOF detected puts "" exit } if {$line eq "" || $line eq "y" || $line eq "Y"} { return 1 } if {$line eq "n" || $line eq "N"} { return 0 } }

   }
   method stuck {score} {

puts "$me sticks with score $score"

   }
   method busted {score} {

puts "Busted! ($me still on score $score)"

   }
   method won {score} {

puts "$me has won! (Score: $score)"

   }

}

proc rollDie {} {

   expr {1+int(rand() * 6)}

} proc rotateList {var} {

   upvar 1 $var l
   set l [list {*}[lrange $l 1 end] [lindex $l 0]]

} proc broadcast {players message score} {

   set p0 [lindex $players 0]
   foreach p $players {

$p $message $p0 $score

   }

}

proc pig {args} {

   set players $args
   set scores [lrepeat [llength $args] 0]
   while 1 {

set player [lindex $players 0] set safe [lindex $scores 0] set s 0 while 1 { if {$safe + $s >= 100} { incr safe $s $player won $safe broadcast $players winner $safe return $player } if {![$player wantToRoll $safe $s]} { lset scores 0 [incr safe $s] $player stuck $safe break } set roll [rollDie] broadcast $players rolled $roll if {$roll == 1} { $player busted $safe break } incr s $roll } broadcast $players turnend $safe rotateList players rotateList scores

   }

}</lang> Then the classes that create the various implemented strategies: <lang tcl>oo::class create RoboPlayer {

   superclass Player
   variable me
   constructor {name} {

# Add a symbol to the name to mark a robot... next "$name\u00ae"

   }
   method wantToRoll {safeScore roundScore} {

puts -nonewline "$me has ($safeScore,$roundScore)... " set decision [my Decide $safeScore $roundScore] puts [lindex {stick roll} $decision] return $decision

   }
   method stuck {score} {

puts "$me sticks with score $score"

   }
   method busted {score} {

puts "Busted! ($me still on score $score)"

   }
   method won {score} {

puts "$me has won! (Score: $score)"

   }

}

  1. Just takes a random decision as to what to play

oo::class create RandomPlayer {

   superclass RoboPlayer
   constructor {} {next "Random"}
   method Decide {a b} {expr {rand() < 0.5}}

}

  1. Rolls until it scores at least 20 from a round or goes bust

oo::class create To20Player {

   superclass RoboPlayer
   constructor {} {next "To20"}
   method Decide {safeScore roundScore} {expr {$roundScore < 20}}

}

  1. Like To20, but will roll desperately once another player reaches 80

oo::class create Desperate {

   superclass RoboPlayer
   variable me scores
   constructor {} {

next "Desperate" set scores {}

   }
   method Decide {safeScore roundScore} {

dict for {who val} $scores { if {$who ne [self] && $val >= 80} { return 1 } } return [expr {$roundScore < 20}]

   }
   # Keep an eye on other players
   method turnend {who score} {

next $who $score dict set scores $who $score

   }

}</lang> Demonstration, pitting the three of them against each other: <lang tcl>pig [RandomPlayer new] [To20Player new] [Desperate new]</lang>

Output:
Random® has (0,0)... roll
Busted! (Random® still on score 0)
To20® has (0,0)... roll
To20® has (0,4)... roll
Busted! (To20® still on score 0)
Desperate® has (0,0)... roll
Desperate® has (0,6)... roll
Desperate® has (0,10)... roll
Desperate® has (0,13)... roll
Desperate® has (0,17)... roll
Desperate® has (0,21)... stick
Desperate® sticks with score 21
Random® has (0,0)... roll
Busted! (Random® still on score 0)
To20® has (0,0)... roll
To20® has (0,2)... roll
To20® has (0,7)... roll
To20® has (0,11)... roll
To20® has (0,16)... roll
To20® has (0,19)... roll
To20® has (0,25)... stick
To20® sticks with score 25
Desperate® has (21,0)... roll
Desperate® has (21,6)... roll
Desperate® has (21,12)... roll
Busted! (Desperate® still on score 21)
Random® has (0,0)... stick
Random® sticks with score 0
To20® has (25,0)... roll
Busted! (To20® still on score 25)
Desperate® has (21,0)... roll
Desperate® has (21,4)... roll
Desperate® has (21,7)... roll
Desperate® has (21,9)... roll
Busted! (Desperate® still on score 21)
Random® has (0,0)... stick
Random® sticks with score 0
To20® has (25,0)... roll
To20® has (25,5)... roll
Busted! (To20® still on score 25)
Desperate® has (21,0)... roll
Desperate® has (21,2)... roll
Desperate® has (21,7)... roll
Desperate® has (21,11)... roll
Desperate® has (21,14)... roll
Desperate® has (21,19)... roll
Desperate® has (21,24)... stick
Desperate® sticks with score 45
Random® has (0,0)... stick
Random® sticks with score 0
To20® has (25,0)... roll
To20® has (25,5)... roll
To20® has (25,8)... roll
To20® has (25,14)... roll
To20® has (25,18)... roll
To20® has (25,20)... stick
To20® sticks with score 45
Desperate® has (45,0)... roll
Desperate® has (45,6)... roll
Desperate® has (45,11)... roll
Desperate® has (45,14)... roll
Desperate® has (45,18)... roll
Desperate® has (45,21)... stick
Desperate® sticks with score 66
Random® has (0,0)... roll
Random® has (0,2)... stick
Random® sticks with score 2
To20® has (45,0)... roll
To20® has (45,6)... roll
To20® has (45,12)... roll
To20® has (45,18)... roll
To20® has (45,24)... stick
To20® sticks with score 69
Desperate® has (66,0)... roll
Desperate® has (66,6)... roll
Busted! (Desperate® still on score 66)
Random® has (2,0)... roll
Busted! (Random® still on score 2)
To20® has (69,0)... roll
To20® has (69,4)... roll
To20® has (69,8)... roll
To20® has (69,14)... roll
To20® has (69,20)... stick
To20® sticks with score 89
Desperate® has (66,0)... roll
Desperate® has (66,6)... roll
Desperate® has (66,10)... roll
Desperate® has (66,12)... roll
Desperate® has (66,18)... roll
Desperate® has (66,23)... roll
Desperate® has (66,27)... roll
Desperate® has (66,29)... roll
Desperate® has (66,31)... roll
Desperate® has won! (Score: 101)