sábado, 6 de abril de 2019

Creando videojuegos en Rust lang (Parte 13)

Programa 7. "pre_hello_ggez7" El equivalente a un "switch" en Rust: "match"
Si queremos dirigir el flujo de nuestro programa usando "enums" lo podemos hacer usando "match". En Rust "match" es muy parecido a un "switch" en C. Simplemente enlistas todos los casos a manejar y pones "=>" y una coma "," al final de ese caso. De hecho como los enums pueden ser "structs" o "tuple structs" podemos asignaros a variables ("tuple structs") o usar los nombres directamente ("structs").

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
enum Direccion {
    Avanzar,
    Detenido(char),
    Click { x: i64, y: i64 }
}

fn main() {
    let direccion_jugador1 = Direccion::Avanzar;
    match direccion_jugador1 {
        Direccion::Avanzar => println!("¡Avanzar!"),
        Direccion::Detenido(input_char) => println!("¡{}!", input_char),
        Direccion::Click { x, y } => println!("¡x={} y y={}!", x, y)
    }
    let direccion_jugador2 = Direccion::Click {x: 6, y: 3};
    match direccion_jugador2 {
        Direccion::Avanzar => println!("¡Avanzar!"),
        Direccion::Detenido(input_char) => println!("¡{}!", input_char),
        Direccion::Click { x, y } => println!("¡x={} y y={}!", x, y)
    }
    let direccion_jugador3 = Direccion::Detenido('p');
    match direccion_jugador3 {
        Direccion::Avanzar => println!("¡Avanzar!"),
        Direccion::Detenido(input_char) => println!("¡{}!", input_char),
        Direccion::Click { x, y } => println!("¡x={} y y={}!", x, y)
    }
    println!("¡Fin del juego!");
}

El resultado de correr el código es el siguiente:
También hay funciones más avanzadas de "match". Podemos usar "|" para apuntar múltiples condiciones a un sólo grupo de código "a | b | c =>", así podemos replicar el comportamiento de "switch" en C/C++. También podemos usar "_" para manejar el resto de los casos similar a "default" en C/C++. Es decir podemos usar " _ => " para manejar los casos que no caen en los mencionados directamente.

Navegación:
Primera parte
Siguiente parte
Parte anterior

Fuente:

Creando videojuegos en Rust lang (Parte 12)


Programa 6. "pre_hello_ggez6" Enumeraciones: "enums"
Ahora haremos un pequeño programa para ver como funcionan las enumeraciones ("enums"). Las enumeraciones son útiles en videojuegos porque pueden enumerar estados de objectos (dirección 2D: "Norte", "Sur", "Este" u "Oeste") o estados del juego ("Corriendo" o en "Pausa"). De hecho también podemos definir "structs" o incluso "tuple structs" para los enums para guardar datos sobre el estado o definir como se guardarán los estados (coordenadas del ratón) o otros tipos de datos (entes grabados con enteros). Para los enums y sus valores usamos "Carmel case".

En este ejemplo asignaremos algunos enums que usan structs vacios (como etiquetas), tuple structs y structs con valores y luego los imprimiremos. Similar a los structs tenemos que ponerles a todos los enums "#[derive(Debug)]" para poderlos imprimirlos con "{:?}".

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
#[derive(Debug)]
enum Direccion {
    Norte,
    Sur,
    Este,
    Oeste
}

#[derive(Debug)]
enum Raton {
    ClickDerecho { x: i64, y: i64 },
    ClickIzquierdo { x: i64, y: i64 }
}

#[derive(Debug)]
enum Entes {
    Jugador(i32),
    Mounstro(i32)
}

#[derive(Debug)]
enum Estado {
  Corriendo(char),
  Pausa(char)
}

