Trilhas
/
Python
Python
/
Exercícios
/
O jogo alienígena da Ellen
O jogo alienígena da Ellen

O jogo alienígena da Ellen

Exercício de aprendizagem

Introdução

Programação orientada a objetos em Python

Se você vem programando em um estilo funcional, declarativo ou imperativo, mudar o foco para a programação orientada a objetos (OOP) pode parecer um pouco estranho. Uma abordagem de OOP pede que você pense em modelar um problema como um ou mais objects que interagem entre si, mantêm estado e agem sobre os dados. Os objetos podem representar entidades do mundo real (como carros ou gatos) ou conceitos mais abstratos (como inteiros, veículos ou mamíferos). Cada objeto se torna uma instância única na memória do computador e representa uma parte do modelo como um todo.

Classes

As Classes são as definições de novos tipos de objeto, e é a partir delas que novas instances de objetos são criadas. Muitas vezes elas agrupam dados com código ou funções que operam sobre esses dados. Nesse sentido, as classes são moldes ou conjuntos de instruções a partir dos quais muitos objetos de um tipo semelhante podem ser construídos e usados. Um programa complexo pode ter muitas classes, e cada uma constrói muitos tipos diferentes de objetos. O processo de construir um objeto a partir de uma classe é chamado de instantiation (ou criação de uma instância da classe).

Uma definição de classe em Python é bem simples:

class MyClass:
    # Class body goes here

Atributos de classe

Os campos de classe (também conhecidos como properties, attributes, data members ou variables) podem ser adicionados ao corpo da classe:

class MyClass:
    number = 5
    string = "Hello!"

Uma instância (objeto) de MyClass pode ser criada e vinculada a um nome ao chamar a classe (da mesma forma que se chama uma função):

>>> new_object = MyClass()

# Class is instantiated and resulting object is bound to the "new_object" name.
# Note: the object address 'at 0x15adc55b0' will vary by computer.
>>> new_object
<__main__.MyClass at 0x15adc55b0>

Os Class attributes são compartilhados por todos os objetos (ou instâncias) criados a partir de uma classe e podem ser acessados via dot notation, um . colocado depois do nome do objeto e antes do nome do atributo:

>>> new_object = MyClass()

# Accessing the class attribute "number" via dot-notation.
>>> new_object.number
5

# Accessing the class attribute "string" via dot-notation.
>>> new_object.string
'Hello!'

# Instantiating an additional object and binding it to the "second_new_object" name.
>>> second_new_object = MyClass()

>>> second_new_object
# Note: the object address "at 0x15ad99970" will vary by computer.
<__main__.MyClass at 0x15ad99970>

# Second_new_object shares the same class attributes as new_object.
>>> new_object.number == second_new_object.number
True

Os atributos de classe são definidos no corpo da própria classe, antes de quaisquer outros métodos. Eles pertencem à classe, o que permite que sejam compartilhados entre as instâncias dela. Como esses atributos são compartilhados, é possível acessar e manipular o valor deles diretamente a partir da classe. Alterar o valor dos atributos de classe altera o valor para todos os objetos instanciados a partir da classe:

>>> obj_one = MyClass()
>>> obj_two = MyClass()

# Accessing a class attribute from an object.
>>> obj_two.number
5

# Accessing the class attribute from the class itself.
>>> MyClass.number
5

# Modifying the value of the "number" class attribute.
>>> MyClass.number = 27

# Modifying the "number" class attribute changes the "number" attribute for all objects.
>>> obj_one.number
27

>>> obj_two.number
27

Ter um monte de objetos com dados sincronizados o tempo todo não é lá muito útil. Felizmente, os objetos criados a partir de uma classe podem ser personalizados com seus próprios instance attributes (ou propriedades, variáveis ou campos de instância). Como o nome sugere, os atributos de instância são exclusivos de cada objeto e podem ser modificados de forma independente.

Personalizando a instanciação de objetos com __init__()

O especial "dunder method" (abreviação de "double underscore method") __init__() é usado para personalizar instâncias de classe e pode ser usado para inicializar atributos ou propriedades de instância dos objetos. Por seu papel na inicialização de atributos de instância, __init__() também é chamado de class constructor ou initializer. O __init__() recebe um parâmetro obrigatório chamado self, que se refere ao objeto recém-inicializado ou criado. Os dados para os atributos ou propriedades de instância podem então ser passados como argumentos de __init__(), depois do parâmetro self.

