0
\$\begingroup\$

Hello i build project Slot Machine this project was inspired by Tech with Tim here's his code from this project how he implement that --> https://github.com/techwithtim/Python-Slot-Machine/blob/main/main.py .I changed the code of the project on my way but something is same but most of the things i tried to change to check if i understand the project well.I am looking for maybe another way to implement the structure of code and tell me if i've done something wrong , what i should do better or to add something more.

import random
MAX_VALUE = 100
MIN_VALUE = 1
ROWS = 3
COLUMNS = 3

symbols_count = {
    "A":2,
    "B":4,
    "C":5,
    "E":3,
    "F":2
}

symbols_values = {
    "A":2,
    "B":4,
    "C":5,
    "E":3,
    "F":2
}
def check_winnings(columns,bet_lines,bet,balance):
    first_column = []
    second_column = []
    third_columns = []
    for element in columns:
        if len(first_column) < 3:
            first_column.append(element)
        elif len(second_column) < 3:
            second_column.append(element)
        elif len(third_columns) < 3:
            third_columns.append(element)
    check_duplicates_first = set(first_column) # we make sets because if there's three characters same it will return only one but remove the two duplicates
    check_duplicates_second = set(second_column)
    check_duplicates_third = set(third_columns)

    if len(check_duplicates_first) == 1:
        print("Winning line is --> line one --<")
        balance += bet_lines * bet
        print(f"You got it this time ,now you have {balance}$")
        playagain(balance)
    elif len(check_duplicates_second) == 1:
        print("Winning line is --> line two <--")
        balance += bet_lines * bet
        print(f"You got it this time ,now you have {balance}$")
        playagain(balance)
    elif len(check_duplicates_third) == 1:
        print("Winning line is --> line three --<")
        balance += bet_lines * bet
        print(f"You got it this time ,now you have {balance}$")
        playagain(balance)
    elif len(check_duplicates_first) > 1 or len(check_duplicates_second) > 1 or len(check_duplicates_third):
        get_sum = bet_lines * bet
        balance = balance - get_sum
        print(f"You didn't get it try again ,now you have {balance}$")
        playagain(balance)




def spin_slot_machine(symbols):
    all_symbols = []
    columns = []
    length_of_spin = 9
    for symbol,symbol_count in symbols.items():
        for i in range(symbol_count):
            all_symbols.append(symbol)
    for i in range(length_of_spin):
        get_random = random.choice(all_symbols)
        columns.append(get_random)
    return columns 
def print_slot_machine(columns):
    first_row = ' | '.join(columns[0:3])
    second_row = ' | '.join(columns[3:6])
    third_row = ' | '.join(columns[6:9])
    print(first_row)
    print(second_row)
    print(third_row)






def deposit():
    while True:
        deposit_money = input("How much money would you like to deposit?: $")
        if deposit_money.isdigit():
            deposit_money = int(deposit_money)
            if deposit_money > 0:
                break
            else:
                print("You should deposit more than 0$")
        print("Enter a digit")
    return deposit_money 
def bet_on_lines():
    while True:
        lines = input("On how many lines would you like to bet(1-3)?: ")
        if lines.isdigit():
            lines = int(lines)
            if lines >= 1 and lines <= 3:
                break
            else:
                print("Number of lines should be between 1-3")
        print("Enter a number of lines")
    return lines


def get_bet():
    while True:
        bet = input("How much money would you like to bet on one line(1$-100$): ")
        if bet.isdigit():
            bet = int(bet)
            if bet <= MAX_VALUE and bet >= MIN_VALUE:
                break
            else:
                print("Money should be between 1-100$")
        else:
            print("Enter a digit")
    return bet 


def spin():
    balance = deposit()
    lines_number = bet_on_lines()
    while True:
        bet_money = get_bet()
        total_bet = bet_money * lines_number 
        if total_bet > balance:
            print(f"Your balance is {balance}$.Balance shoudn't be less than betting money , bet less!")
        else:
            break
    print(f"You are betting {total_bet}$ on {lines_number} lines.")
    slot_machine = spin_slot_machine(symbols_count)
    print_slot_machine(slot_machine)
    check_winnings(slot_machine,lines_number,bet_money,balance)
def playagain(balance):
    while True:
        answer = input("Do you want to play again(Press Enter or q to quit): ")
        if answer != "" or balance == 0:
            print(f"You left with {balance}$, Good Bye!")
            break
        else:
            if int(balance) > 0:
                lines_number = bet_on_lines()
                bet_money = get_bet()
                slot_machine = spin_slot_machine(symbols_count)
                print_slot_machine(slot_machine)
                check_winnings(slot_machine,lines_number,bet_money,balance)
                break





spin()
\$\endgroup\$

1 Answer 1

2
\$\begingroup\$

PEP-8

First off, your whitespace is all over the place. PEP-8 recommendations are a set of rules to standardise the layout and appearance of Python code to make it easier for anyone who comes along to read and use it. It recommends:

Surround top-level function and class definitions with two blank lines.

Amongst other things. A good linter such as flake8 or pylint will highlight these issues for you automatically.

