<jupyter_start><jupyter_text>Pong Vamos a empezar a hacer un juego completo, empezaremos con un juego muysencillo e histórico: El **Pong**.> Nota:Aunque existieron con anterioridad otros videojuegos de las dos décadas anteriores como [OXO](https://es.wikipedia.org/wiki/OXO) (ejecutado en una computadora única en el mundo) y posterior a éste [Spacewar!](https://es.wikipedia.org/wiki/Spacewar!) bajo la PDP-1 de DEC, eran, en su mayoría, proyectos experimentales: Pong está considerado por muchos como el más importante de entre la primera generación de videojuegos modernos, debido a que fue el primero en comercializarse a nivel masivo y no ejecutarse en máquinas únicas. Más información sobre el [Pong en la wikipedia](https://es.wikipedia.org/wiki/Pong)<jupyter_code>from IPython.display import HTML

HTML('''
<iframe width="560" height="315" src="https://www.youtube.com/embed/e4VRgY3tkh0" frameborder="0" allow="autoplay; encrypted-media" allowfullscreen></iframe>
''')<jupyter_output><empty_output><jupyter_text>Primeros pasos Empezamos con los `imports` habituales:<jupyter_code>import pygame
import random
import time<jupyter_output><empty_output><jupyter_text>Y algunas constantes también habituales: El tamaño de la pantalla y algunos colores básicos:<jupyter_code># Tamaño de pantalla
SIZE = WIDTH, HEIGHT = 800, 640

# Colores
BLACK = (0, 0, 0)
WHITE = (255, 255, 255)
RED = (255, 0, 0)
GREEN = (0, 255, 0)
BLUE = (0, 0, 255)
CYAN = (0, 255, 255)
MAGENTA = (255, 0, 255)
YELLOW = (255, 255, 0)

# Velocidad del juego
FPS = 30<jupyter_output><empty_output><jupyter_text>Vamos a empezar programando la pelota, de forma parecida a como hicimos con las estrellas enel ejercicio anterior. Aunque solo necesitamos una pelota, en la mayoría de los lenguajes orientadosa objetos se usa el concepto de clase, lo que significa que tenemos que escribir una clase pelotay luego instanciar un objeto de tipo pelota:<jupyter_code>class Ball:
    
    def __init__(self, x=0, y=0):
        self.x = x
        self.y = y
        
    def update(self):
        self.x += 1
    
    def draw(self, canvas):
        rect = pygame.Rect((self.x-5, self.y-5), (11, 11))
        pygame.draw.rect(canvas, WHITE, rect)<jupyter_output><empty_output><jupyter_text>Nuestra clase `Ball` tiene dos atributos, `x` e `y`, para saber su posición en la pantalla,y dos métodos, uno llamado `update` que servira para actualizar el estado de la pelota (Por el momento no hace nada). y otro llamada `draw` que dibuja la pelota, que ennuestro caso es simplemente un cuadrado blanco.El método `draw` utiliza una clase que no hemos usado antes, `Rect`. Resulta que los rectángulos son muy útiles a la hora de desarrollar un videojuego. Podemosespecificar un rectángulo usando una tupla de cuatro elementos, peropygame define su propia clase para rectángulos porque eso le permite añadir determinados atributos y métodos muy útiles, como por ejemplo, saber si dos rectángulos se solapan, modificar al ancho y el alto, detectarsi un punto está dentro o fuera del rectángulo, etc...Para crear un rectángulo, podemos hacerlo de dos maneras:1) La primera es pasándole cuatro parámetros: La posición x de la esquina superior derecha, laposición y de la misma esquina, el ancho del rectangulo y el alto del rectangulo.2) La segunda es pasarle dos parametros, cada uno de los cuales es una dupla (una tuplade dos elementos). La primera dupla son las coordenadas x e y, y la segunda el ancho y el alto.Es decir, las siguientes dos lineas crean dos rectángulos exactamente iguales:<jupyter_code>r1 = pygame.Rect(50, 50, 100, 150)
