2
\$\begingroup\$

I made a 3 reel slot machine using the pygame library in Python. This is what I have so far.
I want to improve it to make it work for a 3x5 slot machine with multiple paylines, right now it is a 1x3 slot machine.
I also want to improve on my programming skills as I am still fairly new to programming and have only been doing it for about a year. If anyone has any recommendations on how to make this code cleaner or more efficient, let me know.
nums.json contains this list of numbers. I made this list of numbers so some icons have more weight than others, hence why some give the player more money.
[ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 2, 2 ]

This is the slotmachine class file.

import random

import json

f = open("nums.json")
nums = json.load(f)

class SlotMachine:
    def __init__(self, windict):
        self.windict = windict
        self.output = ""

    def play(self):
        num = random.choice(nums)
        self.output += str(num)
        return num

    def checkwin(self):
        if self.output in self.windict:
            win = str(self.windict[self.output] * 100)
            self.output = ""
            return win
        else:
            loss = "0"
            self.output = ""
            return loss

This is the main Python file.

import pygame

from slotmachine import SlotMachine

import time

pygame.init()
eagles = "The Best of My Love - On the Border (front).jpg"
beatles = "Abbey Road (front).jpg"
pink = "Wish you were here (front).jpg"
(width, height) = (800, 600)
screen = pygame.display.set_mode((width, height))
pygame.display.set_caption("Ryan's Slot Machine")
white = (255, 255, 255)
green = (0, 255, 0)
blue = (0, 0, 128)
screen.fill(white)
bg = pygame.image.load("border.png")
screen.blit(bg, (0, 0))
pygame.display.update()
picdict = {0: eagles, 1: beatles, 2: pink}
positions = ((200, 275), (350, 275), (497, 275))
slot = SlotMachine({"000": 1, "111": 2, "222": 5})
font = pygame.font.Font("freesansbold.ttf", 25)


def icon_to_screen(x, y, path):
    icon = pygame.image.load(path)
    small_icon = pygame.transform.scale(icon, (100,50))
    screen.blit(small_icon, (x, y))
    pygame.display.update()
    

def main():
    play = True
    while play:
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                play = False
            if event.type == pygame.KEYDOWN:
                if event.key == pygame.K_p:
                    screen.fill(white)
                    bg = pygame.image.load("border.png")
                    screen.blit(bg, (0, 0))
                    pygame.display.update()
                    for position in positions:
                        i = slot.play()
                        icon_to_screen(position[0], position[1], picdict[i])
                        time.sleep(.5)
                    text = font.render(slot.checkwin(), True, green, blue)
                    screen.blit(text, (670,36))
                    pygame.display.update()

              
if __name__ == "__main__":
    main()
\$\endgroup\$

1 Answer 1

1
\$\begingroup\$

nums.json should have a better name like weights.json, and should only contain three numbers: the weights of each slot outcome, as in

 [ 10, 5, 2 ]

When you open this file, do so with a context manager.

picdict shouldn't be a dictionary: you have a simple integer index, so just make it a tuple.

Don't call random.choice; call random.choices passing your weights.

Don't make output stringly-typed. Build it up as a tuple of integers. Similarly, checkwin (which should be named check_win) should return an integer and not a string.

The logic in check_win can be simplified by unconditionally calling get on windict with a default of 0 for no winnings. Only reset output once.

Some of the simple constants like your colours can stay as capitalised constants in the global namespace. The rest of the global code should move into a function. One easy way to achieve this is to move the global code into the __init__ of a class.

play needs to go away, and you should just use a while True.

When you check KEYDOWN you should simultaneously check the key code after an and.

Add a constant for things like the antialias parameter to render().

Suggested

Only partially tested since I don't have your images:

import json
import random
import pygame
import time

WHITE = (255, 255, 255)
GREEN = (0, 255, 0)
BLUE = (0, 0, 128)
ANTIALIAS = True

with open("nums.json") as f:
    WEIGHTS = json.load(f)


class SlotMachine:
    def __init__(self, win_dict: dict[tuple[int, ...], int]):
        self.win_dict = win_dict
        self.output: tuple[int, ...] = ()

    def play(self) -> int:
        num, = random.choices(population=range(3), weights=WEIGHTS)
        self.output += num,
        return num

    def check_win(self) -> int:
        winnings = self.win_dict.get(self.output, 0)
        self.output = ()
        return 100 * winnings