playagain violates the "functions should be snake-case" principle.

If-else

In your check_winnings function, you have your if-block which checks each row for whether each row's symbols are all the same and then whether they're not. The else block will handle that for you. If something doesn't match any of the preceding ifs, the else will run, which is what you want here.

    #Reformatted for clarity and to highlight typo
    elif (len(check_duplicates_first) > 1 or
          len(check_duplicates_second) > 1 or 
          len(check_duplicates_third)):

simply becomes:

    else:

You have the inverse problem in playagain where you have:

        if answer != "" or balance == 0:
            print(f"You left with {balance}$, Good Bye!")
            break
        else:
            if int(balance) > 0:

Which could easily be:

elif int(balance) > 0:

However, following a break you needn't have the else at all.

Sampling and using the right functions

Another bug comes in with the fact that you are doing several independent choices for your wheels:

    for i in range(length_of_spin):
        get_random = random.choice(all_symbols)
        columns.append(get_random)

This does not obey symbol_counts (as done with replacement) and in principle, it would be possible for you to roll ['A']*9

First off, we could clean it up using a list comprehension (as in several other places)

    # If you don't use a loop argument, it is customary to name it _
    columns = [random.choice(all_symbols) for _ in range(length_of_spin)

second, we could use random.choices (see help(random))

choices(population, weights=None, *, cum_weights=None, k=1) method of Random instance
    Return a k sized list of population elements chosen with replacement.
    columns = random.choices(all_symbols, k=length_of_spin)

However, noting this is with replacement, we should use random.sample, which does what we want!

    columns = random.sample(all_symbols, k=length_of_spin)

It even lets us avoid having to construct a list of all symbols:

    columns = random.sample(list(symbols.keys()), 
                            counts=list(symbols.values()),
                            k=length_of_spin)

Learning the tools can save you a lot of effort and time.

Simplify

Python has tools for slicing and dealing with lists, this means that we can avoid a lot of busywork

    first_column = []
    second_column = []
    third_columns = []
    for element in columns:
        if len(first_column) < 3:
            first_column.append(element)
        elif len(second_column) < 3:
            second_column.append(element)
        elif len(third_columns) < 3:
            third_columns.append(element)
    # we make sets because if there's three characters same
    # it will return only one but remove the two duplicates
    check_duplicates_first = set(first_column)
    check_duplicates_second = set(second_column)
    check_duplicates_third = set(third_columns)

Constructs 3 temporary lists (in a somewhat complicated manner) and then builds a set from those lists. We can skip half of this work:

    first_column = columns[0:3]
    second_column = columns[3:6]
    third_column = columns[6:9]

In fact, since its so simple, we can just directly do:

    check_duplicates_first = set(columns[0:3])
    check_duplicates_second = set(columns[3:6])
    check_duplicates_third = set(columns[6:9])

DRY

Don't repeat yourself (DRY) is the principle that good code should be written once. Take:

    if len(check_duplicates_first) == 1:
        print("Winning line is --> line one --<")
        balance += bet_lines * bet
        print(f"You got it this time ,now you have {balance}$")
        playagain(balance)
    elif len(check_duplicates_second) == 1:
        print("Winning line is --> line two <--")
        balance += bet_lines * bet
        print(f"You got it this time ,now you have {balance}$")
        playagain(balance)
    elif len(check_duplicates_third) == 1:
        print("Winning line is --> line three --<")
        balance += bet_lines * bet
        print(f"You got it this time ,now you have {balance}$")
        playagain(balance)
    elif len(check_duplicates_first) > 1 or len(check_duplicates_second) > 1 or len(check_duplicates_third):
        get_sum = bet_lines * bet
        balance = balance - get_sum
        print(f"You didn't get it try again ,now you have {balance}$")
        playagain(balance)

For each of the winning statements, the only line which changes is the winning line, and even the lose statement computes the total spent and calls playagain (N.B. Really, we don't actually want this function calling playagain, that results in nested recursion, the top level should call it, but that's another issue). We could instead (and still not the most concise, but simpler):

    winner = None
    if len(check_duplicates_first) == 1:
        winner = "one"
    elif len(check_duplicates_second) == 1:
        winner = "two"
    elif len(check_duplicates_third) == 1:
        winner = "three"

    get_sum = bet_lines * bet
    if winner:
        balance += get_sum
        print(f"Winning line is --> line {winner} --<")
        print(f"You got it this time, now you have {balance}$")
    else:
        balance -= get_sum
        print(f"You didn't get it try again, now you have {balance}$")

    playagain(balance)

Likewise, you could define a single function to handle user input to replace deposit, bet_on_lines and get_bet all of which request an integer in a given range from a user, with a certain prompt.

Something like:

def get_int(prompt, min_val=None, max_val=None):
    while True:
        val = input(prompt)
        if val.isdigit():
            val = int(val)
            if min_val <= val <= max_val:
                break
        # It'll show a bit oddly when either is not entered, but for now it'll do
        print(f"Val must be between {min_val}-{max_val}")
    return val

Similarly you reduplicate a lot of effort converting your linear list of columns into the ROWSxCOLS array which you could easily do once and make your functions work with lists of lists or arrays.

Unused variables

ROWS, COLUMNS and symbols_values all seem to be unused, but could be useful. You could use ROWS and COLUMNS to compute the slice lengths, number of slices (using lists of sets rather than separate variables).

symbols_values was presumably originally intended to be a multiplier on the reward received.

Summary

Putting all of the above together, you might end up with something like (adding docstring and main guard)

"""
Play a slot machine game
"""

import random

MAX_VALUE = 100
MIN_VALUE = 1
ROWS = 3
COLUMNS = 3

# It should be noted that using these counts it is impossible for
# A or F to win, and others are very unlikely
# Probably want to increase the count a little
SYMBOLS_COUNT = {
    "A": 2,
    "B": 4,
    "C": 5,
    "E": 3,
    "F": 2
}

SYMBOLS_VALUES = {
    "A": 2,
    "B": 4,
    "C": 5,
    "E": 3,
    "F": 2
}


def check_winnings(columns, bet_lines, bet, balance):
    """ Compute winnings based on columns """
    # Can do this because we pass columns around as a list of lists
    sets = [set(x) for x in columns[1:bet_lines]]

    # Allow multiple winners
    winners = [(i, symb[0]) for i, symb in enumerate(sets) if len(symb) == 1]

    winnings = 0
    if winners:  # Check against empty list
        for line, symb in winners:
            print("Line {line} won with {symb}")
            # Use symbols values as multiplier on returns
            # Diminishing returns for more lines
            winnings += SYMBOLS_VALUES[symb] * bet
        print(f"You won ${winnings}.")
    else:
        print("You didn't get any winners.")

    return winnings


def spin_slot_machine(symbols, rows=ROWS, cols=COLUMNS):
    """ Return set of wheels from slot machine """
    spin = random.sample(list(symbols.keys()),
                         counts=list(symbols.values()),
                         k=rows*cols)
    wheels = [spin[rows*i:rows*i+cols] for i in range(rows)]
    return wheels


def print_slot_machine(columns):
    """ Display slot machine to user """
    print("\n".join(" | ".join(col) for col in columns))


def get_int(prompt, min_val=None, max_val=None):
    """ Get an integer from a user within given bounds """
    while True:
        val = input(f"{prompt} ({min_val}-{max_val})? ")
        if val.isdigit():
            val = int(val)
            if min_val <= val <= max_val:
                break
        print(f"Invalid option {val}: must be an integer between {min_val}-{max_val}")
    return val


def get_bet(balance, cols):
    """ Get the user's bet """
    while True:
        lines_number = get_int("On how many lines would you like to bet, total bet is lines*bet",
                               1, cols)
        bet_money = get_int("How much money would you like to bet per line",
                            MIN_VALUE, MAX_VALUE)
        total_bet = bet_money * lines_number

        if total_bet <= balance:
            return bet_money, lines_number

        print(f"Your balance is {balance}$. You cannot afford this bet, bet less!")


def play_round(balance, rows=ROWS, cols=COLUMNS):
    """ Play a single round of slots """
    bet_money, lines_number = get_bet(balance, cols)

    print(f"You are betting {bet_money}$ on {lines_number} lines.")

    balance -= lines_number * bet_money

    slot_machine = spin_slot_machine(SYMBOLS_COUNT, rows, cols)
    print_slot_machine(slot_machine)

    balance += check_winnings(slot_machine, lines_number, bet_money, balance)

    return balance


def play_game(rows=ROWS, cols=COLUMNS):
    """ Play multiple rounds until bank exhausted or user exits """
    balance = get_int("How much starting money do you wish to have", 10, 1_000_000)
    play = True
    while play:
        balance = play_round(balance, rows, cols)
        print(f"Balance now sits at {balance}$.")
        if balance <= 0:
            print(f"You left with {balance}$, Good Bye!")
            break

        while True:
            answer = input(
                "Do you want to play again (Press 'y' to play again or 'q' to quit): ").lower()
            if answer == "y":
                play = True
                break
            if answer == "q":
                print(f"You left with {balance}$, Good Bye!")
                play = False
                break
            print(f"Invalid option {answer}.")


if __name__ == "__main__":
    play_game()

Some other extensions you may wish to add include the fact that real slot machines have a set of wheels, rather than random sequences. You might want to generate these wheels in advance and simulate spinning them (index into a point, and getting the next COLUMNS values in the sequence).

\$\endgroup\$
2
  • \$\begingroup\$ Thank you a lot , but did my code was good because i am begginer-intermediate i tried my best or should looking more for optimizating. \$\endgroup\$
    – mcccuklev
    Oct 10, 2022 at 13:42
  • 2
    \$\begingroup\$ Your code is fine, it does most of what you set out to do. Optimisation is rarely a concern, particularly at this stage. Get it right, then get it fast IF needed. More important is that it does the job it set out to do in a comprehensible and clear way, that way someone else can come along and get it faster if they need to, or make it suit their needs. \$\endgroup\$ Oct 10, 2022 at 18:53

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.