fn main() {
    let direccion_jugador1 = Direccion::Este;
    let raton_jugador1 = Raton::ClickDerecho {x: 1, y: 2};
    let ente_jugador1 = Entes::Jugador(1);
    let estado_jugador1 = Estado::Corriendo('c');
    println!("J1 ¡Dirección {:?}!", direccion_jugador1);
    println!("J1 ¡Raton {:?}!", raton_jugador1);
    println!("J1 ¡Ente {:?}!", ente_jugador1);
    println!("J1 ¡Estado {:?}!", estado_jugador1);
    println!();

    let direccion_jugador2 = Direccion::Norte;
    let raton_jugador2 = Raton::ClickIzquierdo {x: 6, y: 3};
    let ente_jugador2 = Entes::Jugador(2);
    let estado_jugador2 = Estado::Pausa('p');
    println!("J2 ¡Dirección {:?}!", direccion_jugador2);
    println!("J2 ¡Raton {:?}!", raton_jugador2);
    println!("J2 ¡Ente {:?}!", ente_jugador2);
    println!("J2 ¡Estado {:?}!", estado_jugador2);
    println!();

    let direccion_jugador3 = Direccion::Sur;
    let raton_jugador3 = Raton::ClickDerecho {x: 8, y: 4};
    let ente_jugador3 = Entes::Jugador(3);
    let estado_jugador3 = Estado::Corriendo('c');
    println!("J3 ¡Dirección {:?}!", direccion_jugador3);
    println!("J3 ¡Raton {:?}!", raton_jugador3);
    println!("J3 ¡Ente {:?}!", ente_jugador3);
    println!("J3 ¡Estado {:?}!", estado_jugador3);
    println!();

    let direccion_mounstro1 = Direccion::Oeste;
    let ente_mounstro1 = Entes::Mounstro(1);
    println!("Mounstro1 ¡Dirección {:?}!", direccion_mounstro1);
    println!("Mounstro1 ¡Ente {:?}!", ente_mounstro1);
    println!();

    println!("¡Fin del juego!");
}

Y el resultado de correr el programa anterior es el siguiente:


Navegación:
Primera parte
Siguiente parte
Parte anterior

Fuente (inglés):

viernes, 5 de abril de 2019

Creando videojuegos en Rust lang (Parte 11)

Programa 5. "pre_hello_ggez5" Tuplas y structs: "tuple structs"
En algunas situaciones queremos usar estructuras de datos ordenados, pero queremos hacer referencias sin usar nombres. Para estas situaciones tenemos los "tuple structs" que comparten características de "tuples" y también características de "structs".
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
#[derive(Debug)]
struct Punto2d(i32, i32);

#[derive(Debug)]
struct Punto3d(i32, i32, i32);

fn main() {
    let punto1 = Punto2d(1, 2);
    println!("Coordenadas x={} y y={}", punto1.0, punto1.1);
    println!("{:?}", punto1);
    let punto2 = Punto3d(-1, 3, 4);
    println!("Coordenadas x={}, y={} y z={}", punto2.0, punto2.1, punto2.2);
    println!("{:?}", punto2);
}


Como se puede observar podemos llamar los elementos usando números (".0", ".1", ".2", etc.). Funciona con varios y diversos tipos, similar a las tuplas y structs. Esto ayuda a hacer el código más legible para grupos de datos comunes de los juegos como valores de colores (RGB y alpha) o coordenadas y vectores como vemos en el ejemplo.

Navegación:
Primera parte
Siguiente parte
Parte anterior

jueves, 4 de abril de 2019

Creando videojuegos en Rust lang (Parte 10)

Programa 4. "pre_hello_ggez4" Creando estructuras de datos: "structs"
Las estructuras de datos o los "structs" son formas de agrupar diferentes tipos de datos bajo un mismo nombre para poder procesarlos usando etiquetas. Son importantes en los videojuegos porque los structs se pueden guardar en la memoria estática ("stack") al contrario de la memoria dinámica ("heap"). Usar memoria estática es a veces deseable para mejorar el desempeño del programa. Un struct puede contener diferentes tipos de datos por lo cual es muy versátil en su uso.

En este código usamos un struct para guardar el estado de nuestro juego con información de Identificador (ID) del jugador, el nombre del jugador, la posición (x,y) del jugador y un "char" de identificación, ponemos valores iniciales e imprimimos los valores:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
struct EstadoDelJuego {
    id_jugador: u32,
    nombre_jugador: String,
    x: i32,
    y: i32,
    id_char: char
}

fn main() {
    let mi_juego = EstadoDelJuego{id_jugador: 1, 
        nombre_jugador: String::from("Jugador 1"), 
        x: 10, y: 5, id_char: 'a'};
    println!("Valor id_jugador: {}", mi_juego.id_jugador);
    println!("Valor nombre_jugador: {}", mi_juego.nombre_jugador);
    println!("Valor x: {}", mi_juego.x);
    println!("Valor y: {}", mi_juego.y);
    println!("Valor id_char: {}", mi_juego.id_char);
}

