Questions
Currently I have three loops that are almost identical, only differing in the index where they stop. Is there an easier way to do this?
Extract the stop-index as a variable, and then issue a single call to colored
.
Did my best to separate specific tasks to their own functions, but I feel there is still some overlap.
This still needs a lot of work. For example, in your __check_lines
you're both calculating a winnings sum and reformatting your results matrix.
At the beginning I wrote this to fit any NxN slot interface, but ended up inserting some hardcoded values for a 5x3 machine, like most are. Would like some suggestions on how to tackle this issue, especially for calculating the pre-determined lines.
Don't accept width and height. Your rawlines
(which I call win_database
and your reference calls win lines) should imply the dimensions.
The good
You've written some type hints (you could use more).
You've managed to avoid globals.
You have a properly-written main
function and __main__
guard.
You've used a terminal colour library in a seemingly sensible way.
Points to work on
dim
does not benefit from being a tuple; just accept two arguments.
Missing a bunch of hints, such as spin() -> None
.
rawlines
should be an immutable tuple of tuples, and should be a class ("static") variable rather than an instance variable.
The code is kind of a swamp of inappropriate mutation and state retention. self.result
shouldn't be present on Slot
at all. Either pass it around in parameters and return values, or (as I'll demonstrate) split out a second Spin
class. The simple path to immutability is NamedTuple
.
Don't quit
. Raise a domain-specific exception.
Don't for i in range(self.h)
; loop like a native - the safe choice here is a generator function.
Don't double-underscore. That's reserved for name mangling; use a single underscore for private-like members.
Reduce self.player.balance = self.player.balance +
by using +=
.
length
is not a length; it's some kind of divider string - we can think of a better variable name.
${self.player.balance:0.2f}
could instead leverage Python's built-in locale currency formatting support.
Since random behaviour is crucial for this application, you should either pass in an instance of Random
, or at least pass in an optional random seed for testability.
w
and h
should just be spelled in full. More importantly - accepting those parameters gives the caller the illusion that they can influence the width and height, but they can't! Those are hard-coded in rawlines
.
It would be a good idea to write a fallback for terminals that don't support colour; something like
if termcolor.termcolor._can_do_colour():
yield termcolor.colored(text=f' {symbol} ', color='green')
else:
yield f'[{symbol}]'
Suggested
Demonstrating some of the above with less mutation, a little bit more OOP, etc.: this behaves identically to your old code. It took a lot of effort to figure out how the old code worked, and I suspect that there's still a bunch of simplification to the win-condition algorithm that can be done.
"""
Simulation imitating the slot machine described in
https://chiphungry.com/mad-mouse-the-zappening/
"""
import dataclasses
import functools
import locale
import random
from enum import Enum
from typing import Iterator, Iterable, NamedTuple
import termcolor
class InsufficientFundsError(ValueError):
"""
Thrown if the user attempts to spin but doesn't have enough balance to submit the bet.
"""
@dataclasses.dataclass(slots=True)
class Player:
name: str
balance: float
def pay(self, bet: float) -> None:
new_balance = self.balance - bet
if new_balance < 0:
raise InsufficientFundsError()
self.balance = new_balance
def win(self, winnings: float) -> None:
self.balance += winnings
# A vertical index into the result matrix
VIndex = int
# A single "card" symbol, like Q
Symbol = str
# One row in the win condition database
IndexRow = tuple[VIndex, ...]
# One row in the result matrix
SymbolRow = tuple[Symbol, ...]
# One win-product row, with a coefficient for each WinClass entry
CoefRow = tuple[float, float, float, float]
# The matrix of symbols to display to the user
ResultMatrix = tuple[SymbolRow, ...]
class WinClass(Enum):
"""
All of the possible alternatives for win condition, in decreasing order of rarity.
"""
ALL = 0
ALL_BUT_1 = 1
ALL_BUT_2 = 2
NONE = 3
class WinCandidate(NamedTuple):
"""
One win-candidate row, with symbols from a spin mapped onto the win condition database.
These win candidates are not displayed to the user directly. They're used to calculate win class
(in turn calculating the addends for winnings money).
"""
symbols: SymbolRow
win_class: WinClass
@classmethod
def map(cls, symbols: Iterable[Symbol]) -> 'WinCandidate':
"""
From a mapped symbol row, create a WinCandidate instance.
"""
symbols = tuple(symbols)
# Business logic used to determine the win-class for this candidate.
# This is somewhat of a performance bottleneck.
all_len = len(set(symbols))
if all_len > len(symbols) - 2:
win_class = WinClass.NONE # Fast path for very few matches
elif all_len == 1:
win_class = WinClass.ALL
elif len(set(symbols[:-1])) == 1:
win_class = WinClass.ALL_BUT_1
elif len(set(symbols[:-2])) == 1:
win_class = WinClass.ALL_BUT_2
else:
win_class = WinClass.NONE
return cls(symbols=symbols, win_class=win_class)
@property
def front(self) -> Symbol:
return self.symbols[0]
def __str__(self) -> str:
return ''.join(self.symbols)
class Spin:
"""
A single spin of the slot machine.
"""
# Python class slots, not slot-machine slots
__slots__ = 'result', 'bet', 'candidates', 'winnings', 'win_database'
def __init__(
self,
win_coefs_by_symbol: dict[Symbol, CoefRow],
win_database: tuple[IndexRow, ...],
result: ResultMatrix,
bet: float,
) -> None:
self.win_database = win_database
self.result = result
self.bet = bet
self.candidates = tuple(self._get_candidates())
self.winnings = sum(self._get_winnings(win_coefs_by_symbol))
def _get_winnings(
self,
win_coefs_by_symbol: dict[Symbol, CoefRow],
) -> Iterator[float]:
"""
An iterator of winning money. Call sum() on this to get the total winnings for this spin.
"""
for candidate in self.candidates:
coef = win_coefs_by_symbol[candidate.front][candidate.win_class.value]
yield self.bet * coef
def _get_candidates(self) -> Iterator[WinCandidate]:
"""
An iterator of win candidate tuples, made by mapping the spin result symbols onto the win
condition database. In the demo configuration, the iterator is 25 elements long and yields
tuples of 5 symbols each.
"""
for win_indices in self.win_database:
yield WinCandidate.map([
self.result[y][x]
for x, y in enumerate(win_indices)
])
@property
def width(self) -> int:
return len(self.result[0])
def _format_row(self, y: int, result_row: SymbolRow) -> Iterator[str]:
for x, symbol in enumerate(result_row):
for candidate, win_condition in zip(self.candidates, self.win_database):
if y != win_condition[x]:
continue
win_class = candidate.win_class
if win_class != WinClass.NONE:
n_excluded = win_class.value
if x < len(result_row) - n_excluded:
if termcolor.termcolor._can_do_colour():
yield termcolor.colored(text=f' {symbol} ', color='green')
else:
yield f'[{symbol}]'
break
else:
yield f'{symbol:^3}'
def format_lines(self, player: Player) -> Iterator[str]:
sep = '|---'*self.width + '|'
yield sep
for y, result_row in enumerate(self.result):
middle = '|'.join(self._format_row(y=y, result_row=result_row))
yield f'|{middle}|'
yield sep
yield f'Balance: {locale.currency(player.balance)} | {self}'
def __str__(self) -> str:
return (
f'Bet: {locale.currency(self.bet)}'
f' | Win: {locale.currency(self.winnings)}'
)
class SlotMachine:
# Python class slots, not slot-machine slots
__slots__ = (
'win_coefs_by_symbol', 'win_database', 'width', 'height',
'symbols', 'total_winnings', 'rand',
)
def __init__(
self,
win_coefs_by_symbol: dict[Symbol, CoefRow],
win_database: tuple[IndexRow, ...],
rand: random.Random | None = None,
) -> None:
self.win_coefs_by_symbol = win_coefs_by_symbol
self.win_database = win_database
self.symbols = tuple(win_coefs_by_symbol.keys())
self.total_winnings = 0. # The only thing that mutates in this class.
self.width = len(win_database[0])
self.height = 1 + max(
idx
for row in win_database
for idx in row
)
if rand is None:
self.rand = random.Random()
else:
self.rand = rand
def _spin_rows(self) -> Iterator[SymbolRow]:
"""
An iterator of output matrix rows, randomly chosen from the display symbols.
"""
for _ in range(self.height):
yield tuple(self.rand.choices(
population=self.symbols,
k=self.width,
))
def spin(self, player: Player, bet: float) -> Spin:
# Will throw if insufficient balance
player.pay(bet)
spin = Spin(
win_coefs_by_symbol=self.win_coefs_by_symbol,
win_database=self.win_database,
result=tuple(self._spin_rows()),
bet=bet,
)
self.total_winnings += spin.winnings
player.win(spin.winnings)
return spin
def __str__(self) -> str:
return f'Total Winnings: {locale.currency(self.total_winnings)}'
def main(balance: float = 50.00, visible: bool = True, pause: bool = False) -> None:
# Set to the user's locale, for currency formatting
locale.setlocale(category=locale.LC_ALL, locale='')
player = Player(name='Ben', balance=balance)
win_database = (
(0, 0, 0, 0, 0),
(1, 1, 1, 1, 1),
(2, 2, 2, 2, 2),
(0, 1, 2, 1, 0),
(2, 1, 0, 1, 2),
(1, 2, 1, 0, 1),
(1, 2, 1, 2, 1),
(0, 1, 0, 1, 2),
(2, 1, 2, 1, 0),
(2, 1, 0, 1, 0),
(0, 1, 2, 1, 2),
(1, 0, 1, 2, 1),
(2, 1, 2, 1, 2),
(1, 0, 1, 0, 1),
(0, 1, 0, 1, 0),
(1, 1, 2, 1, 1),
(0, 0, 1, 0, 0),
(2, 2, 1, 2, 2),
(1, 1, 0, 1, 1),
(0, 0, 2, 0, 0),
(2, 2, 0, 2, 2),
(1, 1, 1, 2, 1),
(0, 0, 0, 1, 2),
(2, 2, 2, 1, 0),
(2, 1, 1, 1, 0),
)
win_coefs_by_symbol = {
'A': (3.0, 0.8, 0.30, 0),
'K': (2.5, 0.6, 0.24, 0),
'Q': (2.0, 0.5, 0.20, 0),
'9': (1.5, 0.4, 0.16, 0),
'8': (1.2, 0.3, 0.14, 0),
'7': (1.0, 0.2, 0.10, 0),
}
machine = SlotMachine(
win_coefs_by_symbol, win_database,
rand=random.Random(0),
)
while True:
try:
spin = machine.spin(player=player, bet=1.00)
except InsufficientFundsError:
if visible:
print('Insufficient funds')
break
output = '\n'.join(spin.format_lines(player=player))
if visible:
print(output)
print(machine)
if pause:
input('Press enter to continue.')
if visible:
print()
def profile() -> None:
import cProfile
cProfile.run('main(balance=10_000, visible=False)', sort='tottime')
if __name__ == '__main__':
try:
main()
# profile()
except KeyboardInterrupt:
pass
Output
For the purposes of Code Review formatting, this output is shown with the non-colourised fallbacks.
|---|---|---|---|---|
| 7 | 8 | Q | K | 9 |
| Q | 8 | K | Q | 9 |
| 7 | 9 | K | 8 | 9 |
|---|---|---|---|---|
Balance: $49.00 | Bet: $1.00 | Win: $0.00
Total Winnings: $0.00
|---|---|---|---|---|
| K | 7 | 7 | 8 | 7 |
| K | 8 | 7 | 8 | Q |
| A | Q | 9 | 7 | 7 |
|---|---|---|---|---|
Balance: $48.00 | Bet: $1.00 | Win: $0.00
Total Winnings: $0.00
|---|---|---|---|---|
| Q | 7 | K | 8 | 9 |
| A | 8 | Q | 8 | 8 |
| A | Q | 7 | K | K |
|---|---|---|---|---|
Balance: $47.00 | Bet: $1.00 | Win: $0.00
Total Winnings: $0.00
|---|---|---|---|---|
| 7 | K | 9 | K | 7 |
| 8 | Q | A | K | 9 |
| 7 | A | 9 | 8 | 9 |
|---|---|---|---|---|
Balance: $46.00 | Bet: $1.00 | Win: $0.00
Total Winnings: $0.00
|---|---|---|---|---|
| 8 | 9 | 7 | 9 | 9 |
| Q | 9 | Q | 9 | K |
| K | K | 9 | 9 | Q |
|---|---|---|---|---|
Balance: $45.00 | Bet: $1.00 | Win: $0.00
Total Winnings: $0.00
|---|---|---|---|---|
| A | 8 |[7]| 7 | 7 |
|[7]|[7]| 9 | Q | 8 |
| K | 8 |[7]| 7 | 9 |
|---|---|---|---|---|
Balance: $44.20 | Bet: $1.00 | Win: $0.20
Total Winnings: $0.20
|---|---|---|---|---|
| 7 | 9 | Q | 9 | 7 |
| 7 | 8 | A | 9 | Q |
| 9 | 7 | K | 8 | A |
|---|---|---|---|---|
Balance: $43.20 | Bet: $1.00 | Win: $0.00
Total Winnings: $0.20
|---|---|---|---|---|
| K | 8 | K | 8 | A |
| A | 8 | A | 9 | 7 |
| 9 | 8 | A | 9 | 9 |
|---|---|---|---|---|
Balance: $42.20 | Bet: $1.00 | Win: $0.00
Total Winnings: $0.20
|---|---|---|---|---|
| 9 | Q | Q | 7 | A |
| A | 7 | K | A | K |
| 8 | 7 | A | Q | A |
|---|---|---|---|---|
Balance: $41.20 | Bet: $1.00 | Win: $0.00
Total Winnings: $0.20
|---|---|---|---|---|
| K | K | 9 | Q | K |
| 9 | A | A | 7 | K |
| Q | 8 | 7 | 7 | K |
|---|---|---|---|---|
Balance: $40.20 | Bet: $1.00 | Win: $0.00
Total Winnings: $0.20
|---|---|---|---|---|
| 8 | 7 | A | 8 | 7 |
| Q | K | 9 | Q | K |
| Q | Q | 9 | 9 | K |
|---|---|---|---|---|
Balance: $39.20 | Bet: $1.00 | Win: $0.00
Total Winnings: $0.20
|---|---|---|---|---|
| Q | 7 | K | 9 | A |
| 8 | Q | A | K | K |
| 7 | Q | K | Q | 7 |
|---|---|---|---|---|
Balance: $38.20 | Bet: $1.00 | Win: $0.00
Total Winnings: $0.20
|---|---|---|---|---|
| 9 | 9 | 8 | Q | Q |
| 9 | A | K | Q | K |
| 9 | Q | 7 | 9 | Q |
|---|---|---|---|---|
Balance: $37.20 | Bet: $1.00 | Win: $0.00
Total Winnings: $0.20
|---|---|---|---|---|
| Q | 8 | Q | 9 | A |
| Q | K | A | 9 | Q |
| 9 | 8 | 7 | Q | K |
|---|---|---|---|---|
Balance: $36.20 | Bet: $1.00 | Win: $0.00
Total Winnings: $0.20
|---|---|---|---|---|
| Q | 8 | 7 | 8 | K |
| 7 | 9 | A | 8 | 7 |
| Q | 8 | K | K | 8 |
|---|---|---|---|---|
Balance: $35.20 | Bet: $1.00 | Win: $0.00
Total Winnings: $0.20
|---|---|---|---|---|
| A | 8 | 9 | A | A |
| 9 | 7 | K | 7 | A |
| 7 | Q | A | K | Q |
|---|---|---|---|---|
Balance: $34.20 | Bet: $1.00 | Win: $0.00
Total Winnings: $0.20
|---|---|---|---|---|
| 8 | 7 | A | 9 | 9 |
| Q | 7 | K | A | A |
| 8 | 9 | 7 | 7 | 7 |
|---|---|---|---|---|
Balance: $33.20 | Bet: $1.00 | Win: $0.00
Total Winnings: $0.20
|---|---|---|---|---|
| A | 8 | 8 | 9 | 8 |
| 7 | 9 | Q | 9 | K |
| 9 | A | A | Q | 8 |
|---|---|---|---|---|
Balance: $32.20 | Bet: $1.00 | Win: $0.00
Total Winnings: $0.20
|---|---|---|---|---|
| 8 | Q | 9 | A | A |
| 7 | K | Q | 9 | K |
| Q | K | A | K | 9 |
|---|---|---|---|---|
Balance: $31.20 | Bet: $1.00 | Win: $0.00
Total Winnings: $0.20
|---|---|---|---|---|
| A | Q | K | Q | A |
| 7 |[Q]| 7 | 7 | Q |
|[Q]| 8 |[Q]| K | 8 |
|---|---|---|---|---|
Balance: $30.60 | Bet: $1.00 | Win: $0.40
Total Winnings: $0.60
|---|---|---|---|---|
| 7 | 7 | 9 | Q | 7 |
| 9 |[A]|[A]| 8 | A |
|[A]| Q | 9 | Q | Q |
|---|---|---|---|---|
Balance: $29.90 | Bet: $1.00 | Win: $0.30
Total Winnings: $0.90
|---|---|---|---|---|
| 9 | 8 | Q | Q | 7 |
| K | 8 | Q | Q | A |
| 7 | 8 | 7 | Q | 8 |
|---|---|---|---|---|
Balance: $28.90 | Bet: $1.00 | Win: $0.00
Total Winnings: $0.90
|---|---|---|---|---|
| A | Q | K | A | 7 |
| K | A | K | Q | 9 |
| A | 7 | 7 | A | Q |
|---|---|---|---|---|
Balance: $27.90 | Bet: $1.00 | Win: $0.00
Total Winnings: $0.90
|---|---|---|---|---|
| 8 | 7 |[Q]| 7 | K |
|[Q]|[Q]| 8 | K | 9 |
| K | Q | 7 | 7 | 8 |
|---|---|---|---|---|
Balance: $27.10 | Bet: $1.00 | Win: $0.20
Total Winnings: $1.10
|---|---|---|---|---|
| A | A | K | 8 | 7 |
| 9 | 8 | 8 | A | A |
| 7 | A | K | A | A |
|---|---|---|---|---|
Balance: $26.10 | Bet: $1.00 | Win: $0.00
Total Winnings: $1.10
|---|---|---|---|---|
| 7 | K | A | A | 9 |
| 7 | 9 | 7 | 9 | 9 |
|[8]|[8]|[8]| 7 | 8 |
|---|---|---|---|---|
Balance: $25.38 | Bet: $1.00 | Win: $0.28
Total Winnings: $1.38
|---|---|---|---|---|
| 7 | K | 9 | 9 | 8 |
| Q | 8 | Q | 9 | 7 |
| 8 | 9 | A | K | 9 |
|---|---|---|---|---|
Balance: $24.38 | Bet: $1.00 | Win: $0.00
Total Winnings: $1.38
|---|---|---|---|---|
| K | A | 7 | 7 | K |
| Q | 8 | A | 9 | A |
| K | 8 | A | A | Q |
|---|---|---|---|---|
Balance: $23.38 | Bet: $1.00 | Win: $0.00
Total Winnings: $1.38
|---|---|---|---|---|
| Q | 7 | 8 | 8 | A |
| 7 | Q | 9 | Q | A |
| Q | A | A | 9 | 9 |
|---|---|---|---|---|
Balance: $22.38 | Bet: $1.00 | Win: $0.00
Total Winnings: $1.38
|---|---|---|---|---|
| A | 9 | Q | Q | 8 |
| Q | 7 | 9 | Q | 9 |
| Q | A | 8 | 9 | 8 |
|---|---|---|---|---|
Balance: $21.38 | Bet: $1.00 | Win: $0.00
Total Winnings: $1.38
|---|---|---|---|---|
| A | 7 |[8]| 7 | 7 |
|[8]|[8]| 9 |[8]| K |
|[8]| Q |[8]| Q |[8]|
|---|---|---|---|---|
Balance: $23.98 | Bet: $1.00 | Win: $3.60
Total Winnings: $4.98
|---|---|---|---|---|
| A | A | 7 | Q | 7 |
| 7 | 9 | 9 | K | A |
| 8 | 7 | 8 | 8 | Q |
|---|---|---|---|---|
Balance: $22.98 | Bet: $1.00 | Win: $0.00
Total Winnings: $4.98
|---|---|---|---|---|
| K | A | Q | 9 | 7 |
| 7 | Q | A | 8 | 8 |
| A | Q | 8 | A | 7 |
|---|---|---|---|---|
Balance: $21.98 | Bet: $1.00 | Win: $0.00
Total Winnings: $4.98
|---|---|---|---|---|
| 7 | 8 | A | A | Q |
| A | Q | A | 7 | 8 |
| A | 7 | K | K | 8 |
|---|---|---|---|---|
Balance: $20.98 | Bet: $1.00 | Win: $0.00
Total Winnings: $4.98
|---|---|---|---|---|
|[7]| A | A | Q | A |
| 9 |[7]| K | A | Q |
|[7]| A |[7]| Q | Q |
|---|---|---|---|---|
Balance: $20.38 | Bet: $1.00 | Win: $0.40
Total Winnings: $5.38
|---|---|---|---|---|
| K | 7 | 7 | 9 | K |
| 7 | A | 9 | A | 7 |
| 7 | 8 | K | K | 8 |
|---|---|---|---|---|
Balance: $19.38 | Bet: $1.00 | Win: $0.00
Total Winnings: $5.38
|---|---|---|---|---|
| K | Q |[A]| A | A |
|[A]|[A]| Q | 9 | 8 |
|[A]| Q | 9 | A | Q |
|---|---|---|---|---|
Balance: $19.28 | Bet: $1.00 | Win: $0.90
Total Winnings: $6.28
|---|---|---|---|---|
| Q | 7 | A | Q | Q |
| 8 | K | K | K | Q |
| 7 | 8 | K | 9 | 8 |
|---|---|---|---|---|
Balance: $18.28 | Bet: $1.00 | Win: $0.00
Total Winnings: $6.28
|---|---|---|---|---|
| 8 | Q | Q | 8 | Q |
| K | Q | 7 | A | Q |
| 9 | Q | K | A | 8 |
|---|---|---|---|---|
Balance: $17.28 | Bet: $1.00 | Win: $0.00
Total Winnings: $6.28
|---|---|---|---|---|
| 9 | A | K | 8 | 9 |
| 9 | A | 8 | Q | 8 |
| 8 | K | 8 | K | 7 |
|---|---|---|---|---|
Balance: $16.28 | Bet: $1.00 | Win: $0.00
Total Winnings: $6.28
|---|---|---|---|---|
| A | 7 | A | K | 9 |
| 9 | Q | A | K | K |
| 8 | 7 | 9 | A | 8 |
|---|---|---|---|---|
Balance: $15.28 | Bet: $1.00 | Win: $0.00
Total Winnings: $6.28
|---|---|---|---|---|
| K | Q |[9]| K | 7 |
| A | Q | K | 9 | 9 |
|[9]|[9]| Q |[9]| Q |
|---|---|---|---|---|
Balance: $14.68 | Bet: $1.00 | Win: $0.40
Total Winnings: $6.68
|---|---|---|---|---|
| 8 | 8 | K | Q | 9 |
| A | 8 | Q | 9 | A |
| 8 | Q | Q | Q | Q |
|---|---|---|---|---|
Balance: $13.68 | Bet: $1.00 | Win: $0.00
Total Winnings: $6.68
|---|---|---|---|---|
| 8 | K | 9 | A | K |
| 8 | A | 9 | A | Q |
| 7 | A | 9 | A | 7 |
|---|---|---|---|---|
Balance: $12.68 | Bet: $1.00 | Win: $0.00
Total Winnings: $6.68
|---|---|---|---|---|
|[8]|[8]|[8]| A | A |
| 9 | 7 | 7 |[8]| 8 |
| K | 8 | K | A | Q |
|---|---|---|---|---|
Balance: $12.12 | Bet: $1.00 | Win: $0.44
Total Winnings: $7.12
|---|---|---|---|---|
| K | K | Q | 7 | Q |
| Q | 8 | 9 | A | 7 |
| Q | 8 | Q | 8 | Q |
|---|---|---|---|---|
Balance: $11.12 | Bet: $1.00 | Win: $0.00
Total Winnings: $7.12
|---|---|---|---|---|
| K | K | 7 | 9 | A |
| Q | K | A | K | A |
| 9 | K | 9 | 9 | 8 |
|---|---|---|---|---|
Balance: $10.12 | Bet: $1.00 | Win: $0.00
Total Winnings: $7.12
|---|---|---|---|---|
| 9 | K | 7 | Q | Q |
| 9 | 9 | 7 | 7 | 7 |
| 7 | 9 | Q | 8 | K |
|---|---|---|---|---|
Balance: $9.12 | Bet: $1.00 | Win: $0.00
Total Winnings: $7.12
|---|---|---|---|---|
| K | A | A | A | 9 |
| A | 8 | 8 | 7 | Q |
| 7 | Q | Q | A | 7 |
|---|---|---|---|---|
Balance: $8.12 | Bet: $1.00 | Win: $0.00
Total Winnings: $7.12
|---|---|---|---|---|
| 8 | K | Q | K | A |
| A | Q | K | 9 | K |
| K | 8 | 8 | K | 7 |
|---|---|---|---|---|
Balance: $7.12 | Bet: $1.00 | Win: $0.00
Total Winnings: $7.12
|---|---|---|---|---|
| K | Q | 8 | A | 7 |
| A | Q | 8 | A | 8 |
| 7 | Q | A | A | A |
|---|---|---|---|---|
Balance: $6.12 | Bet: $1.00 | Win: $0.00
Total Winnings: $7.12
|---|---|---|---|---|
| 8 | Q | 7 | Q | A |
| Q | 8 | 8 | A | K |
| Q | A | 7 | 7 | K |
|---|---|---|---|---|
Balance: $5.12 | Bet: $1.00 | Win: $0.00
Total Winnings: $7.12
|---|---|---|---|---|
| Q | 9 | K | 9 | K |
| K | Q | 9 | 9 | K |
| K | A | 8 | A | 8 |
|---|---|---|---|---|
Balance: $4.12 | Bet: $1.00 | Win: $0.00
Total Winnings: $7.12
|---|---|---|---|---|
| K | K | 8 | 9 | 8 |
| 9 | 7 | A | 9 | A |
| 8 | Q | 8 | 8 | 9 |
|---|---|---|---|---|
Balance: $3.12 | Bet: $1.00 | Win: $0.00
Total Winnings: $7.12
|---|---|---|---|---|
| K | 8 | K | 9 | 8 |
|[K]|[9]|[K]| K | 8 |
|[9]|[K]|[9]| A | A |
|---|---|---|---|---|
Balance: $2.92 | Bet: $1.00 | Win: $0.80
Total Winnings: $7.92
|---|---|---|---|---|
| Q | 7 | 9 | A |[8]|
|[8]|[8]| 9 |[8]| K |
|[8]|[8]|[8]| Q | 9 |
|---|---|---|---|---|
Balance: $5.06 | Bet: $1.00 | Win: $3.14
Total Winnings: $11.06
|---|---|---|---|---|
| 9 | 7 | A | 7 | 9 |
| Q | Q | A | K | 9 |
| 9 | Q | 7 | Q | K |
|---|---|---|---|---|
Balance: $4.06 | Bet: $1.00 | Win: $0.00
Total Winnings: $11.06
|---|---|---|---|---|
| 7 | K | 9 | 8 | 8 |
| 8 | Q | A | A | 8 |
| 7 | Q | Q | 8 | 9 |
|---|---|---|---|---|
Balance: $3.06 | Bet: $1.00 | Win: $0.00
Total Winnings: $11.06
|---|---|---|---|---|
| K | 8 | K | Q | 9 |
| 8 |[A]|[A]| 9 | A |
|[A]| K | 9 | A | A |
|---|---|---|---|---|
Balance: $2.36 | Bet: $1.00 | Win: $0.30
Total Winnings: $11.36
|---|---|---|---|---|
| 7 | Q | 7 | K | Q |
| Q | K | K | 7 | 8 |
| Q | Q | K | 9 | 8 |
|---|---|---|---|---|
Balance: $1.36 | Bet: $1.00 | Win: $0.00
Total Winnings: $11.36
|---|---|---|---|---|
| 8 | Q | A | 7 | Q |
| Q | A | A | 7 | 9 |
| 8 | A | K | 7 | 8 |
|---|---|---|---|---|
Balance: $0.36 | Bet: $1.00 | Win: $0.00
Total Winnings: $11.36
Insufficient funds
Profiling
This profile is after fixing up the low-hanging fruit on WinCandidate.win_class
. There are other optimisations possible, especially if this is intended for headless simulation.
5538495 function calls (5464053 primitive calls) in 2.292 seconds
Ordered by: internal time
ncalls tottime percall cumtime percall filename:lineno(function)
223326 0.451 0.000 0.571 0.000 288690.py:150(_format_row)
310175 0.339 0.000 0.481 0.000 288690.py:71(map)
322582 0.168 0.000 0.784 0.000 288690.py:134(_get_candidates)
74442 0.150 0.000 0.150 0.000 {built-in method _locale.localeconv}
310175 0.135 0.000 0.135 0.000 288690.py:141(<listcomp>)
322582 0.131 0.000 0.252 0.000 288690.py:123(_get_winnings)
37221 0.080 0.000 0.303 0.000 locale.py:265(currency)
339265 0.076 0.000 0.101 0.000 types.py:176(__get__)
310175 0.057 0.000 0.092 0.000 <string>:1(<lambda>)
972468 0.056 0.000 0.056 0.000 {built-in method builtins.len}
86849/12407 0.053 0.000 0.984 0.000 {method 'join' of 'str' objects}
37221 0.050 0.000 0.076 0.000 random.py:519(<listcomp>)
12407 0.046 0.000 1.119 0.000 288690.py:110(__init__)