Tracks
/
Python
Python
/
Ejercicios
/
El juego de aliens de Ellen
El juego de aliens de Ellen

El juego de aliens de Ellen

Ejercicio de aprendizaje

Introducción

Programación orientada a objetos en Python

Si has estado programando en un estilo funcional, declarativo o imperativo, cambiar el enfoque a la programación orientada a objetos (POO) puede resultar un poco extraño. Un enfoque de POO le pide a quien programa que piense en modelar un problema como uno o más objects que interactúan entre sí, mantienen estado y actúan sobre los datos. Los objetos pueden representar entidades del mundo real (como automóviles o gatos) o conceptos más abstractos (como números enteros, vehículos o mamíferos). Cada objeto se convierte en una instancia única en la memoria de la computadora y representa alguna parte del modelo general.

Clases

Las Classes son las definiciones de nuevos tipos de objetos, y a partir de ellas se crean nuevas instances de objetos. A menudo combinan datos con código o funciones que operan sobre esos datos. En este sentido, las clases son planos o conjuntos de instrucciones a partir de los cuales se pueden construir y usar muchos objetos de un tipo similar. Un programa complejo puede tener muchas clases, y cada una construye muchas variedades diferentes de objetos. El proceso de construir un objeto a partir de una clase se conoce como instantiation (o crear una instancia de la clase).

La definición de una clase en Python es sencilla:

class MyClass:
    # Class body goes here

Atributos de clase

Los campos de clase (también conocidos como properties, attributes, data members o variables) se pueden agregar al cuerpo de la clase:

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

Se puede crear una instancia (objeto) de MyClass y vincularla a un nombre al llamar a la clase (de la misma manera en que se llama a una función):

>>> 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>

Los Class attributes se comparten entre todos los objetos (o instancias) creados a partir de una clase, y se puede acceder a ellos mediante la dot notation, es decir, un . colocado después del nombre del objeto y antes del nombre del 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

Los atributos de clase se definen en el cuerpo de la clase misma, antes que cualquier otro método. Son propiedad de la clase, lo que permite que se compartan entre las instancias de la clase. Como estos atributos se comparten, se puede acceder a su valor y manipularlo desde la clase directamente. Modificar el valor de los atributos de clase cambia el valor para todos los objetos instanciados a partir de la clase:

>>> 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

Tener un montón de objetos con datos sincronizados en todo momento no es particularmente útil. Afortunadamente, los objetos creados a partir de una clase se pueden personalizar con sus propios instance attributes (o propiedades, variables o campos de instancia). Como su nombre lo sugiere, los atributos de instancia son únicos para cada objeto y se pueden modificar de forma independiente.

Personalizar la instanciación de objetos con __init__()

El especial «método dunder» (abreviatura de «método de doble guion bajo») __init__() se usa para personalizar las instancias de clase y se puede usar para inicializar atributos o propiedades de instancia de los objetos. Por su papel en la inicialización de atributos de instancia, __init__() también se conoce como class constructor o initializer. __init__() recibe un parámetro obligatorio llamado self, que se refiere al objeto recién inicializado o creado. Los datos para los atributos o propiedades de instancia se pueden pasar luego como argumentos de __init__(), después del parámetro self.

A continuación, MyClass ahora tiene atributos de instancia llamados location_x y location_y. Como puedes ver, los dos atributos se han asignado al primer y segundo índice del argumento location (una tupla) que se pasó a __init__(). Los atributos location_x y location_y de una instancia de clase ahora se inicializarán cuando instancies la clase y se cree un objeto:

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

Ten en cuenta que solo necesitas pasar un argumento al inicializar MyClass arriba; Python se encarga de pasar self cuando se llama a la clase.

Métodos

Un method es una function que está vinculada a la clase misma (conocida como método de clase, que se tratará en un ejercicio posterior) o a una instancia de la clase (objeto). Los métodos que operan sobre un objeto (instancia) deben definirse con self como primer parámetro. Luego puedes definir el resto de los parámetros como lo harías para una función «normal» o no 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

