Functional Programming with Python — Building a Simple Slot Machine
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# Copyright 2021 Mark-James McDougall and licensed under the MIT License. | |
# This is an example of a single reel slot machine written with a functional | |
# programming paradigm using python. | |
# Once started, the machine will keep running until a jackpot condition is | |
# hit (three matching numbers), and it will also keep track of the number | |
# of times two matching numbers come up. | |
# Example output: | |
# Jackpot! | |
# Number of spins to hit the jackpot: 562 | |
# Number of two of a kinds hit: 82 | |
import numpy as np | |
from enum import Enum | |
# Enum in place of union type to keep track of the spin state. | |
class SpinState(Enum): | |
START = 0 | |
JACKPOT = 1 | |
TWO_OF_A_KIND = 2 | |
LOSS = 3 | |
# Generates a spin for a single slot machine line. | |
# This is technically an impure function because random numbers are | |
# being generated within the function and this results in a different | |
# result given the same parameters. | |
def spin_wheel(symbols_amount: int) -> list[int]: | |
spin = (list(np.random.randint(low=1, high=symbols_amount+1, size=3))) | |
return spin | |
# Notice that the functions below are pure with no side effects. | |
# We're also making use of optional types for type safety. | |
def win_condition_check(spin: list[int]) -> SpinState: | |
# This is an if expression and not an if statement because all | |
# paths evaluate to a value (SpinState). | |
if len(set(spin)) < 2: | |
return SpinState.JACKPOT | |
elif len(spin) != len(set(spin)): | |
return SpinState.TWO_OF_A_KIND | |
else: | |
return SpinState.LOSS | |
# The game_simulation runs until a jackpot condition is reached. | |
# This is a recursive method which uses accumulators to manage state in a functional way | |
# as opposed to creating side effects by using class attributes. | |
def game_simulation(spin_result: SpinState, jackpot_count: int, two_of_a_kind_count: int) -> (SpinState, int, int): | |
# A typical slot machine might display three spinning reels | |
# with about 20 symbols. For simplicity's sake, lets model a single reel. | |
wheel_spin = spin_wheel(20) | |
spin_result = win_condition_check(wheel_spin) | |
# Another if expression which returns a triple once the jackpot is hit, otherwise | |
# make a recursive function call and increment our accumulators. | |
if spin_result == SpinState.JACKPOT: | |
return spin_result, jackpot_count, two_of_a_kind_count | |
elif spin_result == SpinState.TWO_OF_A_KIND: | |
return game_simulation(spin_result, jackpot_count + 1, two_of_a_kind_count + 1) | |
elif spin_result == SpinState.START: | |
return game_simulation(spin_result, jackpot_count, two_of_a_kind_count) | |
else: | |
new_spin = SpinState.LOSS | |
return game_simulation(new_spin, jackpot_count + 1, two_of_a_kind_count) | |
# Entrypoint | |
if __name__ == '__main__': | |
# Pass the initial states to the game_simulation. | |
game_round = game_simulation(SpinState.START, 0, 0) | |
print(f"Jackpot!\nNumber of spins to hit the jackpot: {game_round[1]}\nNumber of two of a kinds hit: {game_round[2]}") |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment