How can I use a class to shrink this code section in Pygame?

Viewed 61

I'm trying to make 5 screens to show information about a team and need to print some text in each one of them. How can I avoid all these lines?

fonte = pygame.font.SysFont('Times New Roman', 20)

nomeEquipe = fonte.render("Nome da Equipe", True, preto)
nomeDavid = fonte.render("David Waters Teixeira Rodrigues", True, preto)
nomeRafael = fonte.render("Rafael Pereira de Souza", True, preto)
nomeVicente = fonte.render("Vicente de Paulo Vidal Alencar", True, preto)
nomeVictor = fonte.render("Victor Jerrysson Gama Bastos", True, preto)
nomeWillian = fonte.render("Willian Alves Batista", True, preto)
funcaoDavid = fonte.render("Função: menu inicial", True, preto)
funcaoRafael = fonte.render("Função: tela da equipe", True, preto)
funcaoVicente = fonte.render("Função: tela sobre", True, preto)
funcaoVictor = fonte.render("Função: jogo", True, preto)
funcaoWillian = fonte.render("Função: jogo", True, preto)

I tried to implement this:

class Fonte:

    def __init__(self, fonte, tamanho):
        pygame.font.SysFont.__init__(self)
        self.fonte = fonte
        self.tamanho = tamanho

class Tela:

    def __init__(self, texto, antialias, cor):
        Fonte.render.__init__(self)
        self.texto = texto
        self.antialias = antialias
        self.cor = cor

fonte = Fonte('Times New Roman', 20)
nomeEquipe = Tela("Nome da Equipe", True, preto)

But I receive this error:

Traceback (most recent call last):
File "/home/rafael/teste.py", line 55, in <module>
nomeEquipe = Tela("Nome da Equipe", True, preto)
File "/home/rafael/teste.py", line 45, in __init__
Fonte.render.__init__(self)
AttributeError: type object 'Fonte' has no attribute 'render'
3 Answers

You can have the single instance of SysFont as a class variable:


class Tela:
    fonte = pygame.font.SysFont('Times New Roman', 20)

    def __init__(self, texto, antialias, cor):
        self.surface = Tela.fonte.render(texto, antialias, cor)
        self.texto = texto
        self.antialias = antialias
        self.cor = cor


nomeEquipe = Tela("Nome da Equipe", True, preto)

However, you are still going to need all those line for the different text.

But, if you can default the last two parameters:

class Tela:
    fonte = pygame.font.SysFont('Times New Roman', 20)

    def __init__(self, texto, antialias=True, cor=preto):
        self.surface = Tela.fonte.render(texto, antialias, cor)
        self.texto = texto
        self.antialias = antialias
        self.cor = cor

Nomes = ["Nome da Equipe", "David Waters Teixeira Rodrigues", "Rafael Pereira de Souza", "Vicente de Paulo Vidal Alencar", "Victor Jerrysson Gama Bastos", "Willian Alves Batista", "Função: menu inicial", "Função: tela da equipe", "Função: tela sobre", "Função: jogo", "Função: jogo"]

Equipes = [Tela(nome) for nome in Nomes]

I recommend to use a lambda expression:

fonte = pygame.font.SysFont('Times New Roman', 20)
fr = lambda t: fonte.render(t, True, preto)

nomeEquipe = fr("Nome da Equipe")
nomeDavid = fr("David Waters Teixeira Rodrigues")
# [...]

However, I recommend saving the text Surfaces in a list. Define a list of strings and create a list of Surfaces using list comprehensions:

text_list = [
    "Nome da Equipe", 
    "David Waters Teixeira Rodrigues".
    "Rafael Pereira de Souza"
    # [...]
    ]

fonte = pygame.font.SysFont('Times New Roman', 20)
surf_list = [fonte.render(t, True, preto) for t in text_list]

The text Surfces can be accessed by subscription (surf_list[0], surf_list[1], ...).


If you want to use a class for the font, do the following:

class Fonte:
    def __init__(self, fontname, size, antialias, cor):
        self.font = pygame.font.SysFont(fontname, size)
        self.antialias = antialias
        self.cor = cor
    def render(self, t):
        return self.font.render(t, self.antialias, self.cor)

fonte = Fonte('Times New Roman', 20, True, preto)

nomeEquipe = fonte.render("Nome da Equipe")
nomeDavid = fonte.render("David Waters Teixeira Rodrigues")
# [...]

First, the error at hand.

render seems to be a method of the pygame.font.SysFont class, but you are trying to call it on your Fonte class (the type itself, not an instance!) which does not define it, so Python has no idea what to do and complains. To make your class do what you likely meant when writing it you should either inherit SysFont (and thus its render definition) or define a suitable render method yourself, then create an instance and call the method on that.

Past that, your Tela class tries to call __init__ on a method, which makes no sense, while also passing that method an instance of Tela. That line is a giant mess.

Below is a fixed version that should do the same your original code did but with classes, somewhat in the spirit of what gives you the error. I would not suggest this is a good idea and indeed I think it is not appropriate given the available context.

Disclaimer: I did not test this with PyGame, I just spun up the interpreter with toy classes to check the super().__init__(...) call. This should have the same behaviour of your "before" code, which I assume you are happy with and does something useful.

class Fonte(pygame.font.SysFont):
    def __init__(self, *args, **kwargs):
        super().__init__(self, *args, **kwargs)


class Tela:
    def __init__(self, texto, antialias, cor):
        self.texto = texto
        self.antialias = antialias
        self.cor = cor

    def render_with_font(self, fonte):
        return fonte.render(self.texto, self.antialias, self.cor)
        

fonte = Fonte('Times New Roman', 20)
nomeEquipe = Tela("Nome da Equipe", True, preto).render_with_font(fonte)
...

Now, in more practical terms, notice that the new classes do nothing to reduce the amount of stuff we have to write! We could have just used SysFont in the first place like in the original and saved code and effort.

Onto what you likely want to do, i.e. get the behaviour of your "before" code without a thousand fonte.render(...) lines.

Your case looks a lot like you want a number of variables containing the results of fairly homogeneous calls. This is verbose no matter how you put it if you insist on having separate variables, but it is very easy to shrink if you accept the fact that you have just one variable that is some kind of collection, like a list or dict:

# The data must still be declared somewhere, can't reduce this
text_list = [
    'Nome da Equipe',
    'David Waters Teixeira Rodrigues',
    # ...
]
rendered_text_list = []
for text in text_list:
    rendered_text_list.append(fonte.render(text, True, preto))
# nomeEquipe = rendered_text_list[0]
# nomeDavid = rendered_text_list[1]
# and so on, you can use the list instead of variables

The last for cycle is a textbook case for list comprehensions, but I find good old loops easier to grok for beginners.

Related