Al igual que el acceso a atributos, llamar a un método simplemente requiere poner un . después del nombre del objeto y antes del nombre del método. El método llamado no necesita una copia del objeto como primer parámetro; Python completa self automáticamente:

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)

Se puede acceder a los atributos de clase desde dentro de los métodos de instancia de la misma manera en que se accede a ellos fuera de la clase:

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

Marcadores de posición o stubs de implementación con pass

En ejercicios de concepto y stubs de ejercicios de práctica anteriores, habrás visto la palabra clave pass usada dentro del cuerpo de funciones en lugar de código real. La palabra clave pass es un marcador de posición sintácticamente válido: evita que Python arroje un error de sintaxis por una función o definición de clase vacía. Esencialmente, es una forma de decirle al intérprete de Python: «¡No te preocupes! Voy a poner código aquí en algún momento, simplemente todavía no lo he hecho».

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

Instrucciones

Ellen está creando un juego en el que el jugador tiene que pelear contra alienígenas. Acaba de aprender sobre la programación orientada a objetos (POO) y tiene muchas ganas de aprovechar lo que el uso de classes podría aportarle a su programa.

Para alegría de Ellen, te ofreciste a ayudar y ella te encargó la tarea de programar los alienígenas contra los que el jugador tiene que pelear.

1. Crea la clase Alien

Define la clase Alien con un constructor que acepte dos parámetros <x_coordinate> y <y_coordinate>, y asígnalos a las variables de instancia x_coordinate y y_coordinate. Además, cada alienígena comenzará con un nivel de salud de 3, así que también debes inicializar la variable health.

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

Ahora, cada alienígena debería poder llevar un seguimiento interno de su propia posición y su salud.

2. El método hit

A Ellen le gustaría que la class Alien tuviera un método hit que reduzca en 1 la salud de un objeto alienígena cada vez que se lo llama. De esta forma, puede simplemente llamar a <alien>.hit() en lugar de tener que cambiar manualmente la salud de un alienígena. Depende de ti si hit() lleva los puntos de salud hasta cero o por debajo de cero.

>>> alien = Alien(0, 0)

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

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

3. El método is_alive

Te das cuenta de que, si la salud sigue disminuyendo, en algún momento probablemente llegará a 0 (¡o incluso menos! ). Sería buena idea agregar un método is_alive al que Ellen pueda llamar rápidamente para verificar si el alienígena está... bueno... vivo. 😉 <alien>.is_alive() debe devolver un Boolean.

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

4. El método teleport

¡En el juego de Ellen, los alienígenas pueden teletransportarse! Vas a tener que escribir un método teleport que reciba nuevos valores de x_coordinate y y_coordinate, y cambie las coordenadas del alienígena en consecuencia.

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

5. El método collision_detection

Obviamente, si algo puede golpear a los alienígenas, entonces necesitan poder detectar cuándo ocurrió esa colisión. Sin embargo, los algoritmos de detección de colisiones pueden ser complicados, y todavía no sabes cómo implementar uno. Ellen dijo que lo hará más adelante, pero aun así le gustaría que el método collision_detection aparezca en la clase como recordatorio para desarrollar esa funcionalidad. Va a tener que recibir una variable de algún tipo (probablemente otro objeto), pero eso es realmente todo lo que sabes. Vas a tener que asegurarte de que incluir la definición del método en la clase no cause errores cuando se lo llame:

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

6. Contador de alienígenas

Ellen volvió con una nueva petición para ti. Quiere llevar un registro de cuántos alienígenas se han creado a lo largo de la partida. Dice que tiene algo que ver con el sistema de puntuación.

Por ejemplo:

>>> 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. Crea una lista de alienígenas

A Ellen le encanta lo que has hecho hasta ahora, pero tiene un favor más que pedirte. Le gustaría una función independiente (fuera de la clase Alien() ) que cree una list de objetos Alien(), a partir de una lista de posiciones (como tuples).

Por ejemplo:

>>> 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 en GitHub El enlace se abre en una ventana o una pestaña nuevas
Python Exercism

¿Todo listo para empezar El juego de aliens de Ellen?

Regístrate en Exercism para aprender y dominar Python con 17 conceptos146 ejercicios y mentoría humana real, todo gratis.