🐷

A game of Pig exercise

Write a command-line program that simulates the game of Pig.
Pig is a two-player game played with a 6-sided die.
The game has the following rules:
Each turn, a player repeatedly rolls a die until either a 1 is rolled or the player decides to “hold”:
  • If the player rolls a 1, they score nothing, and it becomes the next player’s turn.
  • If the player rolls any other number, it is added to their turn total, and their turn continues. The player can then decide to continue to roll again or “hold”.
  • If a player chooses to “hold”, their current turn total is added to their score, and it becomes the next player’s turn.
The first player to score 100 or more points wins.
For example, the first player, Donald, begins a turn with a roll of 5. Donald could hold and score 5 points but chooses to roll again. Donald rolls a 2 and could hold with a turn total of 7 points but decides to roll again. Donald rolls a 1 and must end his turn without scoring.
The next player, Alexis, rolls the sequence 4-5-3-5-6, after which she chooses to hold and adds her turn total of 23 points to her score.
The game continues, Donald taking the turn. In this turn, Donald rolls 3-2-4 and decides to hold. His total score so far is 9, and the turn passes to Alexis.
The player reaching a score of 100 or more total points wins.
Write a program in Go that simulates this two-player game. There are no human players, only computers. Each computer player plays with some fixed strategy. An example strategy is “hold after accumulating a score of at least 10 for each turn”. In this strategy, the player will keep on rolling until they accumulate a score of at least 10 in that round. If they roll a 1 before getting a turn total of 10, they score 0 points, and their turn passes to the next player.
Some example rolls for this strategy could be: - 3-4-2-4 (turn total is 13, which is greater than 10) and then the player holds and passes the turn to the next player. - 3-5-1 (turn total is 0, since a 1 is rolled) and then the player must pass the turn to the next player. - 6-4 (turn total is 10), hence player holds and passes the turn to the next player.
Similarly, another computer player may have a strategy to “hold after accumulating a score of at least 25 for each turn”. This player is “greedy” because they try to accumulate more points in a single round. This also means they may roll 1, and not get any points for that round. But, well, that’s their strategy.
The program should run a simulation where 10 such games are played between two players, where each player uses some fixed strategy. The program should output the number of wins and losses of each player.

Story 1

Player 1 uses a strategy of always holding after accumulating a score of at least 10, while Player 2 uses a strategy of always holding after reaching a sum of at least 15.
Here’s how a sample pig command will be executed and the sample output it should produce.
$ ./pig 10 15 Holding at 10 vs Holding at 15: wins: 3/10 (30.0%), losses: 7/10 (70.0%)
In this case, there are 10 games played between these two players. The output indicates that Player 1 wins 30% of these games (i.e. 3 out of 10 games) while Player 2 wins 70% (i.e. 7 out of 10) games. Thus, in this case, you could say that Player 2 has a better strategy than Player 1. i.e. Holding at 15 is better strategy than holding at 10.

Story 2

Player 1 has a fixed strategy of always holding after accumulating a score of at least 21 (21 is passed as an argument to the pig cli)
Player 2 changes their strategy for each set of games as follows:
  • First, Player 2 always uses a strategy of always holding after accumulating a score of at least 1.
  • Then, Player 2 always uses a strategy of always holding after accumulating a score of at least 2.
  • Then, Player 2 always uses a strategy of always holding after accumulating a score of at least 3.
  • Then, Player 2 always uses a strategy of always holding after accumulating a score of at least 4.
  • Then, Player 2 always uses a strategy of always holding after accumulating a score of at least 20.
  • Then, Player 2 always uses a strategy of always holding after accumulating a score of at least 22.
  • Then, Player 2 always uses a strategy of always holding after accumulating a score of at least 23.
  • Then, Player 2 always uses a strategy of always holding after accumulating a score of at least 100.
Notice the pattern. Also notice that Player 2 never uses “hold till at least 21” strategy. Why? Since, that’s the same strategy used by Player 1. There’s no point in simulating games against two players that use the same strategy.
Player 2 uses a total of 99 strategies. You should simulate 10 games for every Player 2’s strategy. Thus, there will be a total of 990 games played (99 strategies * 10 games per strategy).

