01-29-2021, 01:15 PM
Dave Britten's recent post regarding RPN models that could self-score a Yahtzee game prompted me to look up the game rules that I hadn't looked at since the early '70s. As I am inclined of late to see a list-based approach to many problems, this seemed like an interesting exercise to try. Several of the ListExt commands are a nice fit for the scenarios presented for scoring in the game.
First, some ground rules for my approach:
Aces - Sixes
The "upper section" scoring simply sums the values of a chosen die value (1 through 6). LCNT (List Count) is a good fit for this one:
Sorted Group Counts
A couple of the "lower section" scoring subroutines make use of a shared intermediate step. I think of this as "sorted group counts", and have named the subroutine that computes it SGC accordingly. SGC returns a list of counts of matching dice in the supplied input, sorted in ascending order.
Example:
If the input list is { 1 6 4 1 4 }, SGC returns { 1 2 2 } (one 6, two 1s, two 4s). Note that the results are sorted by quantity, not die value.
The line comments below show the intermediate results with the above input for better understanding.
Three or Four of a Kind
This subroutine checks the size of the largest group of repeated die. If 3 or 4 is <= the largest count, then the sum of all dice is returned.
Full House
This one is fairly brute-force. It simply finds the sorted group count, and checks to see if it exactly matches 2 of one value and 3 of another.
Small or Large Straight
The approach used here is to look at the differences of the sorted dice list to determine if a straight of the specified size exists. The ListExt command LDLTA gives the same result as the built-in \GDLIST, but is slightly faster and much easier to type into a program. :-)
Yahtzee (5 of a Kind)
A very simple scenario to check.
Chance (any combination)
Even simpler than Yahtzee!
These are just the methods that came to mind as I looked at the rules. I'm sure there's other approaches (list-based and otherwise), and thoughts and alternate methods are always appreciated.
First, some ground rules for my approach:
- A group of rolled dice is represented as a list of approximate numbers (a quantity of 5 in this case).
- In keeping with the game definition, each scoring subroutine will return either the defined score for that category, or 0 if the dice list doesn't match at all.
- The source code below needs fraction marks to work properly. I've omitted them from the comments simply to make them easier to read.
- These routines only consider primary scoring. Secondary issues like conditional bonuses would need to be considered in the context of a complete game.
- Each instance of the SORT command below can be replaced by Werner's excellent LSORT command. If you don't have it, you should!
Aces - Sixes
The "upper section" scoring simply sums the values of a chosen die value (1 through 6). LCNT (List Count) is a good fit for this one:
Code:
\<<
@ scores aces, twos, threes, fours, fives, sixes
@ SL2: dice list
@ SL1: denomination (1..6)
SWAP OVER @ denomination, dice list, denomination
LCNT * @ result = count * denomination
\>>
Sorted Group Counts
A couple of the "lower section" scoring subroutines make use of a shared intermediate step. I think of this as "sorted group counts", and have named the subroutine that computes it SGC accordingly. SGC returns a list of counts of matching dice in the supplied input, sorted in ascending order.
Example:
If the input list is { 1 6 4 1 4 }, SGC returns { 1 2 2 } (one 6, two 1s, two 4s). Note that the results are sorted by quantity, not die value.
The line comments below show the intermediate results with the above input for better understanding.
Code:
SGC
\<<
@ returns a sorted list of die value group counts
@ SL1: dice list
@ initial input: { 1 6 4 1 4 }
SORT @ { 1 1 4 4 6 }
LRPCT @ { { 1 4 6 } { 2 2 1 } }
LPOPR @ SL2: { { 1 4 6 } } SL1: { 2 2 1 }
NIP @ { 2 2 1 }
SORT @ { 1 2 2 }
\>>
Three or Four of a Kind
This subroutine checks the size of the largest group of repeated die. If 3 or 4 is <= the largest count, then the sum of all dice is returned.
Code:
\<<
@ scores three or four of a kind
@ SL2: dice list
@ SL1: 3 or 4
SWAP DUP LSUM UNROT @ Determines possible score, leaves on stack above input
SGC @ Sorted Group Count
LPOPR NIP @ Highest group count from the list
\<= * @ multiply the computed score by 1 or 0 for the result
\>>
Full House
This one is fairly brute-force. It simply finds the sorted group count, and checks to see if it exactly matches 2 of one value and 3 of another.
Code:
\<<
@ scores Full House
@ SL1: dice list
25. SWAP @ possible score placed above input list
SGC @ Sorted Group Count
{ 2. 3. } SAME * @ score given only if exactly 2 and 3 of a kind
\>>
Small or Large Straight
The approach used here is to look at the differences of the sorted dice list to determine if a straight of the specified size exists. The ListExt command LDLTA gives the same result as the built-in \GDLIST, but is slightly faster and much easier to type into a program. :-)
Code:
\<<
@ scores Small or Large Straight
@ SL2: dice list
@ SL1: 3. (small) or 4. (large)
10. OVER * UNROT @ possible score (30 or 40) placed above input list
SWAP SORT LDLTA SORT @ sort the dice list and determine the sorted differences
SWAP LTAKE 1. + @ keep the first 3 or 4 differences, then add a 1 to the list
LEQ * @ if all of the list values are 1, return the score
\>>
Yahtzee (5 of a Kind)
A very simple scenario to check.
Code:
\<<
@ scores Yahtzee (5 of a kind)
@ SL1: dice list
50. SWAP @ place possible score above input list
LEQ * @ if all values are the same, return the score
\>>
Chance (any combination)
Even simpler than Yahtzee!
Code:
\<<
@ scores Chance (simple sum of dice)
@ SL1: dice list
LSUM @ that's it!
\>>
These are just the methods that came to mind as I looked at the rules. I'm sure there's other approaches (list-based and otherwise), and thoughts and alternate methods are always appreciated.