Abaixo, MyClass agora tem atributos de instância chamados location_x e location_y. Como você pode ver, os dois atributos foram atribuídos ao primeiro e ao segundo índices do argumento location (uma tupla) que foi passado para __init__(). Os atributos location_x e location_y de uma instância de classe agora serão inicializados quando você instanciar a classe e um objeto for criado:

class MyClass:
    # These are class attributes, variables, or fields.
    number = 5
    string = "Hello!"

    # This is the class "constructor", with a "location" parameter that is a tuple.
    def __init__(self, location):

        # This is an instance or object property, attribute, or variable.
        # Note that we are unpacking the tuple argument into two separate instance variables.
        self.location_x = location[0]
        self.location_y = location[1]

# Create a new object "new_object_one", with object property (1, 2).
>>> new_object_one = MyClass((1, 2))

# Create a second new object "new_object_two" with object property (-8, -9).
>>> new_object_two = MyClass((-8, -9))

# Note that new_object_one.location_x and new_object_two.location_x two different values.
>>> new_object_one.location_x
1

>>> new_object_two.location_x
-8

Repare que você só precisa passar um argumento ao inicializar MyClass acima. O Python cuida de passar self quando a classe é chamada.

Métodos

Um method é uma function vinculada à própria classe (conhecido como class method, que será discutido em um exercício posterior) ou a uma instância da classe (objeto). Os métodos que operam sobre um objeto (instância) precisam ser definidos com self como primeiro parâmetro. Você pode então definir o restante dos parâmetros como faria em uma função "normal" ou não vinculada:

class MyClass:
    number = 5
    string = "Hello!"

    # Class constructor.
    def __init__(self, location):
        # Instance properties
        self.location_x = location[0]
        self.location_y = location[1]

    # Instance method. Note "self" as first parameter.
    def change_location(self, amount):
        self.location_x += amount
        self.location_y += amount
        return self.location_x, self.location_y

Assim como no acesso a atributos, chamar um método exige apenas colocar um . depois do nome do objeto e antes do nome do método. O método chamado não precisa de uma cópia do objeto como primeiro parâmetro. O Python preenche o self automaticamente:

class MyClass:
    number = 5
    string = "Hello!"

    def __init__(self, location):
        self.location_x = location[0]
        self.location_y = location[1]

    def change_location(self, amount):
        self.location_x += amount
        self.location_y += amount
        return self.location_x, self.location_y

# Make a new test_object with location (3,7)
>>> test_object = MyClass((3,7))
>>> (test_object.location_x, test_object.location_y)
(3,7)

# Call change_location to increase location_x and location_y by 7.
>>> test_object.change_location(7)
(10, 14)

Os atributos de classe podem ser acessados de dentro dos métodos de instância da mesma forma que são acessados fora da classe:

class MyClass:
    number = 5
    string = "Hello!"

    def __init__(self, location):
        self.location_x = location[0]
        self.location_y = location[1]

    # Alter instance variable location_x and location_y
    def change_location(self, amount):
        self.location_x += amount
        self.location_y += amount
        return self.location_x, self.location_y

    # Alter class variable number for all instances from within an instance.
    def increment_number(self):
        # Increment the 'number' class variable by 1.
        MyClass.number += 1


>>> test_object_one = MyClass((0,0))
>>> test_object_one.number
5

>>> test_object_two = MyClass((13, -3))
>>> test_object_two.increment_number()
>>> test_object_one.number
6

Placeholder ou stub de implementação com pass

Em exercícios de conceito e stubs de exercícios de prática anteriores, você já deve ter visto a palavra-chave pass usada dentro do corpo de funções no lugar de código de verdade. A palavra-chave pass é um placeholder sintaticamente válido: ela impede que o Python lance um erro de sintaxe por causa de uma função ou definição de classe vazia. Essencialmente, é uma forma de dizer ao intérprete do Python: 'Não se preocupe! Eu vou colocar código aqui em algum momento, só ainda não fiz isso.'

class MyClass:
    number = 5
    string = "Hello!"

    def __init__(self, location):
        self.location_x = location[0]
        self.location_y = location[1]

    # Alter instance variable location_x and location_y
    def change_location(self, amount):
        self.location_x += amount
        self.location_y += amount
        return  self.location_x, self.location_y

    # Alter class variable number for all instances
    def increment_number(self):
        # Increment the 'number' class variable by 1.
        MyClass.number += 1

    # This will compile and run without error, but has no current functionality.
    def pending_functionality(self):
       # Stubbing or placholding the body of this method.
       pass