A side note about what it really means to have “Hold until 1” strategy?

  • What it really means when a player uses a strategy of always holding after accumulating a score of at least 1? It basically means, the player passes the turn to the next player immediately after rolling the dice. How?
  • Imagine the player rolls a 1. In that case, their turn ends anyway. If instead, the player rolls a 2. Since 2 is greater than 1, the player accumulates 2 to their score and passes the turn to the next player. Thus, this strategy means the player will only get a single roll. After one roll, they will pass the turn to the next player.
  • Strategy of always holding after accumulating a score of at least 2 is the same as above. Roll a 1 and let go of the turn. Or, roll any other number (since it is greater than or equal to 2), and let go of your turn.
  • Strategy of always holding after accumulating a score of at least 3 is a bit different. You can either roll a 1 and pass the turn. Or if you roll a 2, you will continue to roll again (since 2 is less than 3). You’ll then pass on the turn to the next player after your next roll. If however, you roll a 5, then you’ll immediately pass on the turn to the next player (since 5 is greater than 3). Hope you get the idea.
The program should produce the output as below. The output is truncated here to save the space. Basically, there will be 99 lines in the output. Each line corresponds to a series of 10 games between two players. You should print the win rate and loss rate for these 10 games.
$ ./pig 21 1-100 Holding at 21 vs Holding at 1: wins: 8/10 (80.0%), losses: 2/10 (20.0%) Holding at 21 vs Holding at 2: wins: 8/10 (80.0%), losses: 2/10 (20.0%) Holding at 21 vs Holding at 3: wins: 8/10 (80.0%), losses: 2/10 (20.0%) Holding at 21 vs Holding at 4: wins: 8/10 (80.0%), losses: 2/10 (20.0%) Holding at 21 vs Holding at 5: wins: 8/10 (80.0%), losses: 2/10 (20.0%) ... ... Holding at 21 vs Holding at 20: wins: 5/10 (50.0%), losses: 5/10 (50.0%) Holding at 21 vs Holding at 22: wins: 6/10 (60.0%), losses: 4/10 (40.0%) ... ... Holding at 21 vs Holding at 100: wins: 10/10 (100.0%), losses: 0/10 (0.0%)

Story 3

This story is an extension of the previous story, where both players change their strategies. In story 2, Player 1 had a fixed strategy and Player 2 used different strategies. In this story, let’s allow both players to change their strategies.
Thus, there will be 100 strategies for Player 1 (from “hold until 1” to “hold until 100”) and 99 strategies for Player 2. Why not 100 strategies for Player 2? Player 1 and 2 cannot have the same strategy, i.e. there’s no point in playing a game where both players use “hold until 4” strategy (4 here, is just an example).
Each of these strategies will be played against each other in a match. Each such match will have 10 games. Thus, there will be 99,000 games (100 strategies * 99 strategies * 10 games per match) in total.
Now, instead of printing 9900 output lines (as per Story 2’s logic), let’s do something different. Let’s calculate the win rates for strategies and print that. How?
The output will contain only 100 lines. Each line will indicate wins and losses of Player 1’s single strategy played against Player 2’s all strategies (99 strategies for Player 2) Here’s a sample line from the output.
Result: Wins, losses staying at k = 36: 670/990 (67.7%), 320/990 (32.3%)
Here, Player 1 plays with “hold until 36” strategy (the k denotes 36) against Player 2’s all strategies (“hold until 1 through 35” and “hold until 37 through 100”) For each match, 10 games are played, hence, in all, there are 990 games played (10 games per match * 99 matches)
We print how many of these 990 games Player 1 wins (in this case, it’s 670) and Player 1 loses (in this case, it’s 320). Also, print the win(67.7%) and loss(32.3%) rate percentage.
Repeat this output for all Player 1’s strategies, resulting in an output like below.
$ ./pig 1-100 1-100 Result: Wins, losses staying at k = 1: 277/990 (28.0%), 713/990 (72.0%) Result: Wins, losses staying at k = 2: 237/990 (23.9%), 753/990 (76.1%) Result: Wins, losses staying at k = 3: 322/990 (32.5%), 668/990 (67.5%) ... ... Result: Wins, losses staying at k = 99: 225/990 (22.7%), 765/990 (77.3%) Result: Wins, losses staying at k = 100: 210/990 (21.2%), 780/990 (78.8%)
Feel free to make suitable assumptions if needed, and ensure to document them in README.md.

Credits

  • This problem statement is a slight variation of Pig Codewalk from Go’s official website. I have broken the problem statement into three stories to make it approachable. https://go.dev/doc/codewalk/functions/ (this link also contains an implementation of Pig in functional programming style, so if you get stuck, refer here).