Mini Cactpot Algorithm Part 2
This is the second part of the mini cactpot algorithm. This post is about calculating potential values of each line on the ticket and recommend the highest line of value to the user. If you want to see how to set up the mini cactpot ticket, use this link: Part 1.
Class Calculate
Similar to class Game
, class Calculate
will also take one argument, the dictionary of ticket positions. I’m going to initialize the class.
class Calculate:
def __init__(self, ticket):
self.positions = ticket
Since I want to calculate each three number lines, I use dictionary to map each line to its three positions on the ticket.
def lines(self):
ticket_lines = {
1: [self.ticket['g'], self.ticket['h'], self.ticket['i']],
2: [self.ticket['d'], self.ticket['e'], self.ticket['f']],
3: [self.ticket['a'], self.ticket['b'], self.ticket['c']],
4: [self.ticket['a'], self.ticket['e'], self.ticket['i']],
5: [self.ticket['a'], self.ticket['d'], self.ticket['g']],
6: [self.ticket['b'], self.ticket['e'], self.ticket['h']],
7: [self.ticket['c'], self.ticket['f'], self.ticket['i']],
8: [self.ticket['c'], self.ticket['e'], self.ticket['g']]
}
return ticket_lines
My next method, combinations
, is actually a Python built-in. The reason why I wrote out the code was because my free Trinket.io did not support itertools.
@classmethod
def combinations(cls, iterable, r):
pool = tuple(iterable)
n = len(pool)
if r > n:
return
indices = list(range(r))
yield tuple(pool[i] for i in indices)
while True:
for i in reversed(range(r)):
if indices[i] != i + n - r:
break
else:
return
indices[i] += 1
for j in range(i+1, r):
indices[j] = indices[j-1] + 1
yield tuple(pool[i] for i in indices)
The method takes an iterable and return tuples of combination for those elements at specific length. You can read more about it here: itertools.combinations. Suppose if you have a list of 4 items and you want to get all 2 item combinations, you can use itertools.combinations(list, length)
for that.
import itertools
a = [1, 2, 3, 4]
b = list(itertools.combinations(a, 2))
>>> [(1, 2), (1, 3), (1, 4), (2, 3), (2, 4), (3, 4)]
With this method, I can calculate possible combinations for the numbers the user has not picked. So if the user picked 1, 2, 3, 4 then 5, 6, 7, 8, 9 are still available. Consequently, I just need to find out which positions on the ticket that is still hidden and replace those with the available numbers. I received help from Francisco Couzo on StackOverflow on the method to replace hidden values with combinations. Here’s the link for the question I posted: question.
def lists_combinations(self, list_1, list_2):
indices = [i for i, x in enumerate(list_1) if isinstance(x, str)]
for combo in self.combinations(list_2, len(indices)):
for index, char in zip(indices, combo):
list_1[index] = char
yield tuple(list_1)
His function finds the indices of all the string in the first list and replace them with combinations from second list. I modified the function to fit with the class. In my case, list_1
is the value of each line for the dictionary I created earlier. While list_2
is the available numbers.
Calculating Payout
The actual calculation happens in the method lines_combinations
. I used another dictionary to map each line to its possible combinations. It uses the method earlier to do that. num_lines
and num_list
are simply the same arguments for list_combinations
.
def lines_combinations(self, num_lines, num_list):
lines_combo = {}
for line in num_lines:
lines_combo[line] = list(self.lists_combinations(num_lines[line], num_list))
return lines_combo
With the combinations for each line calculated, I can go on crunching out the potential payout of each line. The payout varies depend on the sum for each line. By summing up each combinations returned from lines_combination
and multiply them with the payout, I can get all potential payouts. Finally, I can find the average potential payout by using floor division of the total sum with the length of the list.
@classmethod
def lines_payout(cls, combinations):
payout = {6: 10000, 7: 36, 8: 720, 9: 360, 10: 80, 11: 252, 12: 108,
13: 72, 14: 54, 15: 180, 16: 72, 17: 180, 18: 119,
19: 36, 20: 306, 21: 1080, 22: 144, 23: 1800, 24: 3600}
sum_payout = {}
for line in combinations:
sum_payout[line] = [sum(combo) for combo in combinations[line]]
for line in sum_payout:
sum_payout[line] = sum([payout[value] for value in sum_payout[line]]) // len(sum_payout[line])
return sum_payout
However, the user does not need to know what happens in the code. Therefore I used another method to print out a message notifying the user which line has the best possible payout. There are cases where multiple lines can have the best payout. recommendation
takes the dictionary with each line average payout and find the maximum value. It returns a string containing the line(s) recommended and the potential payout value(s).
@classmethod
def recommendation(cls, user_payout):
print('---------------')
highest = max(user_payout.values())
recommend = [key for key, value in user_payout.items() if value == highest]
string = ''
for key in recommend:
string += Color.BOLD + '> Line {} has the highest payout of {}!\n'.format(key, user_payout[key]) + Color.END
return string
All that’s left to do now is to write a function to set up the game and calculate the potential payout. That should be a simple matter so I’m going to end the post here.