Instruções

Ellen está criando um jogo em que a pessoa que joga precisa lutar contra aliens. Ela acabou de aprender sobre Programação Orientada a Objetos (POO) e está ansiosa para aproveitar o que o uso de classes pode oferecer ao programa dela.

Para a alegria de Ellen, você se ofereceu para ajudar e ela lhe deu a tarefa de programar os aliens que a pessoa que joga precisa enfrentar.

1. Crie a classe Alien

Defina a classe Alien com um construtor que aceita dois parâmetros, <x_coordinate> e <y_coordinate>, e os coloca nas variáveis de instância x_coordinate e y_coordinate. Todo alien também começa com um nível de vida de 3, então a variável health também deve ser inicializada.

>>> alien = Alien(2, 0)
>>> alien.x_coordinate
2
>>> alien.y_coordinate
0
>>> alien.health
3

Agora, cada alien deve ser capaz de rastrear internamente a própria posição e a própria vida.

2. O método hit

Ellen gostaria que a class Alien tivesse um método hit que diminui a vida de um objeto alien em 1 quando chamado. Assim, ela pode simplesmente chamar <alien>.hit() em vez de ter que alterar manualmente a vida de um alien. Fica a seu critério se hit() leva os pontos de vida para ou abaixo de zero.

>>> alien = Alien(0, 0)

# Initialized health value.
>>> alien.health
3

# Decrements health by 1 point.
>>> alien.hit()
>>> alien.health
2

3. O método is_alive

Você percebe que, se a vida continuar diminuindo, em algum momento ela provavelmente chegará a 0 (ou até menos!). Seria uma boa ideia adicionar um método is_alive que Ellen possa chamar rapidamente para verificar se o alien está... bem... vivo. 😉 <alien>.is_alive() deve retornar um Boolean.

>>> alien.health
1
>>> alien.is_alive()
True
>>> alien.hit()
>>> alien.health
0
>>> alien.is_alive()
False

4. O método teleport

No jogo da Ellen, os aliens têm a habilidade de se teletransportar! Você vai precisar escrever um método teleport que recebe novos valores de x_coordinate e y_coordinate e muda as coordenadas do alien de acordo.

>>> alien.teleport(5, -4)
>>> alien.x_coordinate
5
>>> alien.y_coordinate
-4

5. O método collision_detection

Obviamente, se os aliens podem ser atingidos por algo, eles precisam ser capazes de detectar quando essa colisão acontece. No entanto, algoritmos de detecção de colisão podem ser complicados, e você ainda não sabe como implementar um. Ellen disse que vai fazer isso mais tarde, mas ela ainda gostaria que o método collision_detection aparecesse na classe como um lembrete para construir essa funcionalidade. Ele vai precisar receber uma variável de algum tipo (provavelmente outro objeto), mas isso é realmente tudo o que você sabe. Você vai precisar garantir que colocar a definição do método na classe não cause nenhum erro quando ele for chamado:

>>> alien.collision_detection(other_object)
>>>

6. Contador de aliens

Ellen voltou com um novo pedido para você. Ela quer acompanhar quantos aliens foram criados ao longo do tempo de vida do jogo. Ela diz que isso tem a ver com o sistema de pontuação.

Por exemplo:

>>> alien_one = Alien(5, 1)
>>> alien_one.total_aliens_created
1
>>> alien_two = Alien(3, 0)
>>> alien_two.total_aliens_created
2
>>> alien_one.total_aliens_created
2
# Accessing the variable from the class directly
>>> Alien.total_aliens_created
2

7. Criando uma lista de aliens

Ellen adorou o que você fez até agora, mas ela tem mais um favor a pedir. Ela gostaria de uma função independente (fora da classe Alien()) que cria uma list de objetos Alien(), dada uma lista de posições (como tuples).

Por exemplo:

>>> alien_start_positions = [(4, 7), (-1, 0)]
>>> aliens = new_aliens_collection(alien_start_positions)
...
>>> for alien in aliens:
...     print(alien.x_coordinate, alien.y_coordinate)
(4, 7)
(-1, 0)
Editar via GitHub O link abre em uma nova janela ou aba
Python Exercism

Tudo pronto para começar O jogo alienígena da Ellen?

Crie sua conta no Exercism para aprender e dominar Python com 17 conceitos146 exercícios e mentoria humana de verdade, tudo de graça.