class Game:
    WIDTH, HEIGHT = 800, 600
    POSITIONS = (200, 275), (350, 275), (497, 275)

    def __init__(self) -> None:
        pygame.init()
        pygame.display.set_caption("Ryan's Slot Machine")

        self.screen = pygame.display.set_mode((self.WIDTH, self.HEIGHT))
        self.screen.fill(WHITE)

        self.bg = pygame.image.load("border.png")
        self.screen.blit(self.bg, (0, 0))
        pygame.display.update()

        self.slot_pictures = (
            "The Best of My Love - On the Border (front).jpg",
            "Abbey Road (front).jpg",
            "Wish you were here (front).jpg",
        )
        self.machine = SlotMachine({(0, 0, 0): 1, (1, 1, 1): 2, (2, 2, 2): 5})
        self.font = pygame.font.Font("freesansbold.ttf", 25)

    def icon_to_screen(self, x: int, y: int, path: str) -> None:
        icon = pygame.image.load(path)
        small_icon = pygame.transform.scale(icon, (100, 50))
        self.screen.blit(small_icon, (x, y))
        pygame.display.update()

    def run(self) -> None:
        while True:
            for event in pygame.event.get():
                if event.type == pygame.QUIT:
                    return

                if event.type == pygame.KEYDOWN and event.key == pygame.K_p:
                    self.screen.fill(WHITE)
                    self.screen.blit(self.bg, (0, 0))
                    pygame.display.update()

                    for x, y in self.POSITIONS:
                        i = self.machine.play()
                        self.icon_to_screen(x, y, self.slot_pictures[i])
                        time.sleep(.5)

                    winning_string = str(self.machine.check_win())
                    foreground = GREEN
                    background = BLUE
                    text = self.font.render(winning_string, ANTIALIAS, foreground, background)
                    self.screen.blit(text, (670, 36))
                    pygame.display.update()


if __name__ == "__main__":
    Game().run()

You can further simplify by not keeping an output and not mutating the slot machine at all; just returning three reels at once; and including the factor of 100 in your winnings dictionary:

import json
import random
import pygame
import time

WHITE = (255, 255, 255)
GREEN = (0, 255, 0)
BLUE = (0, 0, 128)
ANTIALIAS = True


class SlotMachine:
    def __init__(self, win_dict: dict[tuple[int, ...], int]):
        with open("weights.json") as f:
            self.weights = json.load(f)
        self.win_dict = win_dict

    def play(self) -> list[int]:  # reel positions
        return random.choices(population=range(3), weights=self.weights, k=3)

    def check_win(self, reels: list[int]) -> int:
        return self.win_dict.get(tuple(reels), 0)


class Game:
    WIDTH, HEIGHT = 800, 600
    POSITIONS = (200, 275), (350, 275), (497, 275)

    def __init__(self) -> None:
        pygame.init()
        pygame.display.set_caption("Ryan's Slot Machine")

        self.screen = pygame.display.set_mode((self.WIDTH, self.HEIGHT))
        self.screen.fill(WHITE)

        self.bg = pygame.image.load("border.png")
        self.screen.blit(self.bg, (0, 0))
        pygame.display.update()

        self.slot_pictures = (
            "The Best of My Love - On the Border (front).jpg",
            "Abbey Road (front).jpg",
            "Wish you were here (front).jpg",
        )
        self.machine = SlotMachine({
            (0, 0, 0): 100,
            (1, 1, 1): 200,
            (2, 2, 2): 500,
        })
        self.font = pygame.font.Font("freesansbold.ttf", 25)

    def icon_to_screen(self, x: int, y: int, path: str) -> None:
        icon = pygame.image.load(path)
        small_icon = pygame.transform.scale(icon, (100, 50))
        self.screen.blit(small_icon, (x, y))
        pygame.display.update()

    def run(self) -> None:
        while True:
            for event in pygame.event.get():
                if event.type == pygame.QUIT:
                    return

                if event.type == pygame.KEYDOWN and event.key == pygame.K_p:
                    self.screen.fill(WHITE)
                    self.screen.blit(self.bg, (0, 0))
                    pygame.display.update()

                    reels = self.machine.play()
                    for (x, y), reel in zip(self.POSITIONS, reels):
                        self.icon_to_screen(x, y, self.slot_pictures[reel])
                        time.sleep(.5)

                    winning_string = str(self.machine.check_win(reels))
                    foreground = GREEN
                    background = BLUE
                    text = self.font.render(winning_string, ANTIALIAS, foreground, background)
                    self.screen.blit(text, (670, 36))
                    pygame.display.update()


if __name__ == "__main__":
    Game().run()
\$\endgroup\$
1
  • \$\begingroup\$ This is very helpful, thank you so much! \$\endgroup\$ Jul 19 at 15:12

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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