Why is such a simple game getting so heavy?

Asked

Viewed 197 times

1

This is the first project I developed using Pygame. Because it’s a simple card game, I hoped it would be very light, but not really. As I am using a very weak computer (single core 1gb ram) it is easy to notice this, here it is running below 20 fps. Will be problem in my code or is the pygame that is a little heavy even?

This is the code of my main class that makes everything work:

import os
import random
import pygame
from button import Button
from card import Card


class Truco(object):

    def __init__(self):
        super(Truco, self).__init__()
        os.environ["SDL_VIDEO_CENTERED"] = "1"
        pygame.init()
        self.load_fonts()
        pygame.display.set_caption("Truco")
        self.screen = pygame.display.set_mode([800, 600])
        self.screen_rect = self.screen.get_rect()
        self.clock = pygame.time.Clock()
        self.play()

    def load_fonts(self):
        self.fonts = {}
        for file in os.listdir("fonts"):
            for size in [18, 24, 40, 80]:
                self.fonts["{}_{}".format(file[0:-4], size)] =\
                    pygame.font.Font(os.path.join("fonts", file), size)

    def play(self):
        self.cards_list = []
        for value in "4567qjka23":
            for suit in "diamonds spades hearts clubs".split():
                self.cards_list.append(Card(suit, value))
        self.human_score = 0
        self.robot_score = 0
        self.background = pygame.image.load("images/background.png")
        self.background_rect = self.background.get_rect()
        self.table = pygame.image.load("images/table.png")
        self.table_rect = self.table.get_rect(center=self.screen_rect.center)
        self.button_correr = Button(
            "images/button_2.png", self.fonts["titan_one_24"],
            "Correr", [255, 255, 255],
            bottomright=self.screen_rect.bottomright)
        self.button_truco = Button(
            "images/button_1.png", self.fonts["titan_one_24"], "Truco",
            [0, 0, 0], midbottom=self.button_correr.rect.midtop)
        self.card_shuffle = pygame.mixer.Sound("sounds/card_shuffle.ogg")
        self.shuffle()

    def shuffle(self):
        self.card_shuffle.play()
        random.shuffle(self.cards_list)
        self.cards_group = pygame.sprite.Group()
        for x in range(0, 40):
            self.cards_list[x].rotoflip(0, "back")
            self.cards_list[x].rect.center = [self.screen_rect.centerx - x / 2,
                                              self.screen_rect.centery - x / 2]
            self.cards_group.add(self.cards_list[x])
        self.cards_human = []
        self.cards_robot = []
        for x in range(0, 6, 2):
            self.cards_human.append(self.cards_list[x])
            self.cards_robot.append(self.cards_list[x + 1])
        self.card_vira = self.cards_list[6]
        self.index = 0
        pygame.time.set_timer(pygame.USEREVENT, 750)

    def give_cards(self, x):
        if x < 3:
            self.cards_robot[x].rect.midtop = [
                self.screen_rect.centerx + (x - 1) * 70, self.screen_rect.top]
            self.cards_human[x].flip("front")
            self.cards_human[x].rect.midbottom = [
                self.screen_rect.centerx + (x - 1) * 70,
                self.screen_rect.bottom]
        else:
            self.card_vira.rotoflip(-90, "front")
            self.card_vira.rect.center = self.cards_list[7].rect.midright
            pygame.time.set_timer(pygame.USEREVENT, 0)

    def loop(self):
        self.running = True
        while self.running:
            self.event()
            self.draw()
            pygame.display.update()
            self.clock.tick(60)

    def event(self):
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                self.running = False
            elif event.type == pygame.USEREVENT:
                self.give_cards(self.index)
                self.index += 1
            elif event.type == pygame.MOUSEBUTTONDOWN:
                self.shuffle()
            elif event.type == pygame.MOUSEMOTION:
                mouse_pos = pygame.mouse.get_pos()
                self.button_correr.hover(mouse_pos)
                self.button_truco.hover(mouse_pos)


    def draw(self):
        score = self.fonts["libre_baskerville_40"].render(
            "{} x {}".format(self.human_score, self.robot_score),
            True, [255, 255, 255], [0, 0, 0])
        fps = self.fonts["libre_baskerville_18"].render("FPS: {}".format(
            self.clock.get_fps()), True, [0, 0, 0])
        fps_rect = fps.get_rect(bottomleft=self.screen_rect.bottomleft)
        self.screen.blit(self.background, self.background_rect)
        self.screen.blit(self.table, self.table_rect)
        self.screen.blit(score, [0, 0])
        self.screen.blit(fps, fps_rect)
        self.cards_group.draw(self.screen)
        self.button_correr.draw(self.screen)
        self.button_truco.draw(self.screen)

    def quit(self):
        pygame.quit()
  • You can upload the entire project, including images/sounds and extra files (from the Button and Card classes)?

  • https://drive.google.com/file/d/1RsQpJ_PGIzCRuFdkhhuf0OAI_0xih--L/view?usp=sharing

1 answer

1


Use Pycharm’s Profiling mode (which is nothing more than utility cProfile under the table) indicates that most of the time (91.3%) is spent on function blit.

inserir a descrição da imagem aqui

Although it is a necessary function to draw things on the screen, maybe we can decrease its use when it is not necessary. When I analyzed the code better, I found the biggest problem: we always draw all the cards, even the ones underneath each other.

I switched the

self.cards_group.draw(self.screen)

For the following:

for card in self.cards_human:
    self.screen.blit(card.image, card.rect)
for card in self.cards_robot:
    self.screen.blit(card.image, card.rect)

self.screen.blit(self.card_vira.image, self.card_vira.rect)

I mean, instead of drawing all the cards, I started drawing only the ones that were in my hand or the robot’s, and the face card. The FPS that was at 45 rose to ~65 on my machine, after removing the limitation of 60fps.

Of course this has the unwanted effect that the rest of the card cake is no longer drawn. The solution would be to have a proper picture for the card cake, or draw only the top card and then draw only the corner of each of the cards below, to avoid drawing the same card over each other several times.

Another optimization that I did but that didn’t have that much effect (maybe ~2fps) was not to load the same file to the back, but to use a global version only loaded once. I mean, in the card.py, did that:

back = pygame.image.load("images/card_back_red_1.png")
back = pygame.transform.smoothscale(back, [70, 95])

class Card(pygame.sprite.Sprite):
    (...)
    self.back = back
  • Thanks, it’s always good to see that you can do it differently.

Browser other questions tagged

You are not signed in. Login or sign up in order to post.