r2 = pygame.Rect((50, 50), (100, 150))
assert r1 == r2
r1.center<jupyter_output><empty_output><jupyter_text>Según el caso puede ser más conveniente un sistema o el otro. Para el caso de la palota, dibujamos un rectángulo con la esquina superior izquierda deplazada $5$ pixels a la derecha y hacia arriba, y luego indicamos un ancho y alto de 11 pixels, lo quenos da un rectangulo de $11\times 11$ pixels, donde la coordenadas x y y apuntan justo al centro del mismo.<jupyter_code>pygame.init()
try:
    pygame.display.set_caption("Starfield")
    screen = pygame.display.set_mode(SIZE, 0, 24)

    # Parte de inicialización del juego
    ball = Ball(WIDTH//2, HEIGHT//2)
    clock = pygame.time.Clock()    
    in_game = True

    while in_game:
        # Obtener datos de entrada
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                in_game = False
        # Recalcular el estado del juego, en base al estado actual y a las entradas
        ball.update()
        # Representamos el nuevo estado
        screen.fill(BLACK)
        ball.draw(screen)
        pygame.display.update()
        clock.tick(FPS)
finally:
    pygame.quit()<jupyter_output><empty_output><jupyter_text>Si ejecutamos el código anterior, vemos que la pelota está inmovil en el centro. **Ejercicio**: Hacer que la pelota se desplace. Lo único que hay que haceres tocar el método `update` de la clase Ball.**Ejercicio**: La pelota va muy lenta. ¿Cómo podemos hacer para que vaya más rápida?**Ejercicio**: La pelota se sale por los bordes de la pantalla. ¿Cómo podemos hace para que, en vez de desaparecer, aparezca por el borde contrario? ¿Y, si en vez de eso, queremos que rebote?  Vectores y álgebra lineal Si hemos resuelto el problema anterior, habremos usado dos valores para indicar la velocidad en el eje x y en el eje y. De la misma manera, hemos usado dos valores conbinados para indicar laposicion de la pelota: la coordenada x y la y.    los matemáticos tienen un nombre para eestos casos en que combinamos dos o más números, sellaman **vectores**. Y es uno de esos conceptos matemáticos que se usan muchísimoen el desarrollo de juegos. El área de las matemáticas que estudia los vectores y otrosconceptos relacionados se llama [Álgebra lineal](https://es.wikipedia.org/wiki/%C3%81lgebra_lineal). Los vectores se pueden usar, como en el caso anterio, para representar distintostipos de datos. Por ejemplo, podemos usar un vector para representar un punto o unaposicion en el espacio (En cuyo caso se habla normalmente de punto, parahacer entender que estamos interesados sobre todo en una posición física). Perotambién se pueden usar para otras cosas, como por ejemplo, para indicar la velocidadde un objeto, un cuyo caso es habitual hablar de vectores (vector de movimiento, vectorde acceleración, etc... pygame no define su propia clase de vectores, pero es muy fácil crear nuestra propia clase. Vamos a crear una clase `Vector2` (El $2$ indica que en este casoson vectores de dos dimensiones, más adelante definiremos una clase `Vector3` para trabajar en tres dimensiones, añadiendo una tercera coordenada $z$). Esta clase nos permitereferirnos a las coordenadas de forma cómoda con los atributos `x``e `y`:<jupyter_code>class Vector2:
    
    def __init__(self, x=0, y=0):
        self.x = x
        self.y = y
        
    def __str__(self):
        return 'Vector2({:.3f}, {:3f})'.format(self.x,self.y)
        
v = Vector2(3, 4)
assert v.x == 3
assert v.y == 4<jupyter_output><empty_output><jupyter_text>Además del constructor, con su nombre _mágico_ especial, `__init__`, hemos definido un nuevo método màgico, en este caso llamado `__str__`. Este método será llamada cadavez que sea necesario convertir un objeto de la clase a una cadena de texto, por ejemplo, para imprimirlo con la función `print`:<jupyter_code>v = Vector2(3, 4)
print(v)<jupyter_output>Vector2(3.000, 4.000000)<jupyter_text>**Ejercicio**: Comentar el método `__str__` y, luego de ejecutar la celda para quese compile la nueva versión, llamar a print otra vez a ver que sale. Una de las formas más habituales de crear un vector es a partir de dos puntos (es decir, apartir de otros dos puntos o vectores). Por ejemplo, si tenemos la posición del personajede nuestro jugador, y tambien tenemos la posicion de un tesoro, puede que nos interes obtener el vector que apunta, partiendo del personaje, hacia el tesoro, como se ve en el siguiente diagrama: ![Mapa del tesoro](./art/mapa-del-tesoro.svg)<jupyter_code>def vector_from_points(v1, v2):
    return Vector2( v2.x - v1.x, v2.y - v1.y)
v = vector_from_points(Vector2(1.2, 3.1), Vector2(3.6, 1.3))
print(v)<jupyter_output>Vector2(2.400, -1.800000)<jupyter_text>Este vector representa la distancia y la orientacion del movimiento quetendría que hacer el jugador para llegar al tesoro.  Módulo o magnitud de un vectorUn concepto importante asociado a los vectores es el de **magnitud** o **módulo** del vector, quees simplemente la distancia entre dos puntos. En el caso anterior, si quisieramossaber cuantos kilómetros (o pixels) tiene que moverse nuestro personaje, esa informaciónnos la da el módulo. Podemos definir nuestro propio método para poder calcular el módulo deun vector, se trata solo de implementar la función de la [distancia euclidea](https://es.wikipedia.org/wiki/Distancia_euclidiana), es decirla raiz cuadrada de la suma de los cuadrados de las coordenadas $x$ e $y$:<jupyter_code>import math

class Vector2:
    
    def __init__(self, x=0, y=0):
        self.x = x
        self.y = y
        
    def __str__(self):
        return 'Vector2({:.3f}, {:3f})'.format(self.x,self.y)
    
    def mod(self):
        return math.sqrt(self.x**2 + self.y**2)
        
v = Vector2(3, 4)
print(v.mod())<jupyter_output>5.0<jupyter_text>Vectores unitarios Los vectores definen dos cosas: una distancia (módulo o magnitudd) y una dirección. Normalmenteesta información están implicitas en el propio vector, pero en determinados casos podemos querertrabajar con esos dos conceptos por separado. Para la magnitud o modulo solo necesitamosun número, pero para indicar la direccion usaremos lo que se llama un **vector unitario** queapunte en la misma direccion que el vector original. Un vector unitario es, simplemente, un vector cuyo módulo es $1$.La operación de obtener un vector unitario a partir de un vector cualquiera se denomina **normalización**.Podemos obtener un vector unitario a partir de un vector normal dividiendo cada una de las coordenadas del vector por su módulo. Implementemos un método `normalize` que haga exactamente esto:<jupyter_code>import math

class Vector2:
    
    def __init__(self, x=0, y=0):
        self.x = x
        self.y = y
        
    def __str__(self):
        return 'Vector2({:.3f}, {:3f})'.format(self.x,self.y)
    
    def mod(self):
        return math.sqrt(self.x**2 + self.y**2)
    
    def normalize(self):
        d = self.mod()
        return Vector2(self.x / d, self.y / d)
v = Vector2(90, 90)
print(v.normalize())<jupyter_output>Vector2(0.707, 0.707107)<jupyter_text>Todo vector unitario tiene módulo $1$<jupyter_code>v = Vector2(90, 90).normalize().mod() == 1<jupyter_output><empty_output><jupyter_text>Suma de vectoresLa suma de vectores toma dos argumentos de tipo Vector y produce un nuevovector resultante, que viene a ser el resultado de combinar los dos. Por ejemplo,si me quiero mover 5 pixels a la derecha y 4 pixel arriba (Vector2(5, -4)) y luegome quiero mover 2 pixeles a la izquierda y 2 pixeles arriba (Vector2(-2, -2)), mis coordenadasfinales serán 3, -6, es cedir que:        Vector2(5, -4) + Vector2(-2, -2)        Debería ser igual a:        Vector2(3, -6)    Podemos hacer un método o una función `add` para sumar vectores:<jupyter_code>def  add(v1, v2):
    return Vector2(v1.x + v2.x, v1.y + v2.y)

v1 = Vector2(5, -4)
v2 = Vector2(-2, -2)
v3 = add(v1, v2)
print(v3)<jupyter_output>Vector2(3.000, -6.000000)<jupyter_text>pero Python nos proporciona un sistema mucho mejor, llamado _sobrecarga de operadores_, por el cual podemos definir como se comportan nuestro objetos ante determinadas operaciones, como por ejemplo, la suma. Para ello, usa una serie de nombres mágicos (como `__init__` y `__str__`,ya vistos). En elcaso de la suma, debemos implemenar un ḿetodo que se debe llamar `__add__`.Veamos como implementar esto en nuestra clase Vector2:<jupyter_code>import math

class Vector2:
    
    def __init__(self, x=0, y=0):
        self.x = x
        self.y = y
        
    def __str__(self):
        return 'Vector2({:.3f}, {:3f})'.format(self.x,self.y)
    
    def mod(self):
        return math.sqrt(self.x**2 + self.y**2)
    
    def normalize(self):
        d = self.mod()
        return Vector2(self.x / d, self.y / d)
    
    def __add__(self, other):
        return Vector2(self.x+other.x, self.y+other.y)<jupyter_output><empty_output><jupyter_text>Y ahora podemos sumar vectores de una forma mucho más legible y cómoda:<jupyter_code>v1 = Vector2(5, -4)
v2 = Vector2(-2, -2)
v3 = v1 + v2
print(v3)<jupyter_output>Vector2(3.000, -6.000000)
{'fraction_non_alphanumeric': 0.08497556361343213, 'fraction_numerical': 0.024436386567870092, 'mean_word_length': 4.101326899879373, 'pattern_counts': {'":': 0, '<': 57, '<?xml version=': 0, '>': 58, 'https://': 6, 'lorem ipsum': 0, 'www.': 1, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '15', 'dir': 'jupyter-structured-clean-dedup', 'id': '156305', 'license': 'MIT', 'n_tokens_mistral': 4525, 'n_tokens_neox': 4365, 'n_words': 1620, 'path': 'pong.ipynb', 'repo_name': 'euribates/Jupyter-Intro', 'size': '12686'}