El resultado de correr el programa es el siguiente:

Vemos como para imprimirlo tuvimos que utilizar un punto (".") para llamar por la etiqueta el valor que queremos de nuestro estado del juego (ejemplo, "mi_juego.x"). Esto es porque no podemos imprimir el "struct" directamente. Nos marca un error si intentamos correr el siguiente código:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
struct EstadoDelJuego {
    id_jugador: u32,
    nombre_jugador: String,
    x: i32,
    y: i32,
    id_char: char
}

fn main() {
    let mi_juego = EstadoDelJuego{id_jugador: 1, 
        nombre_jugador: String::from("Jugador 1"), 
        x: 10, y: 5, id_char: 'a'};
    println!("mi_juego: {}", mi_juego);
}


Es decir que no podemos imprimirlo a menos que le quede claro a Rust como imprimirlo. Una opción es heredar las impresiones internas. Es decir, si todos los componentes dentro del struct tienen una forma de imprimirse en modo debug entonces podemos activar este modo especial para el struct. Para hacer esto seguimos la indicación del mensaje de error. Si intentamos usar "{:?}" ahora nos dice lo siguiente:

Como se puede ver en la imagen nos da dos opciones agregar "#[derive(Debug)]" o implementar manualmente "std::fmt:Debug". En este ejemplo sólo veremos la primera opción y más adelante veremos la segunda opción. Es simplemente cuestión de agregar #[derive(Debug)] antes de la definición de nuestro struct. También podemos usar "{:#?}" para que lo imprima en un formato más amigable. El resultado es el siguiente:


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
#[derive(Debug)]
struct EstadoDelJuego {
    id_jugador: u32,
    nombre_jugador: String,
    x: i32,
    y: i32,
    id_char: char
}

fn main() {
    let mi_juego = EstadoDelJuego{id_jugador: 1, 
        nombre_jugador: String::from("Jugador 1"), 
        x: 10, y: 5, id_char: 'a'};
    println!("mi_juego: {:?}", mi_juego);
    println!("mi_juego: {:#?}", mi_juego);
}


De esta forma podemos ver el struct impreso como si lo viéramos en el código. La otra opción que mencionó el compilador de implementar manualmente "std::fmt:Debug" lo veremos cuando entremos al tema de "traits" e implementación ("impl").

Navegación:
Primera parte
Siguiente parte
Parte anterior

miércoles, 3 de abril de 2019

Creando videojuegos en Rust lang (parte 9)

Programa 3. "pre_hello_ggez3" Un grupo vacío "( )" o la unidad: "unit"
En Rust un tipo de dato primitivo importante agregando con fines de completar el patrón de las tuplas es una tupla vacía. La tupla vacía es básicamente un grupo vacío. También es llamado la unidad ("unit"). Es un tipo de dato que lo escribimos como dos paréntesis juntos, uno abriendo y otro cerrando: "()". Este tipo de dato sólo admite un sólo valor, el valor se expresa de la misma forma: "()". La ventaja de este tipo de dato y este valor es que no ocupa espacio en la memoria y por lo tanto se puede utilizar todos los que queramos sin impactar la memoria del programa.

Este es un código de ejemplo donde declaramos una variable del tipo () con el valor () y lo imprimimos usando la opción de impresión debug ("{:?}"):

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
fn main() {
    let mi_unidad = ();
    println!("mi_unidad = {:?}", mi_unidad);
    let tupla_de_unidades = ((), ());
    println!("tupla_de_unidades = {:?}", tupla_de_unidades);
    let (x, y) = tupla_de_unidades;
    println!("x = {:?}", x);
    println!("y = {:?}", y);
    println!("¡Fin del ejemplo!");
}

Este es el resultado de correr el código:

Este valor es útil de la misma forma que "void" es útil en C, puede usarse para indicar "nada" o tener una función que regresa nada, es decir una función que ejecutamos por lo que realiza pero no para obtener una salida. De la misma forma podemos usar "()" para indicar que ignoraremos un valor o indicarle a los que leen el código que siempre regresaremos el mismo valor vacío y que nunca va a variar a raíz de los datos de entrada. Más delante veremos ejemplos de estas funciones y además de otros ejemplos de uso que he visto en los ejemplos de ggez. Pero es importante tener en claro este concepto de unidad ya que al leer el código de los demás nos enfrentaremos con estos paréntesis sin nada adentro.

