Rutas
/
Python
Python
/
Ejercicios
/
El juego de alienígenas de Ellen
El juego de alienígenas de Ellen

El juego de alienígenas de Ellen

Ejercicio de aprendizaje

Introducción

Programación orientada a objetos en Python

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

Clases

Las Classes son las definiciones de nuevos tipos de objeto, y a partir de ellas se crean nuevas instances de objetos. A menudo agrupan datos junto 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 construir muchos tipos distintos 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 añadir al cuerpo de la clase:

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

Se puede crear una instancia (objeto) de MyClass y vincularla a un nombre llamando a la clase (de la misma forma 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, 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 propia clase, antes de cualquier otro método. Pertenecen a la clase, lo que permite compartirlos 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 especialmente ú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 indica, los atributos de instancia son exclusivos de 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 sirve para inicializar los atributos o propiedades de instancia de los objetos. Por su papel en la inicialización de los atributos de instancia, a __init__() también se le llama class constructor o initializer. __init__() toma un parámetro obligatorio llamado self, que hace referencia al objeto recién inicializado o creado. Los datos para los atributos o propiedades de instancia se pueden pasar después como argumentos de __init__(), a continuación 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 al segundo índice del argumento location (una tupla) que se ha pasado 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 vinculada a la propia clase (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. Después puedes definir el resto de los parámetros como lo harías con 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 los atributos, para llamar a un método basta con 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 rellena 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 forma en que se accede a ellos desde 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

Dejar un hueco o esbozar la implementación con pass

En ejercicios de concepto anteriores y en los esqueletos de ejercicios de práctica, 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 lance un error de sintaxis por una función o una definición de clase vacías. Básicamente, es una forma de decirle al intérprete de Python: «¡No te preocupes! Voy a poner código aquí con el tiempo, simplemente aún 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 luchar contra alienígenas. Acaba de aprender programación orientada a objetos (POO) y está deseando aprovechar lo que el uso de classes puede ofrecer a su programa.

Para alegría de Ellen, te has ofrecido a ayudarla y ella te ha encargado programar los alienígenas contra los que tiene que luchar el jugador.

1. Crea la clase Alien

Define la clase Alien con un constructor que acepte dos parámetros <x_coordinate> y <y_coordinate>, y que los guarde en las variables de instancia x_coordinate y y_coordinate. Cada alienígena también empezará con un nivel de salud de 3, así que la variable health también debe inicializarse.

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

Ahora, cada alienígena debe 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 cuando se 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 añadir un método is_alive al que Ellen pueda llamar rápidamente para comprobar 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 tienen la capacidad de teletransportarse! Tendrás que escribir un método teleport que reciba nuevos valores de x_coordinate y y_coordinate, y que 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 se ha producido 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 ha dicho que lo hará más adelante, pero aun así le gustaría que el método collision_detection apareciera en la clase como recordatorio para desarrollar esa funcionalidad. Tendrá que recibir una variable de algún tipo (probablemente otro objeto), pero eso es realmente todo lo que sabes. Tendrás que asegurarte de que incluir la definición del método en la clase no cause ningún error al llamarlo:

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

6. Contador de alienígenas

Ellen ha vuelto 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. Crear un array 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 un list de objetos Alien(), a partir de un array 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 pestaña nueva
Python Exercism

¿Listo para empezar El juego de alienígenas de Ellen?

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