Navegación:
Primera parte
Siguiente parte
Parte anterior

Fuentes (inglés):

martes, 2 de abril de 2019

Creando videojuegos en Rust lang (parte 8)

Programa 2. "pre_hello_ggez2" Definiendo grupos de datos: "touples"
En Rust un tipo de dato primitivo son conjuntos de datos ordenados o tuplas. Llamados "touples" en inglés. Ejemplos son las coordenadas en el plano cartesiano (x,y) donde tiene sentido agrupar estos números y el orden es importante (2,3) significa "x = 2" y "y = 3".

Si intentamos correr al siguiente código que intenta imprimir tuplas en la línea 3 y en la línea 12 nos marca un error de compilación:


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
fn main() {
 let mi_tupla = (1, 4);
 println!("mi_tupla = {}", mi_tupla);
        let (x1, y1) = mi_tupla; // Asignando mi_tupla a variables separadas
 println!("x = {}", x1);
 println!("y = {}", y1);
        let (x2, y2) = (2, 3); // Asignando una tupla a variables
 println!("x = {}", x2);
 println!("y = {}", y2);
 // Una tupla puede contener diferentes tipos de datos
 let mi_tupla2 = (-1i32, 6u32, "hola", '@');
 println!("mi_tupla2 = {}", mi_tupla2);
        println!("¡Fin del ejemplo!");
}



Esto es porque de forma predetermina no hace suposiciones sobre el formato de impresión. Más adelante veremos como implementar un formato de impresión. Por ahora nos limitaremos a mostrar dos opciones que están a veces disponibles. Opción 1 es cambiar "{}" por "{:?}" que le pide al compilador que intente hacer una impresión rápida de debug es decir muy cercano a como se ve el código. Opción 2 es similar a la opción uno, consiste en cambiar "{}" por "{:#?}" que es igual una opción de impresión debug pero más bonita (ejemplo, usa saltos de línea). En este caso son equivalentes, así que si corremos el siguiente código obtenemos lo que esperaríamos:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
fn main() {
 let mi_tupla = (1, 4);
 println!("mi_tupla = {:?}", mi_tupla);
        let (x1, y1) = mi_tupla; // Asignando mi_tupla a variables separadas
 println!("x = {}", x1);
 println!("y = {}", y1);
        let (x2, y2) = (2, 3); // Asignando una tupla a variables
 println!("x = {}", x2);
 println!("y = {}", y2);
 // Una tupla puede contener diferentes tipos de datos
 let mi_tupla2 = (-1i32, 6u32, "hola", '@');
 println!("mi_tupla2 = {:#?}", mi_tupla2);
        println!("¡Fin del ejemplo!");
}

Después de usar "{:?}" o "{:#?}" en lugar de "{}"
Claro que si queremos guardan los datos de las tuplas necesitamos que las variables que usamos sean del mismo tipo que el tipo de dato contenido en esa posición del a tupla.

Navegación:
Primera parte
Siguiente parte
Parte anterior

Fuente (inglés):

lunes, 1 de abril de 2019

Creando videojuegos en Rust lang (parte 7)

Programa 1. "pre_hello_ggez1" Advertencias por variables no utilizadas
Si ejecutamos el siguiente código:

1
2
3
4
fn main() {
    let var1 = 100i32;
    println!("¡Fin del juego!");
}

Obtenemos un mensaje de advertencia porque declaramos una variable que no utilizamos:

El programa sí se ejecuta, es sólo una advertencia. Para evitar esta advertencia podemos agregar "_" antes de nuestra variable. Esta sugerencia también es mostrada en la advertencia diciendo que "consideremos usar '_var1' en vez de 'var1'". Es decir si ejecutamos el código siguiente no obtendremos la advertencia:

1
2
3
4
fn main() {
    let _var1 = 100i32;
    println!("¡Fin del juego!");
}


Con lo anterior podemos manejar estos errores si nos aparecen. En en el ejemplo "Hello ggez" de Github compilan en código en partes por lo que les aparece este error varias veces en un inicio. Siguiendo este ejemplo y en medida que llenan su ejemplo los errores van desapareciendo.

Navegación:
Primera parte
Siguiente parte
Parte anterior

Fuente (inglés):