Weblogs Código

Blog Bitix

Formas de generar un número aleatorio en un rango con Java

julio 22, 2021 05:30

En Java hay varias formas de generar números aleatorios, la clase Random permite generar números aleatorios individuales y desde Java 8 con la adición de streams permite obtener una secuencia de números aleatorios de tamaño determinado o indefinido. La clase Math también permite generar números aleatorios aunque es más recomendable usar la clase Random. Finalmente, en caso de querer un identificativo único universal está la clase UUID que genera números aleatorios de 128 bits que se representan mediante caracteres alfanuméricos.

Java

Las computadoras hacen el mejor esfuerzo para ser capaces de generar números aleatorios, para ello hacen uso de la entropía de que disponen para obtener aleatoriedad como datos de entrada que recibe por dispositivos de teclado, ratón o red. Generar números aleatorios es útil en ciertas funcionalidades de programación como la criptografía pero también útil en tareas más sencillas como seleccionar un elemento de un array de forma aleatoria u obtener un número aleatorio entre dos cifras.

Todos los lenguajes de programación ofrecen funciones de soporte para generar números aleatorios, el lenguaje Java también puede hacerse de varias formas.

Contenido del artículo

Generar números aleatorios en un rango

Java ofrece varias clases y formas para generar números aleatorios, dependiendo de cada una la forma de generar un número aleatorio u obtener un número aleatorio en un rango varía ligeramente.

Con la clase Random

La clase Random permite generar números aleatorios con varios métodos según el tipo de datos deseado, en el caso de querer números enteros del tipo int con el método nextInt que devuelve números enteros uniformemente distribuidos entre 0 de forma inclusiva y el límite superior indicado de forma exclusiva.

Dada la especificación del método nextInt si se desea un número aleatorio entre un rango distinto que no empiece en el 0 hay que realizar una pequeña operación matemática.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
package io.github.picodotdev.blogbitix.javarandom;

...

public class RandomUtil {

    public static int getInt(int min, int max) {
        return new Random().nextInt(max - min + 1) + min;
    }

    ...
}
RandomUtil-random.java
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
package io.github.picodotdev.blogbitix.javarandom;

...

public class Main {

    public static void main(String[] args) {
        System.out.println("Random");
        String randomNumbers = IntStream.rangeClosed(1, 10)
                .mapToObj(i -> Integer.toString(RandomUtil.getInt(0, 10)))
                .collect(Collectors.joining(", "));
        System.out.printf("Numbers: %s%n", randomNumbers);

        ...
    }
}
Main-random.java
1
2
Random
Numbers: 10, 7, 4, 6, 9, 6, 8, 8, 8, 9
RandomUtil-random.out

Usando un stream

En el caso de desear una secuencia de números aleatorios la clase Random ofrece soporte para obtener un stream en Java 8 de enteros que son números aleatorios.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
package io.github.picodotdev.blogbitix.javarandom;

...

public class RandomUtil {

    ...

    public static IntStream getIntStream(int min, int max) {
        return new Random().ints(min, max + 1);
    }

    public static IntStream getIntStream(int min, int max, int size) {
        return new Random().ints(size, min, max + 1);
    }

    ...
}
RandomUtil-stream.java
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
package io.github.picodotdev.blogbitix.javarandom;

...

public class Main {

    public static void main(String[] args) {
        ...

        System.out.println("\nStream");
        String streamNumbers = RandomUtil.getIntStream(1, 10, 10)
                .mapToObj(i -> Integer.toString(i))
                .collect(Collectors.joining(", "));
        System.out.printf("Numbers: %s%n", streamNumbers);

        ...
    }
}
Main-stream.java
1
2
Stream
Numbers: 10, 10, 4, 1, 8, 2, 7, 8, 3, 5
RandomUtil-stream.out

Con la clase Math

Es más eficiente usar la clase Random pero otra forma posible de generar números aleatorios es con la clase Math. El método random de Math devuelve números aleatorios del tipo double entre 0 de forma inclusiva y 1 de forma exclusiva. Para obtener el número aleatorio hay que hacer una multiplicación y conversión a entero.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
package io.github.picodotdev.blogbitix.javarandom;

...

public class RandomUtil {

    ...

    public static int getIntMath(int min, int max) {
        return (int)(Math.random() * ((max - min) + 1)) + min;
    }

    ...
}
RandomUtil-math.java
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
package io.github.picodotdev.blogbitix.javarandom;

...

public class Main {

    public static void main(String[] args) {
        ...

        System.out.println("\nMath");
        String mathNumbers = IntStream.rangeClosed(1, 10)
                .mapToObj(i -> Integer.toString(RandomUtil.getIntMath(0, 10)))
                .collect(Collectors.joining(", "));
        System.out.printf("Numbers: %s%n", mathNumbers);

        ...
    }
}
Main-math.java
1
2
Math
Numbers: 10, 3, 2, 7, 6, 0, 9, 5, 0, 4
RandomUtil-math.out

Generar un identificativo único universal

Si se desea generar un identificador único universal para una entidad en vez de un número aleatorio en un rango que tiene posibilidades de repetirse está la clase UUID que genera número únicos de 128 bits que se presentan con caracteres alfanuméricos.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
package io.github.picodotdev.blogbitix.javarandom;

...

public class RandomUtil {

    ...

    public static UUID getUUID() {
        return UUID UUID.randomUUID();
    }
}
RandomUtil-uuid.java
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
package io.github.picodotdev.blogbitix.javarandom;

...

public class Main {

    public static void main(String[] args) {
        ...

        System.out.println("\nUUID");
        String uuidNumbers = IntStream.rangeClosed(1, 10)
                .mapToObj(i -> RandomUtil.getUUID().toString())
                .collect(Collectors.joining(", "));
        System.out.printf("Numbers: %s%n", uuidNumbers);
    }
}
Main-uuid.java
1
2
UUID
Numbers: 1a42ebd3-0c80-4b45-bc88-b774a43e3758, 5a3b0fe4-8101-48cb-aa97-21e1fc2a3169, 9a58541e-58b9-4743-9f48-639e2d8f1dcf, 108e3db7-210b-4ad9-80b2-6838ebcb9109, ec023062-a7a5-4f59-9385-3ac929c6d28c, 612205dc-9bf7-44a3-9573-d8277aafcefa, 508801be-a5cb-4d9e-b1bc-be7c772ddc9a, cdde20d2-7521-417a-a881-1e20acdfecd7, 2ef8aaf7-647e-45c8-b573-eac65cbfd777, cf4d72a4-aac0-4218-944c-000673fe65bc
RandomUtil-uuid.out
Terminal

El código fuente completo del ejemplo puedes descargarlo del repositorio de ejemplos de Blog Bitix alojado en GitHub y probarlo en tu equipo ejecutando siguiente comando:
./gradlew run

» Leer más, comentarios, etc...

Blog Bitix

Los niveles de madurez REST, ejemplo con HATEOAS y documentación con Swagger de un servicio con Spring Boot

julio 15, 2021 05:00

Los niveles de madurez de una API implementada con las convenciones REST trata de aplicar los conceptos y semántica de la web y el protocolo HTTP a un servicio web. Muchas APIs que dicen ser REST no cumplen con todos los niveles de madurez para ser considerada RESTful que incluyen HATEOAS para crear enlaces entre los recursos y HAL para codificar los datos. Muchas se quedan en el nivel 2 al hacer uso únicamente de recursos y verbos, llegar a cumplir el nivel 3 para incluir controles hypermedia tiene algunas ventajas adicionales. Spring Boot proporciona soporte para crear una API que soporte el nivel de madurez 3 de REST y Springdoc permite generar la documentación de la API con Swagger.

Java

Spring

Utilizar REST para implementar un servicio es muy común por ser fácil de construir y consumir, se ha convertido en un estándar para los servicios web. REST proporciona acciones para las operaciones, cacheo, redirecciones y delegación, seguridad tanto para cifrado como para autenticación, compatibilidad hacia atrás y evolución de las APIs, escalabilidad y servicios sin estado. A pesar de su amplio uso en realidad no define ninguna especificación, es simplemente una aproximación, estilo y restricciones para construir servicios escalables basados en la web.

Cómo alternativa a los servicios REST están gRPC y GraphQL que también son capaces de utilizar como medio de transporte el protocolo HTTP pero se basan en aproximaciones diferentes.

Utilizar el protocolo HTTP no es suficiente para que en servicio se considere que implementa REST de forma completa, al implementar un servicio basado en la semántica del protocolo HTTP y la web hay varios niveles de madurez. Spring Boot ofrece soporte para implementar servicios web que cumplan con todos los niveles de madurez de REST y Springdoc crear la documentación a partir de las anotaciones de Swagger.

Contenido del artículo

Los niveles de madurez REST

REST se basa en los mismos estándares que se utilizan para las páginas web, estos son el protocolo HTTP y los hiperenlaces que construyen la web. El protocolo HTTP tiene una semántica para cada una de sus operaciones que incluyen las diferentes operaciones básicas de CRUD (crear, leer, actualizar y eliminar), códigos de estado para el resultado de la operación y direcciones de los recursos. Las páginas web devuelven HTML, los servicios REST como formato de datos suelen emplear JSON. Los servicios REST son la aplicación de los mismos conceptos de la web a integración de servicios para computadoras, en vez de a humanos o navegadores web.

Los niveles de madurez de REST son la aplicación de la semántica del protocolo HTTP y la web a los servicios web. Cada uno de estos niveles incluye una aplicación del protocolo HTTP y la web que el servicio REST debe seguir.

Muchos servicios que se denominan REST no cumplen con todos los niveles de madurez de REST, no es suficiente utilizar HTTP como transporte, utilizar URLs bonitas para los recursos y usar verbos HTTP. No son pocos los servicios que se denominan como REST pero que no implementan todos los niveles de madurez.

Nivel 0, transporte HTTP

En este nivel simplemente se usa HTTP como medio de transporte para hacer llamadas remotas sin usar la semántica de la web. Cada petición tiene su propia dirección de endpoint, estas URLs puede que sigan algunas convenciones como utilizar guiones medios para mejorar legibilidad de las URLs, preferiblemente letras en minúsculas y sin extensiones en las URLs, un endpoint puede devolver los datos en el formato solicitado según la cabecera Accept de modo que la extensión es redundante o no es necesaria.

En este nivel de madurez las URLs suelen incluir verbos que es una mala práctica, como en los siguientes ejemplos.

1
2
3
/addMessage
/deleteMessage
/getMessage
rest-0.txt

Nivel 1, recursos

Los recursos son una parte fundamental del protocolo HTTP, cada recurso tiene su propia dirección web, endpoint o URL. Normalmente en una aplicación los modelos corresponden con su propio recurso junto a su propio  endpoint o URL.

En este nivel se aplican varias convenciones como las URLs no incluyen una / al final de la dirección, una / representa una relación jerárquica entre diferentes recursos, es posible usar singular o plural para los nombres según se prefiera pero de forma consistente.

Los endpoints en este nivel de madurez son de la siguiente forma.

1
2
/messsage

rest-1.txt

Nivel 2, verbos

Las operaciones que se realizan sobre los recursos son las operaciones de creación, obtención, actualización y eliminación o CRUD. Usando los diferentes verbos del protocolo HTTP es posible asignar a cada uno de ellos las diferentes operaciones básicas de manipulación de datos.

Si se quiere obtener un elemento concreto de un recurso se realiza una petición al recurso con el verbo GET indicando el identificativo del modelo, si se quieren obtener todos los elementos del recurso se realiza una petición con el verbo GET sin especificar ningún identificativo, si se quiere crear un nuevo elemento en el recurso se utilizar el verbo POST, si se quiere modificar el verbo PUT y si se quiere eliminar el verbo DELETE.

  • POST: verbo utiliza para realizar operaciones de creación sobre un recurso.
  • GET: verbo utiliza para obtener un elemento de la colección o varios elementos de la colección.
  • PUT: verbo utilizado para realizar operaciones de modificación.
  • DELETE: verbo utilizado para realizar operaciones de eliminación.

Las cabeceras que son parte del protocolo HTTP son metadatos utilizados con diferentes propósitos como indicar en qué formato se quieren los datos en la respuesta o añadir seguridad.

Los parámetros de las URLs son otra parte del protocolo HTTP que permiten proporcionar argumentos y datos en la propia URL después del símbolo ? en vez de como datos en el cuerpo de la petición. Los parámetros de las consultas son utilizados con diferentes propósitos como especificar los criterios de una búsqueda o propiedades de los datos que se desean como paginación, filtrado u ordenación.

Otra parte del protocolo HTTP con los códigos de estado, los códigos de estado HTTP son un número que indica el resultado de la operación. Estos son varios de los códigos de estado más comunes:

  • 200: la operación se ha procesado correctamente.
  • 201, CREATED: un nuevo recurso ha sido creado.
  • 204, NO CONTENT: el recurso ha sido eliminado, no se devuelven datos en el cuerpo de la respuesta.
  • 304, NOT MODIFIED: los datos retornados no han cambiado y provienen de una caché.
  • 400, BAD REQUEST: la respuesta es inválida y no puede ser procesada. La descripción del mensaje de error puede ser devuelta en lo datos retornados.
  • 401, UNAUTHORIZED: acceder o manipular el recurso requiere autenticación.
  • 403, FORBIDDEN: el servidor entiende la petición pero las credenciales proporcionadas no permiten el acceso.
  • 404, NOT FOUND: el recurso de la URL no existe.
  • 500, INTERNAL SERVER ERROR: se ha producido un error interno al procesar la petición por un fallo de programación. En la respuesta no se siempre se devuelve una descripción del error, sin embargo en las trazas del servidor debería haber información detallada del error.

Tanto para enviar datos como obtener datos el formato utilizado es JSON por ser un formato de texto plano y manipulable desde JavaScript en un navegador web.

Aunque hasta este nivel puede ser suficiente para implementar un servicio y proporcionar la funcionalidad, no es suficiente para considerarlo RESTful, es necesario el siguiente nivel de madurez con los controles hypermedia.

Nivel 3, controles hypermedia

Este nivel se divide en dos aspectos, negociación de contenido y descubrimiento de enlaces del recurso. Este es el nivel al que muchas implementaciones de servicios REST no implementan por mayor sencillez aún sin las ventajas que proporcionan los controles hypermedia o por los problemas de los controles hypermedia que si son ignorados ni utilizados no proporcionan ninguna de sus ventajas.

La negociación del contenido permite al cliente especificar el formato de los datos en los que quiere el resultado. Se solicita con la cabecera Accept en la petición. Por ejemplo, un cliente del servicio REST que desee los datos en formato JSON debe proporcionar una cabecera Accept: application/json y si los desea en formato XML una cabecera Accept: application/xml. En caso de enviar datos en el cuerpo de la petición el formato de los datos proporcionados se especifica con la cabecera Content-Type. En caso de que el servicio no soporte el tipo de datos proporcionado o no sea capaz de proporcionar en el formato solicitado devuelve el código de estado 415 que indica formato de tipo de datos no soportado.

La web es una colección de páginas web relacionadas a través de enlaces. HATEOAS es el principio que aplica enlaces en los datos de las entidades que permite navegar entre ellas y descubrir las acciones disponibles, un cliente de un servicio REST que implemente HATEOAS no necesita conocer las URLs para interaccionar con las diferentes acciones, estas son devueltas en los datos de la respuesta como metadatos.

Para obtener los enlaces que ofrece el recurso es necesario hacer una petición y obtener datos, esto es un problema ya que si el cliente ha de conocer de antemano los enlaces o hacer una petición para obtenerlos se anulan parte de las ventajas de HATEOAS, el cliente ha de harcodearlos en su código. Esta acción index permite obtener todos los enlaces que se ofrece en el recurso que el cliente puede utilizar.

1
2
#!/usr/bin/env bash
curl -v http://localhost:8080/message/index
curl-get-index.sh
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
{
    "_links": {
      "self": {
        "href": "http://localhost:8080/message/index"
      },
      "getAll": {
        "href": "http://localhost:8080/message"
      },
      "getById": {
        "href": "http://localhost:8080/message/{id}",
        "templated": true
      },
      "add": {
        "href": "http://localhost:8080/message"
      },
      "deleteById": {
        "href": "http://localhost:8080/message/{id}",
        "templated": true
      }
    }
  }
curl-get-index.json

Al realizar la siguiente llamada al servicio del ejemplo cuando se devuelve una entidad Message el JSON de sus datos incluye una propiedad _links con los enlaces de sus acciones, en este caso realizar la operación de eliminar. La propiedad links es un array de enlaces que tienen la URL y un nombre o identificativo asociado.

1
2
#!/usr/bin/env bash
curl -v http://localhost:8080/message/1
curl-get.sh
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
{
  "id": 1,
  "text": "Hello World!",
  "_links": {
    "self": {
      "href": "http://localhost:8080/message/1"
    },
    "deleteById": {
      "href": "http://localhost:8080/message/1"
    }
  }
}
curl-get.json

Con HATEOAS en vez de que los clientes construyen las URLs de los recursos para hacer peticiones las obtienen de los datos de la respuesta, al mismo tiempo en la respuesta se especifica las acciones posibles de modo que el cliente no necesita implementar lógica para determinar si una operación es posible. La aplicación tampoco necesita construir URLs con interpolación de cadenas para incluir el identificativo de una entidad, el enlace completo es devuelto en los datos. Esto permite a los clientes no tener que implementarlo reduciendo el riesgo de que la lógica de operaciones posibles del servidor y el cliente quede desincronizadas.

HAL es un formato de tipos de datos que permite codificar no sólo datos sino también controles hypermedia, indicando a los consumidores otras partes de la API a las que llamar. El enlace self indica al propio recurso, el enlace root indica el recurso de la colección, los enlaces add y delete indican dos operaciones posibles.

Ventajas y problemas de HATEOAS

Al cambiar la estructura de las URLs se rompe la compatibilidad de la API con versiones anteriores, uno de los beneficios de HATEOAS es que si la estructura de la URL de la API puede cambiar sin afectar a los clientes al describir estos las URLs de forma dinámica.

Los enlaces devueltos proporcionan al cliente la lista de operaciones que es posible llamar según el estado de la aplicación o la entidad. Esto es útil para los desarrolladores de los clientes dado que no han de duplicar lógica de cuando es posible realizar una operación. En los casos de varias operaciones encadenadas realizadas en varios pasos con HATEOAS la API guía a los clientes hacia el siguiente paso en el flujo proporcionando únicamente los enlaces que son relevantes según el estado de la aplicación.

La documentación de la API sigue siendo requerida para describir la semántica de cada enlace junto con información como la estructura de los tipos y tipo de contenido.

En la parte negativa está que HATEOAS añade complejidad a la API, que afecta tanto al desarrollador de la API como al consumidor de la misma. Hay que realizar un trabajo adicional para añadir los enlaces apropiados en cada respuesta según el estado de la entidad. Esto provoca que la API sea más compleja de construir que una API que no implementa HATEOAS.

Los clientes de la API también tienen complejidad añadida para entender la semántica de cada enlace además de tener y procesar cada respuesta para obtener los enlaces. Los beneficios pueden compensar esta complejidad añadida pero hay que tenerla en cuenta.

Si la API es pública seguramente algún cliente la use de forma que la usa incorrectamente sin usar el hypermedia, haciendo a HATEOAS inútil.

Ejemplo de recurso REST con HATEOAS y ejemplo de código

En el artículo Cómo documentar una API REST con Swagger implementada con Spring Boot incluía como ejemplo un servicio REST que únicamente implementa hasta el nivel de madurez 2 de REST, esta es la revisión del servicio para implementar hasta el nivel 3 incluyendo hypermedia con HATEOAS y HAL.

Spring HATEOAS proporciona métodos y clases para incluir los enlaces de hypermedia de las entidades que se devuelven como resultado en el servicio. La clase RepresentationModel es una clase base que incluye métodos para añadir los controles hpermedia, la clase EntityModel es utilizada cuando el resultado es para una única entidad, CollectionModel cuando el resultado es una colección de entidades y PagedModel cuando el resultado es paginado.

Este es la implementación de servicio REST de ejemplo que trata mensajes, permite obtener una lista de mensajes, crear nuevos y eliminar además de una acción para descubrir todos los enlaces del recurso. Para crear los enlaces de hypermedia de HAL que se devuelven en el JSON como respuesta del servicio se delegan en una clase RepresentationModelAssembler.

 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
66
67
68
69
70
71
72
73
74
package io.github.picodotdev.blogbitix.springresthateoas;

...

@RestController
@ExposesResourceFor(Message.class)
public class MessageController implements MessageApi {

    private MessageModelAssembler assembler;
    private Map<Long, Message> messages;

    public MessageController(MessageModelAssembler assembler) {
        this.assembler = assembler;

        this.messages = new HashMap<>();
        this.messages.put(1l, new Message(1l, "Hello World!"));
        this.messages.put(2l, new Message(2l, "Welcome to Blog Bitix!"));
    }

    @Override
    public ResponseEntity<CollectionModel<EntityModel<Message>>> index() {
        try {
            Collection<Link> links = List.of(
                Link.of(linkTo(MessageController.class).toUriComponentsBuilder().path(MessageApi.class.getMethod("index").getAnnotation(GetMapping.class).value()[0]).build().toUriString()).withSelfRel(),
                Link.of(linkTo(MessageController.class).toUriComponentsBuilder().path(MessageApi.class.getMethod("getAll").getAnnotation(GetMapping.class).value()[0]).build().toUriString()).withRel("getAll"),
                Link.of(linkTo(MessageController.class).toUriComponentsBuilder().path(MessageApi.class.getMethod("getById", Long.class).getAnnotation(GetMapping.class).value()[0]).build().toUriString()).withRel("getById"),
                Link.of(linkTo(MessageController.class).toUriComponentsBuilder().path(MessageApi.class.getMethod("add", Message.class).getAnnotation(PostMapping.class).value()[0]).build().toUriString()).withRel("add"),
                Link.of(linkTo(MessageController.class).toUriComponentsBuilder().path(MessageApi.class.getMethod("deleteById", Long.class).getAnnotation(DeleteMapping.class).value()[0]).build().toUriString()).withRel("deleteById")
            );
            return ResponseEntity.ok(CollectionModel.empty(links));
        } catch (NoSuchMethodException e) {
            throw new RuntimeException(e.getMessage(), e);
        }
    }

    @Override
    public ResponseEntity<CollectionModel<EntityModel<Message>>> getAll() {
        List<Message> entities = messages.entrySet().stream().map(e -> e.getValue()).collect(Collectors.toList());
        return ResponseEntity.ok(assembler.toCollectionModel(entities));
    }

    @Override
    public ResponseEntity<EntityModel<Message>> getById(Long id) {
        if (!exists(id)) {
            throw new ResponseStatusException(HttpStatus.NOT_FOUND, "Message not found");
        }
        return ResponseEntity.ok(assembler.toModel(messages.get(id)));
    }

    @Override
    public ResponseEntity<Void> add(Message message) {
        if (exists(message.getId())) {
            throw new ResponseStatusException(HttpStatus.CONFLICT, "Already exists");
        }
        if (message.isBlank()) {
            throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "Invalid data");
        }
        messages.put(message.getId(), message);
        return ResponseEntity.ok().build();
    }

    @Override
    public ResponseEntity<Void> deleteById(Long id) {
        if (!exists(id)) {
            throw new ResponseStatusException(HttpStatus.NOT_FOUND, "Message not found");
        }
        messages.remove(id);
        return ResponseEntity.ok().build();
    }

    private boolean exists(Long id) {
        return messages.containsKey(id);
    }
}
MessageController.java

Estos son dos comandos de curl para realizar una petición y obtener datos de una colección de entidades.

1
2
#!/usr/bin/env bash
curl -v http://localhost:8080/message
curl-get-all.sh
 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
{
  "_embedded": {
    "messages": [
      {
        "id": 1,
        "text": "Hello World!",
        "_links": {
          "self": {
            "href": "http://localhost:8080/message/1"
          },
          "deleteById": {
            "href": "http://localhost:8080/message/1"
          }
        }
      },
      {
        "id": 2,
        "text": "Welcome to Blog Bitix!",
        "_links": {
          "self": {
            "href": "http://localhost:8080/message/2"
          },
          "deleteById": {
            "href": "http://localhost:8080/message/2"
          }
        }
      }
    ]
  },
  "_links": {
    "self": {
      "href": "http://localhost:8080/message/"
    },
    "add": {
      "href": "http://localhost:8080/message"
    }
  }
}
curl-get-all.json

Los enlaces de hypermedia siguiendo la especificación HAL incluidos en el JSON es posible incluirlos directamente con la clase EntityModel, sin embargo, si la misma entidad es devuelta por varios endpoints para no duplicar código es posible delegar la creación de la representación del modelo en una clase dedicada a esta tarea.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
package io.github.picodotdev.blogbitix.springresthateoas;

...

@Component
class MessageModelAssembler implements RepresentationModelAssembler<Message, EntityModel<Message>> {

    @Override
    public EntityModel<Message> toModel(Message message) {
        return EntityModel.of(message,
                linkTo(methodOn(MessageApi.class).getById(message.getId())).withSelfRel(),
                linkTo(methodOn(MessageApi.class).deleteById(message.getId())).withRel("deleteById"));
    }

    @Override
    public CollectionModel<EntityModel<Message>> toCollectionModel(Iterable<? extends Message> entities) {
        CollectionModel<EntityModel<Message>> model = RepresentationModelAssembler.super.toCollectionModel(entities);
        model.add(linkTo(methodOn(MessageController.class).getAll()).withSelfRel());
        model.add(Link.of(linkTo(MessageController.class).toUriComponentsBuilder().build().toUriString()).withRel("add"));
        return model;
    }
}
MessageModelAssembler.java

En caso de que la API esté detrás de un proxy los enlaces devueltos por las entidades han de ser adaptados, Spring proporciona un filtro que aplicado a la aplicación permite especificar con cabeceras los datos de las URLs.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
package io.github.picodotdev.blogbitix.springresthateoas;

...

@SpringBootApplication
public class Main {

    @Bean
    public ForwardedHeaderFilter forwardedHeaderFilter() {
        return new ForwardedHeaderFilter();
    }

    public static void main(String[] args) {
        SpringApplication.run(Main.class, args);
    }
}
Main.java
1
2
#!/usr/bin/env bash
curl -v -H "X-Forwarded-Host: picodotdev.github.io" http://localhost:8080/message/index
curl-get-index-proxy.sh
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
{
    "_links": {
      "self": {
        "href": "http://picodotdev.github.io/message/index"
      },
      "getAll": {
        "href": "http://picodotdev.github.io/message"
      },
      "getById": {
        "href": "http://picodotdev.github.io/message/{id}",
        "templated": true
      },
      "add": {
        "href": "http://picodotdev.github.io/message"
      },
      "deleteById": {
        "href": "http://picodotdev.github.io/message/{id}",
        "templated": true
      }
    }
  }
curl-get-index-proxy.json

Para usar las clases que ofrecen el soporte para HATEOAS es necesario incluir la dependencia de Spring Boot en el archivo de construcción de Gradle.

 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
plugins {
	id 'java'
	id 'application' 
	id 'org.springframework.boot' version '2.5.2'
	id 'com.github.johnrengelman.processes' version '0.5.0'
	id 'org.springdoc.openapi-gradle-plugin' version '1.3.2'
}

application {
	group = 'io.github.picodotdev.blogbitix.springresthateoas'
	version = '0.0.1-SNAPSHOT'
	sourceCompatibility = '11'
	mainClass = 'io.github.picodotdev.blogbitix.springresthateoas.Main'
}

repositories {
	mavenCentral()
}

dependencies {
	implementation platform('org.springframework.boot:spring-boot-dependencies:2.5.2')

	implementation 'org.springframework.boot:spring-boot-starter'
	implementation 'org.springframework.boot:spring-boot-starter-web'
	implementation 'org.springdoc:springdoc-openapi-webmvc-core:1.5.9'
	implementation 'org.springframework.boot:spring-boot-starter-hateoas'
	implementation 'org.springdoc:springdoc-openapi-ui:1.5.9'
	implementation 'org.springdoc:springdoc-openapi-hateoas:1.5.9'
}
build.gradle

Documentación con Swagger

Swagger permite documentar un servicio REST, también incluye soporte para documentar un servicio que cumpla con el principio de hypermedia HATEOAS. Swagger proporciona varias anotaciones que se incluyen en la interfaz del servicio, al procesarlas genera un esquema de la interfaz del servicio con OpenAPI a partir del cual genera la documentación que incluye los endpoints y argumentos, verbos, códigos de respuesta y datos de los modelos. Swagger también permite hacer llamadas a los servicios y obtener el comando curl para hacer la petición desde la línea de comandos.

La definición de la interfaz del servicio además de las anotaciones de Spring para el servicio REST incluye las anotaciones de Swagger para generar el esquema del servicio en http://localhost:8080/v3/api-docs y generar la documentación en formato HTML accesible en la dirección http://localhost:8080/swagger-ui.html.

 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
package io.github.picodotdev.blogbitix.springresthateoas;

...

@Tag(name = "message", description = "the message API")
@RequestMapping(value = "/message", produces = { "application/hal+json" })
public interface MessageApi {

	@Operation(summary = "Get resource links", description = "Returns resource links", responses = {
			@ApiResponse(responseCode = "200", description = "Successful operation",
				links = {
					@Link(name = "self", operationId = "self"), 
					@Link(name = "getAll", operationId = "getAll"), 
					@Link(name = "getById", operationId = "getById"), 
					@Link(name = "add", operationId = "add"), 
					@Link(name = "deleteById", operationId = "deleteById")
			})
	})
	@GetMapping(value = "/index")
	ResponseEntity<CollectionModel<EntityModel<Message>>> index();

	@Operation(summary = "Get all messages", description = "Returns all messages", responses = {
		@ApiResponse(responseCode = "200", description = "Successful operation",
				links = { @Link(name = "self", operationId = "self"), @Link(name = "add", operationId = "add") })
	})
	@GetMapping(value = "")
	ResponseEntity<CollectionModel<EntityModel<Message>>> getAll();

	@Operation(summary = "Get a message by id", description = "Return a message", responses = {
		@ApiResponse(responseCode = "200", description = "Successful operation",
				links = { @Link(name = "self", operationId = "self"), @Link(name = "deleteById", operationId = "deleteById") }),
		@ApiResponse(responseCode = "400", description = "Invalid id supplied"),
		@ApiResponse(responseCode = "404", description = "Message not found")
	})
	@GetMapping(value = "/{id}")
	ResponseEntity<EntityModel<Message>> getById(@Parameter(description = "Id of message to return", required = true) @PathVariable("id") Long id);

	@Operation(summary = "Adds a message", description = "Add a message")
	@ApiResponses(value = {
			@ApiResponse(responseCode = "200", description = "Successful operation"),
			@ApiResponse(responseCode = "400", description = "Invalid data"),
			@ApiResponse(responseCode = "409", description = "Already exists") })
	@PostMapping(value = "")
	ResponseEntity<Void> add(@Parameter(description = "Message to add", required = true) @RequestBody Message message);

	@Operation(summary = "Deletes a message by id", description = "Delete a message")
	@ApiResponses(value = {
			@ApiResponse(responseCode = "200", description = "Successful operation"),
			@ApiResponse(responseCode = "400", description = "Invalid id supplied"),
			@ApiResponse(responseCode = "404", description = "Message not found") })
	@DeleteMapping(value = "/{id}")
	ResponseEntity<Void> deleteById(@Parameter(description = "Id of message to delete", required = true) @PathVariable("id") Long id);
}
MessageApi.java

Esta es la documentación de Swagger.

Documentación de servicio REST con Swagger UI

Documentación de servicio REST con Swagger UI

Documentación de servicio REST con Swagger UI

» Leer más, comentarios, etc...

Variable not found

setTimeout(()=>vnf.resume(), 5184000000)

julio 15, 2021 06:05

Como es habitual por estas fechas, me complace (seriamente) informaros de que a partir de la semana  el blog quedará en "modo verano" y dejaré de publicar nuevos contenidos hasta ya entrado septiembre. 

Durante este periodo procuraré desconectar totalmente durante un par de semanas, y el resto del tiempo al menos poder bajar un poco el ritmo, a ver si puedo disfrutar un poco de esas cosas que dicen que existen fuera de las pantallas ;)

¡Nos vemos a la vuelta!

Playa
Playa de Costa Ballena, Rota (Cádiz). Imagen: Hotel Elba

Publicado en Variable not found.

» Leer más, comentarios, etc...

Variable not found

CRUD en Blazor usando el componente DataGrid de Syncfusion

julio 13, 2021 08:44

SyncfusionSemanas atrás echamos un vistazo por encima a Blazor UI Components, los componentes profesionales para Blazor de Syncfusion, y algún tiempo después revisamos más en detalle uno de los grandes protagonistas en el mundo de los componentes, los de creación de gráficas estadísticas o Charts.

Sin embargo, si hay un tipo de componentes que todos usamos de forma frecuente en nuestras aplicaciones profesionales son, sin duda alguna, los grids o rejillas de datos. Por esa razón, los componentes que de alguna forma simplifican su creación son probablemente los más populares en todas las suites profesionales o gratuitas, sea cual sea la tecnología en la que trabajemos.

En este post centraremos el foco en Blazor DataGrid, la propuesta de Syncfusion para crear interfaces de rejillas de datos de forma sencilla y eficaz para Blazor Server y WebAssembly.

Pero antes de empezar, recordad que, aunque se trata de un producto comercial, dispone de una licencia community bastante permisiva, que permite que en muchos casos podamos utilizar los componentes de forma totalmente gratuita.

Nota: lo que estás leyendo es un post patrocinado por Syncfusion, pero en ningún momento han revisado previamente o condicionado de alguna forma su contenido.

Blazor DataGrid

Syncfusion Datagrid

Blazor DataGrid es un completo componente, perteneciente a la familia Blazor Components de Syncfusion, destinado a crear interfaces de visualización y edición de datos en formato tabular.

Diseñado bajo el concepto mobile-first, proporciona funcionalidades de visualización, consulta y edición de datos, aportando un gran número de características que seguro cubren la mayoría de escenarios:

  • Enlace a datos locales (colecciones o listas en memoria, EF) o remotos (JSON, APIs, OData, WCF).
  • Ordenación, filtrado y paginación de datos.
  • Agrupación y agregación de columnas.
  • Edición de registros en línea, dentro del propio grid, en cuadros de diálogo o en modo bulk/batch.
  • Selección de filas o columnas.
  • Soporte para rejillas maestro-detalle.
  • Personalización visual mediante temas y plantillas custom.
  • Exportación de datos a formatos frecuentes (Excel, CSV y PDF).

Y todo ello, utilizando técnicas de virtualización de filas y columnas que hacen posible trabajar con conjuntos de datos con miles o millones de filas sin despeinarse.

Veamos cómo utilizarlo.

Puesta en marcha

Como ya hemos visto en otros posts, los componentes de Syncfusion se distribuyen de forma individual a través de NuGet, por lo que sólo tenemos que añadir a nuestros proyectos los que utilicemos. En este caso, para utilizar la rejilla de datos debemos instalar el paquete Syncfusion.Blazor.Grid.

Pero como también hemos visto en artículos anteriores, antes de usar estos componentes debemos disponer de claves de licencia del producto. Podéis descargar la free trial, que os permitirá trastear con el componente durante 30 días, o bien, si sois desarrolladores individuales o empresas con menos de 5 empleados, registraros para recibir una licencia community, que os dará acceso a todos los componentes sin limitación, de forma totalmente gratuita.

Sea como sea, una vez registrados, desde el dashboard de Syncfusion podremos obtener una clave de licencia e introducirla en el código de inicialización de nuestra aplicación:

using Syncfusion.Licensing;
...
public class Program
{
public static void Main(string[] args)
{
SyncfusionLicenseProvider.RegisterLicense("YOUR-LICENSE-KEY-HERE");
...
}
}

También es importante añadir a la etiqueta <head> de la página contenedora de la aplicación (_Host.cshtml en Blazor Server o index.html en WebAssembly) la referencia a la hoja de estilos del tema visual elegido (hay cinco diferentes):

<head>
...
<link href="_content/Syncfusion.Blazor.Themes/bootstrap4.css" rel="stylesheet" />
</head>

También, como es habitual en estos casos, suele ser una buena idea insertar un @using en el archivo _Imports.razor de la raíz del proyecto para que los componentes estén disponibles en todos los archivos .razor de la aplicación:

@using Syncfusion.Blazor.Grids

Por último, debemos registrar los servicios de Syncfusion en el inyector de dependencias:

services.AddSyncfusionBlazor();

Hecho esto, lo tenemos todo listo para crear nuestra primera rejilla de datos, para lo que utilizaremos el componente <SfGrid> .

Mostrando datos con el componente <SfGrid>

Supongamos una fuente de datos bastante simple, como la siguiente:

public class Person
{
public int Id { get; set; }
public string Name { get; set; }
public DateTime BirthDate { get; set; }
public string FavoriteColor { get; set; }
}

public class DataServices
{
public static List<Person> GetAll()
{
var rnd = new Random();
var people = new List<Person>();
for (int i = 1; i < 101; i++)
{
people.Add(new Person() {
Id = i,
Name = $"Person {i}",
BirthDate = DateTime.Today.AddDays(-rnd.Next(1, 36500)),
FavoriteColor = ((KnownColor)rnd.Next(1, 150)).ToString()
});
}
return people;
}
}

La rejilla de Syncfusion admite distintos tipos de enlace a fuentes de datos. Aquí vamos a ver el más simple para que podáis entender fácilmente su funcionamiento, pero en la documentación oficial podéis consultar cómo utilizar otros orígenes como Entity Framework, SqlClient, Web API, OData y más.

Ahora, introduzcamos en una página nuestra primera instancia del grid, el componente <SfGrid>, simplemente indicándole la fuente de datos:

@page "/"

<h1>Grid demo</h1>

<SfGrid DataSource="@_data"></SfGrid>
@code
{
IEnumerable<Person> _data;
protected override void OnInitialized()
{
_data = DataServices.GetAll();
}
}

Como podemos ver en la siguiente captura, dado que no hemos configurado aún nada de la rejilla, <SfGrid> ha tomado varias decisiones por nosotros, mostrando una columna por cada propiedad pública del conjunto de datos:

Rejilla mostrando todos los datos en forma de tabla

Obviamente, a falta de más personalización, los datos no están paginados, las columnas usan como encabezado el nombre del campo, y en su contenido el formato por defecto de cada tipo de datos.

Podemos mejorar todo esto muy fácilmente. Por ejemplo, para activar la paginación basta con establecer el atributo AllowPaging de SfGrid a true, mientras que si queremos definir detalladamente las columnas podemos usar un elemento <GridColumns> y definir en su interior un <GridColumn> por cada columna a mostrar (por ejemplo, observad que ahora no queremos mostrar la columna "Id"):

<SfGrid DataSource="@_data" AllowPaging="true">
<GridColumns>
<GridColumn Field="@nameof(Person.Name)"/>
<GridColumn Field="@nameof(Person.BirthDate)"
HeaderText="Birthdate" Format="dd/MM/yyyy"/>
<GridColumn Field="@nameof(Person.FavoriteColor)"
HeaderText="Favorite color"/>
</GridColumns>
</SfGrid>

Como podemos ver, sólo con esto la cosa ya habrá mejorado bastante:

Rejilla con paginación y personalización de columnas

Esto pinta bien, así que continuemos explorando el componente. Mirando su extensa documentación, vemos que incluye capacidad de ordenar y filtrar por columnas, así que vamos a ello ;)

Para habilitar la ordenación simplemente hay que establecer a cierto el atributo AllowSorting de forma general, en el componente <SfGrid>, así como en todas las columnas que queremos que sean ordenables.

También podemos habilitar el filtrado usando el atributo AllowFiltering de la misma forma, y establecer algunas propiedades con <GridFilterSettings>. En el siguiente ejemplo, indicamos que deseamos filtrar mediante un menú, y que las búsquedas sean case insensitive:

<SfGrid DataSource="@_data" 
AllowPaging="true" AllowSorting="true" AllowFiltering="true">
<GridFilterSettings Type="FilterType.Menu" EnableCaseSensitivity="true" />
<GridColumns>
<GridColumn Field="@nameof(Person.Name)"
AllowSorting="true" AllowFiltering="true"/>
<GridColumn Field="@nameof(Person.BirthDate)"
HeaderText="Birthdate" Format="dd/MM/yyyy"
AllowSorting="true" AllowFiltering="true" />
<GridColumn Field="@nameof(Person.FavoriteColor)"
HeaderText="Favorite color"
AllowSorting="true" AllowFiltering="true"/>
</GridColumns>
</SfGrid>

Con estos simples cambios, ya tenemos una completa rejilla capaz de mostrar datos, ordenarlos y filtrarlos:

Rejilla con ordenación y filtros

¿Vamos a por el CRUD completo?

El componente grid de Syncfusion también es capaz de gestionar las operaciones de actualización. Aunque obviamente requiere algo más de trabajo de configuración del componente, el tiempo a dedicar a esto siempre será muy inferior al que supondría implementar una solución por nuestra cuenta.

Para conseguirlo, lo primero que debemos asegurar es que SfGrid conoce cuál es la columna que actúa como clave primaria de las filas. Para ello es necesario definir la columna, aunque sea invisible, con el atributo IsPrimaryKey de la siguiente forma:

...
<GridColumns>
<GridColumn Field="@nameof(Person.Id)" IsPrimaryKey="true" Visible="false" />
... resto de columnas
</GridColumns>

A continuación, vamos a configurar el componente para que habilite las operaciones de creación, actualización y eliminación de filas.

Para ello, en primer lugar, le añadiremos una barra de herramientas usando el atributo Toolbar de <SfGrid>. Esta toolbar es totalmente configurable, aunque de momento le incluiremos sólo comandos predefinidos específicamente para facilitar la manipulación de datos:

<SfGrid DataSource="@_data" 
AllowPaging="true" AllowSorting="true" AllowFiltering="true"
Toolbar="@(new List<string>() { "Add", "Edit", "Delete", "Cancel", "Update" })">
...

Aparte, dentro del componente <SfGrid> incluiremos también el componente <GridEditSettings> para configurar las opciones de edición. En este caso, como puede intuirse, permitiremos realizar todas las operaciones, la edición se realizará en un cuadro de diálogo flotante y mostraremos un diálogo de alerta antes de eliminar filas:

...
<GridEditSettings AllowAdding="true" AllowDeleting="true" AllowEditing="true"
ShowDeleteConfirmDialog="true" Mode="EditMode.Dialog" />

El código completo quedaría así:

<SfGrid DataSource="@_data" 
AllowPaging="true" AllowSorting="true" AllowFiltering="true"
Toolbar="@(new List<string>() { "Add", "Edit", "Delete", "Cancel", "Update" })">

<GridEditSettings AllowAdding="true" AllowDeleting="true" AllowEditing="true"
ShowDeleteConfirmDialog="true" Mode="EditMode.Dialog" />
<GridFilterSettings Type="FilterType.Menu" EnableCaseSensitivity="true" />
<GridColumns>
<GridColumn Field="@nameof(Person.Id)"
IsPrimaryKey="true" Visible="false" />
<GridColumn Field="@nameof(Person.Name)"
AllowSorting="true" AllowFiltering="true"/>
<GridColumn Field="BirthDate"
HeaderText="Birthdate" Format="dd/MM/yyyy"
AllowSorting="true" AllowFiltering="true"/>
<GridColumn Field="FavoriteColor"
HeaderText="Favorite color"
AllowSorting="true" AllowFiltering="true"/>
</GridColumns>
</SfGrid>

¡Y eso es todo! Con algo más de una decena de líneas tenemos en funcionamiento una rejilla completa con paginación, ordenación, filtros, altas, bajas y modificaciones.

Grid con altas, bajas y modificaciones

Obviamente, siempre la realidad será más compleja que esto y tendremos que sumergirnos en configuraciones más avanzadas del componente, pero como punto de partida no está nada mal, ¿verdad?

Como decíamos al principio, el componente Grid para Blazor de Syncfusion es una solución bastante completa, cuyas funcionalidades sobrepasan en mucho lo que podríamos abarcar en un post introductorio como este:

  • Enlace a prácticamente cualquier tipo de fuente de datos.
  • Uso de data annotations para especificar formatos, validaciones y otras características de las columnas.
  • Múltiples opciones de configuración de filas y columnas.
  • Uso de plantillas personalizas para definir exactamente cómo queremos mostrar los datos.
  • Mantenimiento del estado de grids (página actual, orden, filtros...), para conservarlo entre cambios de página.
  • Exportación e impresión de datos.
  • Barras de herramientas y menús contextuales personalizados.
  • Sistema de eventos para introducir lógica personalizada en múltiples puntos.
  • Copiar y pegar filas o celdas.
  • Selección individual o múltiple de filas.
  • Agrupaciones y agregaciones de datos.
De hecho, con lo que hemos visto aquí sólo hemos rascado levemente su superficie, pero os animo a que echéis un vistazo a su documentación y veáis sus muchas posibilidades.

Publicado en Variable not found.

» Leer más, comentarios, etc...

Blog Bitix

Cómo documentar una API REST con Swagger implementada con Spring Boot

julio 12, 2021 08:00

Una API REST no está obligada a publicar una definición de su API, sin embargo, para quien deba usar API es muy útil disponer de su documentación para usarla correctamente y descubrir de qué endpoints se compone, métodos HTTP, cuales son sus parámetros, el esquema de los cuerpos de la petición y de los resultados, los tipos de los datos y sus formatos, los códigos de retorno devueltos, las cabeceras y su autenticación. OpenAPI permite definir la interfaz de una aplicación de forma agnóstica de la tecnología y lenguaje en el que se implementa, por otro lado Swagger a partir de esa definición permite generar una interfaz HTML con su documentación. La librería Springdoc junto con Spring Boot permite generar tanto la especificación de la API como la documentación simplemente añadiendo una dependencia y varias anotaciones en la implementación de la API.

Java

Spring

Disponer de documentación es esencial para el desarrollo, también es el caso de tener que usar una API REST donde es necesario conocer que endpoints dispone la API, métodos HTTP, cuales son sus parámetros, el esquema de los cuerpos de la petición y de los resultados, los tipos de los datos y sus formatos, los códigos de retorno devueltos, las cabeceras y su autenticación.

GraphQL en sus especificaciones detallan además del protocolo define también una forma de exportar un esquema de la API y publicarlo junto con la misma que sirve como documentación. Una API REST que está basada más en convenciones y semántica del protocolo HTTP que en una especificación nada le obliga a proporcionar una especificación de la API. Aunque una API implemente HATEOAS e intente ser más autoexplicativa la documentación sigue siendo útil para explorar la API sin necesidad de realizar las peticiones.

No tener una especificación de la API es un inconveniente porque un cambio en la interfaz de la API puede provocar errores de compatibilidad, no tener su documentación para revisar la API dificulta su uso al implementar un cliente. No tener documentación es un inconveniente pero tener documentación no generada a partir del código fuente o de la especificación de la API también lo es porque la documentación corre el riesgo de no estar actualizada y sincronizada con la implementación en el código fuente. Además de quedar la documentación desactualizada respecto al código fuente requiere tiempo de mantenimiento que no se dedica a otras tareas.

Hay iniciativas y herramientas para suplir la carencia de las API REST de no obligar a proporcionar una especificación de la API REST y generar la documentación documentación a partir del código fuente. También es importante poder probar la API de forma sencilla, una de las formas más habituales de probar una API es que la documentación incluya el comando de la herramienta de línea de comandos curl por su sencillez ni requerimientos adicionales que tener el comando instalado en sistema para ejecutarlo.

Contenido del artículo

Documentación de un API con OpenAPI, Swagger y Springdoc

OpenAPI trata de proporcionar una especificación para definir esquemas de APIs agnósticas de la tecnología y la implementación de las APIs. Definida la interfaz de la API es posible crear un cliente o servidor que cumpla esa API. La definición de la API incluye sus endpoints, métodos HTTP, cuales son sus parámetros, el esquema de los cuerpos de la petición y de los resultados, los tipos de los datos y sus formatos, los códigos de retorno devueltos, las cabeceras y su autenticación.

Por otro lado las herramientas de Swagger permiten generar la documentación a partir de la especificación de la API y si se desea generar una implementación básica inicial de cliente y servidor para diferentes lenguajes de programación. La documentación de Swagger no solo incluye información sino que permite probar la API directamente desde la documentación u obtener el comando curl a ejecutar desde la línea de comandos.

En una aplicación que implementa una API REST con Spring Boot la librería Springdoc permite generar de forma automática la especificación de la API que implementa el código publicándose en un endpoint, esta librería también genera la documentación de Swagger de la API en otro endpoint.

Otra forma de obtener la especificación de la API es mediante el plugin para Gradle de springdoc o utilizar imagen de Docker de Swagger UI para crear un servidor que aloje la documentación. También es posible descargar la última versión de Swagger UI en el directorio dist, cambiar el archivo index.html y reemplazar la URL https://petstore.swagger.io/v2/swagger.json por la especificación de OpenAPI deseada.

1
2
./gradlew generateOpenApiDocs

gradlew-generateOpenApiDocs.sh

El documento en formato JSON incluye de la definición de la API, es un documento con el fin de ser utilizado por alguna herramienta como Swagger UI que en su caso genera la documentación en formato HTML.

 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
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
{
  "openapi": "3.0.1",
  "info": {
    "title": "OpenAPI definition",
    "version": "v0"
  },
  "servers": [
    {
      "url": "http://localhost:8080",
      "description": "Generated server url"
    }
  ],
  "tags": [
    {
      "name": "message",
      "description": "the message API"
    }
  ],
  "paths": {
    "/message": {
      "get": {
        "tags": [
          "message"
        ],
        "summary": "Get all messages",
        "description": "Returns all messages",
        "operationId": "getAll",
        "responses": {
          "200": {
            "description": "Successful operation",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Message"
                }
              }
            }
          }
        }
      },
      "put": {
        "tags": [
          "message"
        ],
        "summary": "Adds a message",
        "description": "Add a message",
        "operationId": "add",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/Message"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "Successful operation"
          },
          "400": {
            "description": "Invalid data"
          },
          "409": {
            "description": "Already exists"
          }
        }
      }
    },
    "...": {
    }
  },
  "components": {
    "schemas": {
      "Message": {
        "type": "object",
        "properties": {
          "id": {
            "type": "integer",
            "format": "int64"
          },
          "text": {
            "type": "string"
          }
        }
      }
    }
  }
}
api-docs.json

La documentación en formato HTML de Swagger tiene el siguiente aspecto con la que además de obtener información sobre la API es posible ejecutar sus operaciones y obtener el comando curl para ejecutarlo desde la linea de comandos.

Documentación de Swagger UI de una API REST

Documentación de Swagger UI de una API REST Documentación de Swagger UI de una API REST

Documentación de Swagger UI de una API REST

Ejemplo de documentación REST con Spring Boot y Swagger

El siguiente ejemplo de Spring Boot implementa una pequeña API REST con un endpoint y varios métodos HTTP, uno para obtener un mensaje, otro para añadir un mensaje y otro para eliminar un mensaje. La API se define en un interfaz con las anotaciones tanto de Spring para REST como las anotaciones de Swagger para la definición de la API y documentación que al iniciar la aplicación permite generar la definición en formato OpenAPI.

 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
package io.github.picodotdev.blogbitix.springrestswagger;

...

@Tag(name = "message", description = "the message API")
@RequestMapping("/message")
public interface MesssageApi {

	@Operation(summary = "Get all messages", description = "Returns all messages", responses = {
		@ApiResponse(responseCode = "200", description = "Successful operation", content = @Content(schema = @Schema(implementation = Message.class))) }
	)
	@GetMapping(value = "", produces = { "application/json" })
	ResponseEntity<List<Message>> getAll();

	@Operation(summary = "Get a message by id", description = "Return a message", responses = {
		@ApiResponse(responseCode = "200", description = "Successful operation", content = @Content(schema = @Schema(implementation = Message.class))),
		@ApiResponse(responseCode = "400", description = "Invalid id supplied"),
		@ApiResponse(responseCode = "404", description = "Message not found") }
	)
	@GetMapping(value = "/{id}", produces = { "application/json" })
	ResponseEntity<Message> getById(@Parameter(description = "Id of message to return", required = true) @PathVariable("id") Long id);

	@Operation(summary = "Adds a message", description = "Add a message", responses = {
		@ApiResponse(responseCode = "200", description = "Successful operation"),
		@ApiResponse(responseCode = "400", description = "Invalid data"),
		@ApiResponse(responseCode = "409", description = "Already exists") }
	)
	@PutMapping(value = "", produces = { "application/json" })
	ResponseEntity<Void> add(@Parameter(description = "Message to add", required = true) @RequestBody Message message);

	@Operation(summary = "Deletes a message by id", description = "Delete a message", responses = {
		@ApiResponse(responseCode = "200", description = "Successful operation"),
		@ApiResponse(responseCode = "400", description = "Invalid id supplied"),
		@ApiResponse(responseCode = "404", description = "Message not found") }
	)
	@DeleteMapping(value = "/{id}", produces = { "application/json" })
	ResponseEntity<Void> deleteById(@Parameter(description = "Id of message to delete", required = true) @PathVariable("id") Long id);
}
MessageApi.java

La implementación de la API simplemente guarda en un mapa los mensajes, en caso de que detecte una condición de error lanza una excepción con el código de estado definido en la API para la condición, en caso de que la operación sea correcta se ejecuta su funcionalidad y se devuelve el código de estado 200 y los datos solicitados en su caso.

 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
package io.github.picodotdev.blogbitix.springrestswagger;

...

@RestController
public class MessageController implements RestApi {

    private Map<Long, Message> messages;

    public RestApiController() {
        this.messages = new HashMap<>();
        this.messages.put(1l, new Message(1l, "Hello World!"));
        this.messages.put(2l, new Message(2l, "Welcome to Blog Bitix!"));
    }

    @Override
    public ResponseEntity<List<Message>> getAll() {
        List<Message> m = messages.entrySet().stream().map(e -> e.getValue()).collect(Collectors.toList());
        return ResponseEntity.ok(m);
    }

    @Override
    public ResponseEntity<Message> getById(Long id) {
        if (!exists(id)) {
            throw new ResponseStatusException(HttpStatus.NOT_FOUND, "Message not found");
        }
        return ResponseEntity.ok(messages.get(id));
    }

    @Override
    public ResponseEntity<Void> add(Message message) {
        if (exists(message.getId())) {
            throw new ResponseStatusException(HttpStatus.CONFLICT, "Already exists");
        }
        if (message.isBlank()) {
            throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "Invalid data");
        }
        messages.put(message.getId(), message);
        return ResponseEntity.ok().build();
    }

    @Override
    public ResponseEntity<Void> deleteById(Long id) {
        if (!exists(id)) {
            throw new ResponseStatusException(HttpStatus.NOT_FOUND, "Message not found");
        }
        messages.remove(id);
        return ResponseEntity.ok().build();
    }

    private boolean exists(Long id) {
        return messages.containsKey(id);
    }
}
MessageController.java

Con los siguientes comandos de curl es posible probar los diferentes métodos de la API.

1
2
#!/usr/bin/env bash
curl -v http://localhost:8080/message
curl-get-all.sh
1
2
#!/usr/bin/env bash
curl -v http://localhost:8080/message/1
curl-get.sh
1
2
#!/usr/bin/env bash
curl -v -X PUT http://localhost:8080/message -H "Content-Type: application/json" --data '{"id": 3, "text": "Darkest Dungeon is a good game"}'
curl-put.sh
1
2
#!/usr/bin/env bash
curl -v -X PUT http://localhost:8080/message -H "Content-Type: application/json" --data '{"id": 1, "text": "Darkest Dungeon is a good game"}'
curl-put-conflict.sh
1
2
#!/usr/bin/env bash
curl -v -X DELETE http://localhost:8080/message/1
curl-delete.sh

Este ejemplo es suficiente, pero no cumple con todos los niveles de madurez de REST, el ejemplo de este otro artículo se puede comparar con el de este para ver las diferencias y conocer las ventajas e inconvenientes de implementar HATEOAS y HAL en una API REST.

Con la aplicación iniciada en en la URL http://localhost:8080/v3/api-docs por defecto se exporta especificación de la API en formato OpenAPI, en la URL http://localhost:8080/swagger-ui.html por defecto está la documentación de la API de Swagger generada por Springdoc. Con solo añadir las dependencias de Springdoc a la herramienta de construcción, en este caso Gradle, Spring Boot hace disponibles ambos endpoints.

 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
plugins {
	id 'java'
	id 'application' 
	id 'org.springframework.boot' version '2.5.2'
	id 'com.github.johnrengelman.processes' version '0.5.0'
	id 'org.springdoc.openapi-gradle-plugin' version '1.3.2'
}

application {
	group = 'io.github.picodotdev.blogbitix.springrestswagger'
	version = '0.0.1-SNAPSHOT'
	sourceCompatibility = '11'
	mainClass = 'io.github.picodotdev.blogbitix.springrestswagger.Main'
}

repositories {
	mavenCentral()
}

dependencies {
	implementation platform('org.springframework.boot:spring-boot-dependencies:2.5.2')

	implementation 'org.springframework.boot:spring-boot-starter'
	implementation 'org.springframework.boot:spring-boot-starter-web'
	implementation 'org.springdoc:springdoc-openapi-webmvc-core:1.5.9'
	implementation 'org.springdoc:springdoc-openapi-ui:1.5.9'
}
build.gradle
Terminal

El código fuente completo del ejemplo puedes descargarlo del repositorio de ejemplos de Blog Bitix alojado en GitHub y probarlo en tu equipo ejecutando siguiente comando:
./gradlew run

» Leer más, comentarios, etc...

Variable not found

Enlaces interesantes 452

julio 12, 2021 06:05

Enlaces interesantes

Ahí van los enlaces recopilados durante la semana pasada. Espero que os resulten interesantes. :-)

Por si te lo perdiste...

.NET Core / .NET

ASP.NET Core / ASP.NET / Blazor

Azure / Cloud

Conceptos / Patrones / Buenas prácticas

    Machine learning / IA / Bots

    Web / HTML / CSS / Javascript

    Visual Studio / Complementos / Herramientas

    Xamarin

    Otros

    Publicado en Variable not found.

    » Leer más, comentarios, etc...

    Arragonán

    Píldora. Deshabilitar la comprobación del certificado SSL en Maven

    julio 09, 2021 12:00

    Hace cosa de un par de semanas que he empecé a colaborar con un nuevo equipo de uno de mis clientes. En su caso trabajan en un entorno Java y utilizan Maven para construir sus proyectos. Como tienen librerías internas publicadas en un Nexus, tuve que configurar mi setting.xml para poder tener acceso a ello, hasta ahí todo normal.

    Pero vez lanzando el primer mvn install me dio un error con el certificado SSL, por ser un certificado autofirmado. Así que tras buscar un poco por ahí, encontré que gracias al subproyecto Maven Wagon podemos decirle que ignore esas comprobaciones porque confiamos en el repository manager configurado:

    mvn install -Dmaven.wagon.http.ssl.insecure=true -Dmaven.wagon.http.ssl.allowall=true -Dmaven.wagon.http.ssl.ignore.validity.dates=true
    

    » Leer más, comentarios, etc...

    Blog Bitix

    Prueba y opinión sobre jugar mediante streaming

    julio 08, 2021 06:00

    Cada una de las empresas más importantes en el sector de los juegos ya tienen sus servicios de juego en la nube o mediante streaming, en unos años puede ser la opción preferente de muchos jugadores dejando a las consolas como hardware dedicado a comprar obsoletas. El principal requerimiento del juego mediante streaming es tener una buena conexión a internet que sea estable y con un ancho de banda superior de al menos 35 Mbps para la máxima calidad. Ahora que tengo una conexión con fibra óptica he probado uno de estos servicios, GeForce NOW, por la buena experiencia que he tenido me replanteo las opciones a futuro para un nueva computadora, no teniendo que ser un equipo de más de 700 € ni requerir una tarjeta gráfica dedicada, puede ser de nuevo un pequeño y compacto Intel NUC.

    Hace un tiempo escribí una opinión Sobre la PlayStation 4, PlayStation 5 y Google Stadia donde comentaba que para que triunfen los juegos en la nube o mediante streaming lo único que les falta es tener un catálogo amplio de juegos e incorporar las últimas novedades desde su lanzamiento.

    Aún sabiendo que la PlayStation 5 sería lanzada en poco más de un año al final me compré una PlayStation 4, ya que en ese momento era la opción más viable para mi, tenía ADSL como conexión a internet y uso GNU/Linux con un pequeño computador Intel NUC que no está destinado a juegos con su gráfica integrada poco potente.

    Con el cambio de conexión de ADSL a fibra en mi conexión a internet que tiene un ancho de banda muy superior hace posible la utilización de algunos servicios como vídeo por streaming y también los nuevos servicios de juego por streaming. Ahora he probado uno de ellos, la opción de GeForce NOW para experimentar esta forma de jugar que puede convertirse en el futuro de los juegos y hacer que la PlayStation 5 y las Xbox X/S sean las últimas consolas con hardware dedicado al finalizar su periodo de vida de entre 7 y 10 años, tiempo en el cual el juego el juego por streaming sea adoptada y la opción preferente de muchos jugadores por sus ventajas.

    Plataformas de juego por streaming

    Plataformas de juego por streaming

    Contenido del artículo

    Juegos mediante streaming

    El juego en la nube o por streaming consiste en vez de utilizar una computadora propia para ejecutar el juego hacerlo utilizando una computadora en la nube. Esto es muy diferente al modo de jugar empleado hasta el momento, ya sea con un ordenador o consola pero siempre disponiendo de este hardware dedicado a juegos.

    Con el juego en la nube los juegos se ejecutan en los servidores del servicio de juegos por streaming por lo que no requieren un equipo potente, cualquier ordenador o un portátil bastará incluso uno que no pase de los 200 €. El juego se ejecuta en la nube, los datos de entrada de teclado, ratón o mando se envía al servidor en la nube, se procesan y la imagen y sonido se envían al cliente como si se tratase de un vídeo.

    El juego mediante streaming cambia las reglas no solo por el hardware necesario sino por el modelo de comercialización de los juegos.

    Ventajas

    La principal ventaja del juego mediante streaming es que no requiere un hardware potente para jugar, los juegos son uno de los programas aún con su aspecto dedicado al ocio que requieren más capacidad de cómputo de un ordenador. Un juego requiere una potente tarjeta gráfica para poder jugar a las últimas novedades lanzadas al mercado, pasado un tiempo salen juegos más exigentes y requieren cambiarla por una nueva más potente. El procesador ha de estar acorde a la gráfica, es otra pieza de hardware a renovar pasado el tiempo. En definitiva una computadora dedicada a juegos requiere ser renovada al cabo de unos años, unos 7 años para poder seguir jugando o menos para los más exigentes.

    Un equipo gamer por sus componentes potentes son caros, un equipo gamer nuevo de gama media alta empieza a partir de los 700 € y puede llegar a precios de hasta 2000 €, teniendo en cuenta que que muchos componentes de una computadora antigua no es posible reutilizarlos. Un equipo consta principalmente de procesador, tarjeta gráfica, memoria RAM, placa base, fuente de alimentación y almacenamiento SSD, en un equipo nuevo pasados algunos años hay que renovarlos todos por compatibilidad y para que estén equilibrados.

    Una gráfica potente no es aprovechada con un procesador poco potente, de nada sirve una computadora potente con poca memoria RAM y al renovar la placa base que implementa nuevos estándares requiere renovar todos los demás componentes al mismo tiempo. Solo la tarjeta gráfica de gama media-alta cuesta entre 300 € y 1000 €, el procesador entre 150 € y 400 €, precios a los que hay que sumar el resto de componentes. Dado que en los juegos en la nube no es necesario este hardware no hay que pagar por adelantado este significativo coste, solo se paga por el servicio mientras se usa.

    Otra ventaja de los juegos en la nube, es que no requieren instalación ni actualizaciones con lo que no hay esperas en el momento disponible para jugar, no hay que esperar a que una actualización se descargue y se instale.

    Finalmente, es posible jugar en cualquier ubicación en la que haya disponible una conexión a internet suficiente con cualquier dispositivo que se posea.

    Requerimientos

    El principal requerimiento del juego mediante streaming es que requiere conexión a internet, preferiblemente de fibra que ofrece unos anchos de banda de entre 100 Mbps y 600 Mbps junto con una menor latencia que otras conexiones, en España son lo habitual en nuevas contrataciones y alta de instalaciones. No en todos los países hay este tipo de conexiones pero a medida que pase el tiempo la cobertura será mayor y llegará a más lugares y países.

    Aunque en este video se muestra el juego Cyberpunk 2077 jugando en Stadia con una conexión ADSL de 13 Mbits, la fibra será mejor por tener menor latencia y más ancho de banda que permitirá evitar artefactos, aún así el vídeo muestra que es perfectamente posible jugar con ADSL. La conexión ha de ser estable, en caso de que haya un corte en la conexión el juego es interrumpido ya que funciona a través de internet.

    El segundo requerimiento es un ordenador o un dispositivo compatible con la plataforma. En caso de un ordenador cualquier ordenador, portátil e incluso teléfono inteligente es suficiente, no necesita una tarjeta gráfica dedicada.

    El tercer requerimiento es una aplicación que haga de cliente para el servicio de streaming. Es un programa para el que hay clientes nativos para los sistemas operativos Windows y macOS, para los usuarios de GNU/Linux en vez de un cliente nativo es posible con un navegador Chrome o Chromium, otra opción es con un dispositivo Google Chromecast instalando la aplicación GeForce NOW de la tienda de Android o Stadia.

    Opinión

    Ahora que tengo fibra como conexión a internet he probado el servicio de juego en streaming de Nvidia GeForce NOW. La característica más destacada de GeForce NOW es que es un simplemente un servicio de juego, los juegos se importan de las colecciones de juegos comprados en Steam o Epic Games. Otra propiedad de GeForce NOW es que ofrece crear una cuenta con la que es posible jugar de forma gratuita durante periodos de una hora esperando en una cola con el resto de usuarios de la plataforma a que se libere un servidor. Los usuarios que pagan por el servicio tienen preferencia en la cola y se ofrece mejor calidad gráfica, su coste es de uno 10 €/mes o de 100 € pagando de forma anual.

    A diferencia de Stadia la lista de juegos compatibles con GeForce NOW es bastante amplia, entre las que se encuentran muchos juegos triple A. GeForce NOW no requiere insertar una tarjeta de crédito al crear la cuenta y está disponibles algunos juegos gratuitos y populares con los que probar el servicio. Otra diferencia de Stadia con GeForce NOW es que al comprar un juego en Stadia este solo se puede jugar en Stadia, GeForce NOW ofrece más libertad ya que el juego se compra en la plataforma que se desee y es posible jugarlo por streaming en GeForce NOW o en el PC si se desea en el futuro o dependiendo de la ocasión. Un punto bueno de Stadia es que ofrece dos modalidades, una por la que se compra el juego y se juega en la plataforma sin coste adicional por el servicio con la limitación de solo poder jugarlo a 1080p, que tampoco está mal, o Stadia Pro que es una suscripción pagando por el servicio que da acceso a todo su catálogo de juegos y permite jugarlos a 4K con mayor calidad.

    Aparte de su reducido catálogo de juegos comparado con otras plataformas pero que va creciendo poco a poco dos cosas no me han gustado de Stadia, una que al crear cuenta en Stadia no se puede hacer sin primero suscribirse a la modalidad Stadia Pro lo que requiere introducir una tarjeta de crédito, lo segundo es que sin completar el registro en Stadia no es posible consultar los precios de los juegos para poder comparar si son caros, tiene el mismo que en cualquier otro lugar y si ofrece descuentos de forma regular, esto ha hecho que aún no me haya decidido a probarlo.

    El modelo de comprar los juegos y poder jugarlos en Stadia cuando se tenga tiempo es adecuado si se es un jugador ocasional evitar pagar por la suscripción en periodos que no se use el servicio o de forma intermitente. Si se es un jugador que juega habitualmente varios días de la semana el modelo de suscripción da acceso a todo el catálogo de juegos sin tener que comprarlos individualmente. PlayStation Now permite acceder al catálogo de juegos exclusivos de la PS, Stadia tiene un servicio de suscripción que también da acceso a todo el catálogo de sus juegos y los que se añadan además de poder jugar en 4K o un servicio sin suscripción comprando los juegos sin incurrir en cuotas mensuales, GeForce NOW permite jugar a los juegos de la colección de Steam y Epic Games sin necesidad de un PC gamer y la opción de jugar mediante su capa gratuita.

    Algunos juegos mediante streaming son compatibles con teclado y ratón y otros es posible jugarlos con mando de las consolas PlayStation, Xbox o compatibles. Aún el juego por streaming no es la opción preferente de los jugadores que siguen comprando PC y consolas, sin embargo, las compañías ya están desarrollando sus servicios de juego, tanto Sony con su PlayStation Now y Microsoft con xCloud a las que se unirán otras tecnológicas con sus servicios como Nvidia con GeForce NOW, Google con Stadia y Amazon con Amazon Luna a falta de lo que haga Nintendo o Apple.

    Si hubiese tenido fibra antes de comprar la PlayStation 4 y hubiera probado GeForce NOW o Stadia posiblemente no hubiese comprado la consola. Sin embargo, esto hace que en el futuro descarte comprar un equipo gamer para jugar, en el futuro cuando el Intel NUC se me quede insuficiente seguiré comprando un equipo del mismo formato, si GeForce NOW, Stadia u otra ofrece estas mismas características e incorpora aún más novedades me plantearía comprar una suscripción anual, que me saldría más barato que el equipo gamer. Después de dos años de comprar el Intel NUC y evaluar que cambiaría me en el futuro me planteaba comprar un equipo lo más pequeño posible pero con gráfica dedicada para juegos, al ya tener fibra el juego por streaming es una nueva posibilidad muy buena que me hace replantear las opciones que evaluaba para jugar en el artículo.

    Haciendo cálculos un ordenador dedicado a juegos de gama media o alta tiene un coste de entre 700 a 1200 € pudiendo llegar a 2000 €, una consola entre 300 € y 500 €. Haciendo un cálculo tomando como referencia los precios de GeForce NOW, PlayStation Now y de Stadia Pro que son de 10 €/mes los precios del PC dan para entre 6 y 10 años pagando un servicio de juegos por streaming, en el caso de las consolas entre 2 y 4, en el caso de Stadia existe la posibilidad de pagar solo por los juegos comprados.

    Un punto para mí muy interesante es que por streaming es posible jugar con el dispositivo de entrada que se prefiera, pudiendo ser con teclado y ratón o con un mando compatible con el servicio. Los juegos de tipo de disparo, de estrategia y de rol resultan más cómodos jugarlos con teclado y ratón, en las consolas solo es posible jugar con mando. El hecho de que no necesite mucho hardware la plataforma de juegos por streaming es accesible desde cualquier ubicación en la que haya una conexión a internet, aspecto que algunas personas les resultará interesante si viajan o en los periodos de vacaciones.

    Por otro lado, aun con el gran trabajo de Steam de permitir ejecutar juegos en GNU/Linux que solo están para Windows el streaming evita los problemas de los controladores gráficos e incluso para los usuarios de FreeBSD u otros BSD que son aún más minoritarios y tienen peor soporte de controladores gráficos que en GNU/Linux el streaming es una opción que da acceso a jugar sin ningún tipos de problema de compatibilidad y rendimiento.

    Prueba del servicio GeForce NOW

    El primer paso para probar GeForce NOW es crear una cuenta de NVidia que únicamente requiere una dirección de correo electrónico, la contraseñas de la cuenta, verificar la cuenta de correo electrónico y elegir un apodo para la cuenta, edad e idioma preferido, no hace falta más información personal.

    El siguiente paso es añadir a la biblioteca de GeForce NOW los juegos adquiridos en Steam, Epic Games y GOG, algunos otros que no están en estas tiendas también están disponibles e integrar las cuentas de Steam y Epic Store en GeForce NOW.

    Juegos de Nvidia GeForce NOW Juegos de Nvidia GeForce NOW

    Juegos de Nvidia GeForce NOW

    Después es necesario descargar la aplicación cliente de GeForce NOW adecuada para el sistema operativo e instalarlo o usar el navegador Chrome o Chromium en cualquier sistema operativo también en GNU/Linux. Finalmente, lanzar el cliente, esperar en la cola de espera unos minutos en función del número de usuarios conectados para jugar durante una hora. Para evitar esperas y jugar a mayor calidad está la suscripción con un precio de 10 €/mes.

    Cola de espera en Nvidia GeForce NOW

    Cola de espera en Nvidia GeForce NOW

    Si el juego está en nuestra colección de juegos de Steam o Epic Games y está soportado por GeForce NOW, es posible jugarlo en GeForce NOW. Dado que la Epic Games está regalando cada semana uno o dos juegos completamente gratuitos algunos de muy buena calidad como Control, Metro 2033 Redux, Frostpunk, City Skylines o Tropico 5. Por si fuera poco hay algunos juegos gratuitos compatibles con GeForce NOW como World of Warships, League of Legends, Path of Exile, Albion Online, Counter-Strike: Global Offensive y muchos más. Utilizando esta combinación de GeForce NOW con juegos regalados por Epic Store he probado este servicio de juego por streaming.

    Estas son algunas capturas de pantalla para observar su calidad. Es algo inferior a lo que es una computadora local por tener la imagen algo de compresión pero tampoco nada exagerado y con más calidad que en muchos vídeos de YouTube.

    Control Control Control

    Metro 2033 Metro 2033 Metro 2033

    World of Warships World of Warships World of Warships

    Trópico 5 Trópico 5

    Juegos Control, Metro 2033, World of Warships, Trópico 5

    En cuanto a retardo de respuesta a las acciones de teclado y ratón apenas se nota, la respuesta es casi inmediata y en muchos juegos no es necesario tener la menor latencia. La experiencia de juego es mejor con una computadora propia pero para los no tan exigentes el servicio de juego por streaming es suficiente.

    Finalmente este es un vídeo del inicio del juego World of Warships y Metro 2033.

    El juego en la nube o por streaming cambia las reglas de juego.

    » Leer más, comentarios, etc...

    Picando Código

    Enlaces Ruby – Junio 2021

    julio 06, 2021 11:30

    RubyColección de los enlaces interesantes sobre Ruby que encontré durante el mes de junio:

    Noticias sobre el lenguaje y programación Ruby general:

    💎 En este post Kingsley Silas explica el camino que usa Ruby para encontrar métodos: Ruby usa una “forma” o “patrón” definidos para determinar el método correcto a llamar y el momento correcto para devolver un error “no method erro”, y podemos llamar a esto el “Ruby Method Lookup Path”. En este tutorial, nos vamos a sumergir en la búsqueda de métodos de Ruby. Al final, vas a tener un buen entendimiento de cómo Ruby recorre la jerarquía de un objeto para determinar a qué método te estás refiriendo.

    💎 Takashi Kokubun escribió un artículo que titula Ruby 3 JIT can make Rails faster (o “la compilación just in time de Ruby 3 puede acelerar a Rails”). Las primeras pruebas con el JIT de Ruby 3.0 hacían a Rails más lento. Pero tras 3 años de investigación Takashi encontró cómo mejorar el rendimiento (compilando TODO), aunque todavía falta trabajo y nos avisa que estemos atentos a Ruby 3.1. Pero confirma que Ruby 3.0 es tres veces más rápido (que Ruby 2.0).

    💎 Kirk Haines – quien dio una charla que recomiendo ver en un reciente stream de The Ruby Galaxy – escribió Implementing a Ruby-like #send() in Crystal. En él explica cómo implementar un método send en Crystal usando macros y es un artículo bastante interesante tanto para Rubystas como para “Crystalistas”.

    💎 Benoit Daloze escribió un artículo sobre los instaladores de Ruby: rvm, ruby-build y ruby-install y los “cambiadores” de Ruby: rvm, rbenv y chruby. Analiza los pros y los contras de cada uno y termina concluyendo que RVM es la herramienta menos recomendada. Es la que uso en mi día a día, pero he usado varios otros antes (particularmente chruby, rbenv y asdf). De repente debería cambiar de nuevo y ver cómo se integra con Spacemacs…

    💎 Maxime Chevalier-Boisvert escribió un artículo súper interesante sobre YJIT: Yet Another Ruby JIT. Desde el año pasado ella trabaja en un equipo de Shopify encargado de mejorar el rendimiento del código Ruby -optimizando el intérprete CRuby y su garbage collector, la implementación de TruffleRuby- y ahora este nuevo compilador JIT dentro de CRuby. Como menciono, es súper interesante tanto el artículo como el proyecto en sí. Ya hay resultados bastante prometedores, y propone mejoras a CRuby que beneficiarían tanto al JIT actual de Ruby 3 (MJIT) como a YJIT y futuras implementaciones. Lectura recomendada – YJIT: Building a New JIT Compiler Inside CRuby. También recomiendo leer el hilo en reddit donde Maxime hizo un AMA espontáneo agregando todavía más detalles al tema.

    💎 Una discusión en reddit plantea que “Ruby y Rails no habían tenido nada como Shopify hasta ahora” en lo que se refiere a soporte empresarial. Si bien tienen equipos dedicados tanto a Ruby como a Rails, otras empresas como GitHub, Stripe y Heroku (que le paga el sueldo al mismísimo Matz) también vienen dándole mucho soporte a Ruby. Pero lo interesante es que en el hilo de Reddit comentaron Peter Zhu (Ruby committer que trabaja en Shopify) y Richard Schneeman (contribuye a Rails, mantiene Puma y Sprockets, trabaja en Heroku) entre otros. Del lado de Shopify, Peter Zhu comenta que los proyectos Ruby principales en Shopify actualmente son: YJIT (mencionado en el punto anterior) y Variable Width Allocation (VWA) para MRI y TruffleRuby, una implementación alternativa de Ruby. También comenta que han colaborado directamente con GitHub tanto en Ruby como en Rails. Si bien es difícil medir el aporte de cada empresa, lo importante es que muchas empresas están trabajando y colaborando para hacer Ruby mejor, lo cual le asegura un futuro interesante y sano a mi lenguaje de programación favorito.

    💎 De este último hilo otro recurso interesante: Peter Zhu publicó un artículo sobre Garbage Collection en Ruby, además de tener varios artículos interesantes más sobre Ruby en su blog.

    💎 Y cuanto más compiladores JIT tengamos mejor (?). Hace tiempo Chris Seaton programó uno para Ruby, hecho en Ruby. Nunca se motivó para terminarlo, pero decidió liberarlo en GitHub. Sirve como material didáctico, en el README del proyecto mismo dice “Se supone que lo leas, no que lo uses”. Tiene varios experimentos y documentos, así que nos puede ayudar a entender cómo funciona un JIT y aprender más de Ruby y el compilador.

    💎 Se publicó nueva versión de JRuby – 9.2.18.0. La rama 9.2.x es compatible con Ruby 2.5.x y se sincroniza con C Ruby. Se está trabajando activamente en la versión 9.3.0, pero esta versión de la rama anterior corrige algunos problemas. Se mejoró soporte para sub procesos en ambientes puramente Java, arreglos en concurrencia, y la biblioteca de socket fue backporteada de master con todos los arreglos recientes, mejoras en compatibilidad y demás. También se publicó después JRuby 9.2.19.0, una versión de correcciones para un problema con el flag --dev y un problema con zonas horarias en Windows.

    💎 Julie Jones, desarrolladora autodidacta, viene publicando en Twitter varios piques de Ruby bajo la premisa de 100 Days Of Code. Entre sus tweets podemos encontrar piques bastante buenos sobre bloques, Procs, y varias cosas más de Ruby. Podemos ver los tweets en este enlace y seguirla en Twitter en @codewithjulie.

    💎 Nat Friedman, CEO de GitHub, twiteó que GitHub procesa 2.8 miles de millones de pedidos a su API por día, con un pico de 55.000 pedidos por segundo. La aplicación es un monolito Rails, confirmado por Raffaele Di Fazio (del equipo de plataforma de GitHub) con la biblioteca Ruby resque para procesos en segundo plano. También hacen deploy a producción entre 20 y 30 veces por día. Son datos bastante interesantes y confirman lo que ya sabemos: Ni Ruby ni Rails están muertos, y Rails sí puede escalar…

    Gemas y bibliotecas

    💎 httplog es una gema para loguear pedidos HTTP hechos desde una aplicación Ruby. Funciona con varias bibliotecas y gemas populares como Net::HTTP (v4+), OpenURI, Patron, HTTPClient y HTTParty, pero está parcialmente testeada para Faraday y Typhoeus.

    💎 Se publicó Rubocop 1.17 que corrige un error que hacía que Rubocop fallara usando el bot Layout/HashAlignment, soporte para pattern matching de Ruby 2.7 y mucho más.

    💎 Textbringer es nada más y nada menos que un editor de texto inspirado en Emacs, escrito en Ruby. Dos de las mejores cosas del mundo combinadas: Emacs y Ruby 🙇

    En el blog:

    Eventos

    💎 Están publicados los videos de Euruko 2021, la conferencia europea de Ruby: Videos Euruko 2021 Día 1Videos Euruko 2021 día 2

    💎 Se realizó EMEA On Rails, un evento virtual para Rubystas. Participan varios grupos de usuarios de distintos países y podemos ver las charlas y talleres en este enlace.

    💎 El jueves 24 fue el meetup online de Ruby Galaxy. Hablaron Ramón Huidobro y Megan Tiu sobre “MINASWAN” (Matt is nice and so we are nice – Matz es bueno así que somos buenos) ha sido parte de la comunidad Ruby desde su fundación, y una de las cosas más lindas que puedes hacer es entrenar y mentorear a alguien. Este jueves a las 19:00 UTC vamos a estar hablando con dos expertos en entrenamiento y amabilidad. Como siempre, se transmitió a través de Twitch.

    El post Enlaces Ruby – Junio 2021 fue publicado originalmente en Picando Código.

    » Leer más, comentarios, etc...

    Header Files

    Argumentos expresivos (parte 2)

    julio 06, 2021 10:30

    Se suele decir que lo más difícil de la programación no es escribir código, es leerlo. Y los que hemos trabajado con bases de código de varios lustros de edad lo sabemos muy bien: funciones que tenemos que leer durante un par de horas para saber qué hacen, cómo lo hacen, sus precondiciones, sus casos borde, sus efectos colaterales. Muchas veces tenemos que pasar por largas sesiones de depuración paso a paso y refactorización para descifrar ese método que otro (¿nosotros?) escribió hace años (¿meses? ¿semanas?). Para más información sobre el trabajo con código legado recomiendo esta lectura.

    La mejor forma de resolver este problema es evitarlo: escribir código que no sólo ha de ser ejectuado por un ordenador sino que ha de ser leído por un ser humano. La expresividad del código es un tema que desde hace unos años me viene apasionando más y más, ya que muchas veces con muy poco esfuerzo es posible mejorar la calidad, legibilidad y mantenibilidad del código drásticamente. Y casi siempre sin añadir overhead a nuestro proyecto.

    En esta entrega extenderemos lo expuesto a comienzos de año a más tipos de datos de una forma muy sencilla.

    Argumentos booleanos expresivos

    Para refrescar, comentábamos que podíamos crear un tipo booleano con un propósito específico, que no fuera convertible implícitamente, y por lo tanto de forma oculta a nuestros ojos:

    struct TrueFalse
    {
      const bool value;
    
      explicit TrueFalse(bool value) : value{value} {}
      explicit TrueFalse(int value) = delete;
      explicit TrueFalse(const void* value) = delete;
      explicit TrueFalse(double value) = delete;
    
      operator bool() const { return value; }
    };
    #define DEF_TRUE_FALSE(name) struct name : TrueFalse { using TrueFalse::TrueFalse; }
    
    DEF_TRUE_FALSE(ReadOnly);
    

    Generalización

    Basándonos en esta solución es posible generalizar parte de la clase para soportar cualquier tipo de dato (aprovecharemos de extender algunas funcionalidades y de mejorar el código)

    template<typename T>
    class ExplicitValue
    {
      T value;
    
    public:
      explicit ExplicitValue(T value) noexcept : value{value} {}
    
      ExplicitValue(const ExplicitValue &other) noexcept : value{other.value} {}
      ExplicitValue(ExplicitValue &&other) noexcept : value{std::move(other.value)} {}
    
      ExplicitValue<T> &operator=(const ExplicitValue &other)
      {
        value = other.value;
        return *this;
      }
      ExplicitValue<T> &operator=(ExplicitValue &&other)
      {
        value = std::move(other.value);
        return *this;
      }
      
      operator T() const { return value; }
    };
    
    #define DEF_EXPLICIT_VALUE(name, Type) class name : public ExplicitValue<Type> { using ExplicitValue::ExplicitValue; }
    #define DEF_TRUE_FALSE(name) DEF_EXPLICIT_VALUE(name, bool)
    

    Por último, podemos extender esta funcionalidad aún más definiendo un literal de usuario para construir el tipo de dato. Es importante tener en cuenta las limitaciones de este operador respecto a los tipos de datos soportados, ya que es probable que tengamos que forzar un casting si nuestro tipo de datos usa valores con menor rango.

    DEF_EXPLICIT_VALUE(Kilometers, long double);
    inline Kilometers operator""_km(long double value) { return Kilometers{value}; }
    
    const auto distance = 42.0_km;
    

    Podéis encontrar el ejemplo completo y ejecutable en Coliru.

    » Leer más, comentarios, etc...

    Variable not found

    ¿Se pueden crear componentes Blazor que no hereden de ComponentBase o alguno de sus descendientes?

    julio 06, 2021 06:05

    Blazor

    Como sabéis, a no ser que se especifique lo contrario mediante la directiva @inherits, los componentes Blazor heredan de Microsoft.AspNetCore.Components.ComponentBase. Esta clase abstracta proporcionada por el framework hace que podamos centrarnos en escribir nuestro código, al encargarse de gestionar aspectos básicos de los componentes como su creación, inicialización, renderizado, el ciclo de vida u otros aspectos de infraestructura.

    Pero aunque heredar de ComponentBase es la inmensa mayoría de las veces la mejor opción, no tiene por qué ser así necesariamente. Blazor reconoce como componente cualquier clase que implemente IComponent, una pieza mucho más pequeña.

    La interfaz IComponent

    Todos los componentes Blazor implementan IComponent, definido en el espacio de nombres Microsoft.AspNetCore.Components de la siguiente forma:

    public interface IComponent
    {
    void Attach(RenderHandle renderHandle);
    Task SetParametersAsync(ParameterView parameters);
    }

    Como podéis observar, la interfaz se reduce a dos métodos:

    • Attach(), que es invocado cuando el elemento es introducido en el DOM.
    • SetParametersAsync(), invocado tras el anterior, al cargar los parámetros por primera vez, y cada vez que se modifique el valor de éstos.

    Dicho esto, veamos el ejemplo más sencillo posible que usa esta interfaz. El componente HelloWorld mostrará el clásico saludo cuando sea introducido en el DOM de una página:

    <h1><HelloWorld /></h1>

    Su código fuente:

    public class HelloWorld : IComponent
    {
    public void Attach(RenderHandle renderHandle)
    {
    renderHandle.Render(builder => builder.AddContent(1, "Hello, world!"));
    }

    public Task SetParametersAsync(ParameterView parameters) => Task.CompletedTask;
    }

    El método Render() del RenderHandle recibe como parámetro un RenderFragment que, como ya hemos visto, es un delegado que permite "escribir" el contenido que será insertado en el DOM al renderizar el componente. En este caso, hemos escrito el delegado en forma de lambda que recibe el RenderTreeBuilder para generar el contenido.

    De esta forma, al llamar a Render() en el método Attach(), estamos forzando a que el contenido sea renderizado justo en el momento en que el componente es insertado en el DOM.

    Vamos a complicar algo más el componente añadiéndole un parámetro que permita indicar un nombre al que saludar. Como podemos comprobar, sigue siendo bastante sencillo:

    public class HelloWorld : IComponent
    {
    private RenderHandle _renderHandle;

    public void Attach(RenderHandle renderHandle)
    {
    _renderHandle = renderHandle;
    }

    public Task SetParametersAsync(ParameterView parameters)
    {
    var name = parameters.GetValueOrDefault<string>("Name") ?? "World";
    _renderHandle.Render(builder => builder.AddContent(1, $"Hello, {name}!"));
    return Task.CompletedTask;
    }
    }

    Fijaos que en este caso hemos desplazado el renderizado al método SetParametersAsync(), que es donde se establecen los parámetros una vez ha sido creado el componente. Para ello hemos tenido que almacenar localmente el RenderHandle suministrado a Attach().

    Este componente podría usarse ahora de esta manera:

    <h1><HelloWorld /></h1> <!-- Muestra Hello, World! -->
    <h1><HelloWorld Name="Jon" /></h1> <!-- Muestra Hello, Jon! -->

    Estos serían ejemplos de los componentes más simples que podemos implementar, lo más pegado al metal que Blazor nos permite. Sobre estos mimbres está construido ComponentBase y todo lo que, sobre él, vayamos montando en nuestras aplicaciones.

    ¿Y es esto útil en la práctica? Pues no mucho más allá de satisfacer nuestra curiosidad y saber cómo funcionan las cosas por dentro 😉. Salvando algunas excepciones de componentes no visuales que no requieran ciclo de vida ni nada de lo que ComponentBase nos facilita, no puedo imaginar casos en los que esta fórmula para construir componentes pueda resultar útil en nuestro día a día.

    Pero bueno, ¿y lo que aprendemos por el camino?

    Publicado en Variable not found.

    » Leer más, comentarios, etc...

    Variable not found

    Enlaces interesantes 451

    julio 05, 2021 06:05

    Enlaces interesantes

    Ahí van los enlaces recopilados durante la semana pasada. Espero que os resulten interesantes. :-)

    Por si te lo perdiste...

    .NET Core / .NET

    ASP.NET Core / ASP.NET / Blazor

    Azure / Cloud

    Conceptos / Patrones / Buenas prácticas

      Machine learning / IA / Bots

      Web / HTML / CSS / Javascript

      Visual Studio / Complementos / Herramientas

      Xamarin / Maui

      Otros

      Publicado en Variable not found.

      » Leer más, comentarios, etc...

      Blog Bitix

      Análisis, guía y consejos del juego roguelike Darkest Dungeon

      julio 03, 2021 06:00

      Si hay un juego en el que no hay una decisión perfecta o en el que hay que hacer sacrificios ese es Darkest Dungeon. Seguir avanzando en una mazmorra supone un riesgo, tocar un objeto sin la provisión adecuada puede tener efectos muy negativos, a veces para completar una misión hay que sacrificar un héroe, equiparse con un abalorio aparte de los efectos positivos la mayoría tiene efectos negativos. Darkest Dungeon es un juego difícil en el que en gran medida para completarlo hace falta conocerlo muy bien, sin una guía y consejos es muy difícil llegar al su final o requeriría mucho tiempo para aprender todos sus trucos, con algunos de ellos el éxito no está asegurado pero hay mayor probabilidad de conseguirlo. Está guía es introductoria no es exhaustiva para completarla incluye varios enlaces con mucha información para profundizar más.

      Darkest Dungeon desarrollado por Red Hook Studios es uno de los mejores juegos en su categoría de rol y roguelike, destacando principalmente por sus mecánicas de juego que ofrecen gran cantidad de combinaciones y posibilidades. Es un juego difícil en el que los progresos se guardan de forma automática, de modo que las acciones realizadas no se pueden deshacer y si un héroe muere permanecerá muerto. En este juego el nivel de dificultad normal se puede considerar difícil, a lo largo del juego siempre se va al filo del desastre y en ocasiones hay tomar la decisión de que personaje es mejor que muera si es inevitable que uno lo haga, la mayoría de objetos que se encuentran y muchos curio tienen cosas negativas, aún con sus beneficios. Hay que exterminar el mal y es el precio a pagar.

      Aún con su dificultad a medida que se juega se van aprendiendo sus mecánicas y funcionamiento que hace del juego más sencillo y con menos riesgos dentro de su constante dificultad que roza el estar siempre al al borde del desastre, a veces se sufren derrotas aunque hace la victoria es más satisfactoria. Su gráficos no son en tres dimensiones pero destacan por su arte y son suficientes para el juego, la música tétrica ambiental al juego en su mundo decadente y peligroso. El juego está traducido al español pero no está doblado, es posible colocar subtítulos para entender los diálogos.

      Posee varias expansiones, The Crimson Court, The Colour of Madness y The Butcher’s Circus que añaden algunos héroes adicionales, nuevas localizaciones y algunas mecánicas de juego nuevas.

      Está disponible tanto en PC como para consultas y en las principales tiendas digitales de juegos, en la tienda de la Epic Store estuvo gratis. Aunque es posible jugarlo en consolas, con ratón es mucho más sencillo jugarlo, con el mando el manejo de la interfaz se hace complicado por la limitación de botones y la falta de puntero.

      Aún no me he pasado el juego y no he llegado al final pero ya he escrito este artículo porque este juego exige conocerlo para poder completarlo, al igual que en otros juegos de autoguardado las acciones son definitivas junto que además un error se penaliza llegando en el peor de los casos a suponer sin mucha dificultad la muerte de uno o varios héroes incluso de nivel alto. Parte de la dificultad del juego se basa en obtener conocimiento del él ya sea por experiencia, información leída o por consejos recibidos.

      Un juego similar a Darkest Dungeon es el juego de rol For the King con algunas mecánicas similares de combates y exploración de mazmorras, sin embargo, Darkest Dungeon es mucho más complejo y difícil. A alguien que le guste uno de los dos juegos seguramente le guste el otro.

      Pantalla inicial Menú

      Pantalla inicial y menú del juego Darkest Dungeon

      Anticipación del juego

      Este artículo contiene información de estrategias para completar más fácilmente el juego, parte de la diversión de un juego es descubrir y superar los retos que se plantean por uno mismo. Sin embargo, algunos juegos son difíciles sin una pequeña ayuda que obliga a tener que dedicarles mucho más tiempo o a recomenzarlos.

      En algunos juegos el argumento es una de las partes más importantes. El texto del artículo no contiene información acerca del argumento del juego, de la mitad o del final, ni hace ningún spoiler por lo que lo puedes leer sin riesgo de conocer alguna parte del argumento de forma anticipada. Sin embargo, algunos enlaces del artículo a otras páginas y vídeos sí pueden contener información del argumento de modo que recomiendo consultar solo las partes del juego una vez superadas.

      Contenido del artículo

      Héroes

      Las misiones para explorar mazmorras se realizan en grupos de cuatro que hay que elegir en la preparación. Hay 16 clases de héroe distintas que reclutar entre los disponibles en los disponibles en la diligencia, cada clase de héroe tiene sus propias habilidades de combate y acampada y estadísticas base únicas entre las que se encuentran los puntos de vida los rasgos positivos y negativos, sus estadísticas de resistencias a varios efectos como aturdimiento y finalmente están las enfermedades que ha adquirido el héroe.

      Algunos tienen  por su clase ciertos beneficios como la anticuaria que incluirla en un grupo permite aumentar acumular montones de dinero de 2500 en vez de 1750 y con beneficios a la obtención de objetos en las exploraciones, las anticuarias son especialmente buenas para obtener botín. Por otro lado el cruzado y la vestal tienen beneficios contra enemigos de tipo impío.

      Las clases de los héroes son abominación, anticuaria, arbalestera, caza recompensas, cruzado, asaltatumbas, diabla, bandolero, maestro canino, bufón, leproso, hombre de armas, ocultista, doctor de plaga y vestal. En las expansiones del juego se añaden rompescudos, mosqueteros y flagelantes. Todos estos tipos de clases son únicos aunque algunos comparten habilidades con efectos comunes como el aturdir, causar hemorragia o infección, curar o habilidades de potenciación.

      Independientemente de la clase de los héroes al embarcar en una misión hay que elegir un grupo de héroes equilibrado que cumplan los roles de tanque que será el que absorba daño, provocador de daño que será el que haga daño en grandes cantidades a los enemigos, curador que mantenga en buena salud al resto de héroes y soporte que proporcione potenciadores. Esta es la regla general algunos héroes tienen habilidades que permiten hacer la función de otro, en un grupo puede haber un héroe especializado en curar pero otro tener también alguna habilidad de curación para apoyar o un héroe proporcionar potenciadores pero también ser capaz de hacer un daño moderado.

      A medida que los héroes realizan misiones ganan experiencia y suben de nivel, lo que mejora sus estadísticas y les permite equiparse con mejores armaduras y armas y un nivel mayor de habilidad. El nivel que tienen los clasifica en aprendiz, veterano y campeón, por otro lado las misiones también se clasifican en aprendiz, veterano y campeón. Los héroes se niegan a participar en misiones de menor nivel al que tienen. Los niveles 0, 1, 2 son de aprendiz, 3 y 4 es un veterano y 5 y 6 es un campeón. Pasar del nivel 5 al 6 no mejora las estadísticas base pero si las resistencias.

      Cada héroe puede tener hasta 5 rasgos positivos que se ganan al subir de nivel  y 5 negativos que se adquieren también al subir de nivel o en algunos curio. Si un héroe ya tiene 5 rasgos y adquiere otro uno anterior es reemplazado, para no perder uno especialmente bueno es posible fijarlos en el sanatorio. Aunque los héroes tienen hasta 7 habilidades entre las que elegir en un mismo momento solo pueden estar activas 4 igual que las de acampada, estas hay que elegirlas por ser las mejores del héroe o por ser las más adecuadas según el grupo de héroes en una misión. Se pueden cambiar en el poblado y también entre los combates pero no en medio de un combate. Los héroes de la diligencia que se incorporan a los barracones con un nivel mayor que 0 las poseen todas a su mayor nivel a diferencia de los héroes que se incorporan de nivel 0 que no poseen todas.

      Abominación Anticuaria Arbalestera

      Caza recompensas Cruzado Asaltatumbas

      Diabla Bandolero Maestro canino

      Bufón Leproso Hombre de armas

      Ocultista Doctor de plaga Vestal

      Héroes de Darkest Dungeon

      Clases, habilidades más destacadas y funciones

      Entre todos las diferentes clases de héroes en un grupo hay que elegir una combinación de ellos en el que sus habilidades se complementen o refuercen. Las funciones de los héroes son las siguientes:

      • Tanque: esta función de héroe es desempeñada por uno con una gran cantidad de puntos de vida y protección capaz de absorber mucho daño sin correr excesivo peligro. Aparte de absorber daño también puede tener la función de hacer daño y otras funciones de apoyo con algunas de sus habilidades como curar o proporcionar potenciadores. Las estadísticas principales de este héroes serán los puntos de vida y protección y en menor medida daño y evasión. Aunque un tanque también podría serlo por tener un alto valor de evasión.
      • Provocador de daño: esta función es la de causar gran cantidad de daño a los enemigos para eliminarlos de un solo golpe o quitar gran cantidades de vida a enemigos con gran cantidad de vida. Las estadísticas principales de los héroes para esta función son la de daño y probabilidad de crítico.
      • Curador: los enemigos también van a tener muchas oportunidades de realizar ataques y causar daño  los héroes, es imprescindible que haya un héroe que cuando sea necesario cure al resto para mantener los puntos de vida del resto en unos niveles que no les haga correr el riesgo de entrar a las puertas de la muerte en el que puedan morir. En el grupo de héroes es aconsejable que haya uno que cure puntos de vida y algún otro con la habilidad de bajar los puntos de estrés.
      • Potenciadores y mermas: otra función que puede desarrollar un héroe es la de soporte para proporcionar potenciadores al resto de héroes del grupo como un aumento de daño, esquiva o velocidad.

      Todos las clases de héroe tienen tres habilidades de acampada comunes y cuatro únicas, que proporcionan potenciadores durante los cuatro siguientes combates o que curan esfuerzo, algunas habilidades tienen efectos negativos. Al realizar una acción con resultado de crítico los héroes obtienen un potenciador.

      Cada tipo de héroe destaca y se diferencia en algunos aspectos del resto:

      • Abominación, ficha: es un héroe flexible ya que todas sus habilidades están a disposición con la transformación. Se puede curar la vida y esfuerzo.
      • Anticuaria: es el héroe con habilidades de ataque más débiles, su mayor utilidad es en su capacidad de aumentar el botín y monedas recuperados. En un combate su función es la de soporte.
      • Arbalestera, ficha: su posición es la 3 o 4, puede hacer grandes cantidades de daño a las últimas posiciones potenciado con los enemigos marcados y hacer las funciones de curación de soporte cuando no hay más enemigos a los que pueda atacar, su curación de batalla no cura mucho pero hace más efectivas las siguientes curaciones.
      • Caza recompensas, ficha: su posición es la 2 pero también puede estar en la 3, tiene la capacidad de mover y causa daño adicional a héroes marcados o aturdidos. No hace la función de tanque ya que no se potencia con protección.
      • Cruzado, ficha: su posición es la 1 o 2, hace la función de tanque potenciado su protección significativamente que dura toda la batalla. A los enemigos impíos les hace daño adicional. Tiene la capacidad de curar puntos de vida y esfuerzo dando soporte al curador principal.
      • Asaltatumbas, ficha: tiene habilidades que le dan movilidad pero no es adecuado junto a héroes que no la tengan, su evasión le permite usarse como tanque a base de evasión.
      • Diabla, ficha: su posición es la 1 o 2. Su habilidad Hachazo perverso le permite atacar a las dos primeras posiciones y con Cisne de hierro puede atacar a enemigos en la posición 4 lo que le permite atacar casi a cualquier posición del enemigo.
      • Bandolero, ficha: puede estar en cualquier posición causando grandes cantidades de daño con su notable probabilidad de crítico y aumento de daño, precisión y crítico con Disparo de seguimiento que dura durante todo el combate útil en los combates largos como los jefes.
      • Maestro canino, ficha: su posición es la 3 o 4 pudiendo llegar a todos los enemigos. Es especialmente adecuado para enemigos vulnerables a sangrado como las bestias pero inefectivo para no muertos que lo resisten. La habilidad Silvido quita mucha protección. Está equipado con dos piezas de galleta de premio que proporciona potenciadores al daño durante tres turnos.
      • Bufón, ficha: su posición ideal es la 3 donde puede usar Tonada inspiradora y usar sus ataques. El potenciador Balada de batalla que proporciona velocidad, precisión y crítico aplica a todo el grupo. Con la habilidad Apoteosis puede causar gran cantidad de daño para acabar con el último enemigo. Es más adecuado para misiones largas por su Tonada inspiradora que cura esfuerzo y potencia la resistencia al esfuerzo.
      • Leproso, ficha: puede ser considerado el mejor tanque del juego, tiene la capacidad de curarse puntos de vida significativamente y esfuerzo que lo hace autosuficiente. Solo es útil en las dos primeras posiciones, su otro defecto es que no tiene mucha precisión lo que hay que corregirlo con algún abalorio o potenciador.
      • Hombre de armas, ficha: su posición es la 1 o 2 donde puede usar todas sus habilidades, también es posible usarlo en la posición 4 donde potencia a los aliados en grupo mejorando precisión, velocidad y evasión, también puede reducir la evasión y velocidad los enemigos. Puede hacer de tanque absorbiendo daño.
      • Ocultista, ficha: su posición ideal es la 3, tiene habilidades para reducir a los enemigos más peligrosos reduciendo su daño y precisión además de marcarlo reduciendo su evasión. Tiene una poderosa habilidad de curación pero no totalmente fiable. Puede mover a enemigos a posiciones adelantadas donde no puedan usar sus habilidades más peligrosas o donde los compañeros de ataques cuerpo a cuerpo los eliminen.
      • Doctor de plaga, ficha: su posición es la 3, aunque sus habilidades tiene un daño base bajo causan efectos negativos al enemigo. Tiene una de las habilidades de movimiento más poderosas y la capacidad de aturdir a dos enemigos en la retaguardia. Tiene una habilidad de curación básica pero que elimina infecciones y sangrado.
      • Vestal, ficha: si posición ideal es la 3. Tiene dos poderosas habilidades de curación, una para un único objetivo y otra que cura menos pero afecta a todo el grupo. Con la habilidad Juicio puede atacar y curarse. Tiene habilidades que restauran el nivel de luz para conservar antorchas en misiones largas.

      Por tipo

      Las clases de los héroes que tienen como función principal en el grupo son:

      • Tanque: Cruzado, Leproso, Hombre de armas, Asaltatumbas (por evasión).
      • Provocador de daño: Caza recompensas, Asaltatumbas, Diabla, Bandolero, Abominación, Hombre de armas, Maestro canino, Arbalestera.
      • Curador (Puntos de vida, Esfuerzo): Ocultista (PV), Vestal (PV), Cruzado (PV, E), Bufón (E), Arbalestera (PV), Doctor de plaga (PV), Maestro canino(E).
      • Soporte, potenciadores y mermas: Bufón, Oultista, Doctor de plaga, Vestal, Hombre de armas, Maestro canino.

      Otro aspecto en el que se pueden clasificar los héroes son por aquellos que tienen la capacidad de hacer daño a medida que pasa el tiempo ya sea por hemorragia o infección. O que tiene la capacidad de mover a enemigos de su posición a otra en la que no puedan utilizar su habilidad más poderosa o de moverse en caso de que los cambien de posición, también si pueden aturdir. La capacidad de hacer daño a enemigos en posiciones 3 y 4, también es importante pues en estas se suelen ubicar los enemigos más peligrosos.

      • Daño por tiempo (Infección, Hemorragia): Asaltatumbas (I), Diabla (H), Bufón (H), Doctor de plaga (H, I), Maestro canino (H), Anticuaria (I).
      • Capacidad de marcar: Caza recompensas, Ocultista, Maestro canino, Arbalestera.
      • Capacidad de aturdir: Doctor de plaga, Vestal, Abominación, Hombre de armas, Diabla.
      • Capacidad de moverse con ataque: Asaltatumbas, Cruzado, Diabla, Bandolero, Bufón, Abominación.
      • Capacidad de mover: Caza recompensas, Ocultista, Doctor de plaga, Abominación, Hombre de armas, Bandolero, Leproso.
      • Capacidad de hacer daño a enemigos en posiciones 3 y 4: Caza recompensas, Asaltatumbas, Diabla, Bufón, Ocultista, Vestal, Abominación, Hombre de armas, Maestro canino, Arbalestera.
      • Capacidad de curarse: Leproso, Abominación, Maestro canino.
      • Capacidad de mermar (Evasión, Daño, Protección, Crítico, Precisión, Velocidad): Ocultista (E, D, Pro), Arbalestera (Pre, C, E), Caza recompensas (Pro, D, V), Hombre de armas (E, V), Maestro canino (Pro), Leproso (D, V, Stealth).
      • Daño adicional (Marcado, Hemorragia, Infección, Aturdido): Arbalestera (M), Caza recompensas (M, Humano), Cruzado (Impío), Asaltatumbas (M, H, I), Bandolero (M), Maestro canino (M, Bestia), Ocultistas (Eldrich), Vestal (Impío).
      • Héroes religiosos: Leproso, Cruzado, Vestal.

      La posición de los héroes también es importante, las habilidades y ataques tienen restricciones de posición.

      • Posición 1-2: Abominación (2), Cruzado (1), Diabla, Leproso (1), Hombre de armas.
      • Posición 2-3: Asaltatumbas, Bandolero, Caza recompensas (2).
      • Posición 3-4: Anticuaria, Arbalestera (4), Maestro canino, Hombre de armas, Vestal, Bufón (3), Ocultista (3), Doctor de plaga (3).

      Poblado, edificios y recursos

      El juego se desarrolla en dos zonas distintas, el poblado donde se prepara la siguiente misión con la exploración de mazmorras y combates.

      En el poblado hay una serie de edificios que atienden a varias necesidades de los héroes como el reclutamiento con la caravana, la reducción de esfuerzo con la taberna y la abadía, el sanatorio para quitar rasgos y enfermedades, la armería y el gremio para mejorar habilidades de los héroes según alcanzan mayores niveles y la mejora de armaduras y arma, otras localizaciones son el cementerio donde ver los héroes caídos en combate, el carro nómada donde comprar nuevos abalorios y el superviviente donde adquirir nuevas habilidades de acampada.

      Los edificios se pueden mejorar con reliquias que se obtienen durante las misiones como emblemas, bustos, cuadros y manuscritos. Estas mejoras permiten reducir los costes de usar los edificios, proporcionan acceso a entrenar habilidades, armas y armaduras de mayor nivel o más celdas en las salas de los edificios para tratar a los héroes.

      Al finalizar una exploración de mazmorra los héroes habrán ganado esfuerzo, quizá alguna infección y si suben de nivel algún rasgo especialmente negativo. Al salir del combate es necesario tratar las afecciones especialmente negativas ganadas por héroes en la taberna, abadía y sanatorio de modo que no tengan penalizaciones en la siguiente misión.

      Feudo Cuidador

      Feudo y cuidador

      Mecánicas del juego

      El juego no explica en gran detalle la mayor parte de sus mecánicas sino que estas se van aprendiendo al experimentarlas, esto provoca cometer errores, provocar la muerte de héroes y alargar el tiempo que se tarda en completar el juego. Algunas de las cosas que no se explican son las siguientes.

      Durante una misión hay un nivel de luz que se va reduciendo a medida se recorre la mazmorra, las antorchas y algunas habilidades permiten aumentar el nivel de luz que afecta a mecánicas como el esfuerzo, probabilidad de ser sorprendidos y sorprender y patrulla. Es recomendable mantener siempre el nivel de luz en un nivel radiante superior a 75. El nivel de luz también afecta al botín obtenido siendo cuanto menos bajo mejor.

      Las hemorragias e infecciones que causan daño al inicio de cada turno del héroe o enemigo se pueden acumular en caso de sufrir varias, el daño que causan que aparentemente no parece peligros con varias de estas acumuladas causan un daño significativo. También hay otros efectos de estado que son temporales y que afectan las estadísticas.

      Los potenciadores y mermas tanto para héroes como enemigos también se acumulan e igual que las hemorragias e infecciones duran las rondas que indiquen.

      En misiones medianas y largas existe la posibilidad de acampar después de la cual los héroes pueden ser sorprendidos por un grupo de enemigos.

      Los héroes tienen un nivel y las misiones también tiene un nivel, los héroes de mayor nivel que la misión se niegan a participar en misiones de nivel bajo.

      Los héroes pueden morir de dos formas, cuando están con cero puntos de vida en el estado de puertas a la muerte en este estado cada vez que sufran daño deberán superar un golpe mortal según su estadística de golpe mortal que por defecto es de un 67%. La otra forma de morir de un héroe es acumulando 200 puntos de esfuerzo momento en el cual es seguro que sufre un ataque al corazón y muere, si llega a 100 puntos adquiere un rasgo negativo que lastra al resto de héroes pudiendo causarles puntos de esfuerzo, realizando acciones aleatorias o efectos algunos leves pero otros muy nocivos.

      El porcentaje de patrulla es de utilidad para evitar peligros al explorar mazmorras, descubrir trampas, sorprender a los enemigos que da la ventaja de tener el turno inicial adicional o evitar que el grupo de héroes sea sorprendido que da el turno adicional a los enemigos. También permite descubrir salas secretas con tesoros.

      Localizaciones

      En el juego sin expansiones hay 4 localizaciones, 5 contando la mazmorra oscura, en las expansiones se añaden una más. A medida que se completan misiones en las localizaciones estas suben de nivel  lo que causa que puedan aparecer enemigos más peligrosos y da acceso a que aparezca la misión de acabar con los jefes. El nivel de las localizaciones llega hasta al 7.

      Cada localización tiene diferentes tipos enemigos. En las ruinas serán principalmente no muertos, nigromantes y cultistas de tipo impío con resistencia al sangrado pero con pocos puntos de vida pero vulnerables a la infección y menos peligrosos que los de otras localizaciones, en esta localización es posible encontrar gran cantidad de tesoros. En la foresta abundan los bandidos de tipo humano y otros seres que causan infecciones. En el laberinto es el lugar de las bestias y hombres cerdo vulnerables a la hemorragia. La cala es habitada por criaturas del mar y no muertos, abundan los enemigos con gran protección que causan infecciones y enfermedades y muchos curio proporcionan potenciadores o mermas o manipulan los rasgos.

      La localización del Darkest Dungeon es el objetivo del juego, tiene algunos aspectos diferentes en el que no se genera de forma aleatoria ni se encuentra botín .

      Localizaciones

      Localizaciones

      Estadísticas

      Cada clase de héroe tiene sus propias habilidades de combate y acampada y estadísticas únicas entre las que se encuentran, puntos de vida, evasión, protección, velocidad, modificador de precisión, probabilidad de crítico y el rango de daño que realiza por ataque. También tienen varios rasgos positivos y negativos que afectan a las estadísticas de diferentes formas. Otro aspecto único de cada clase son las estadísticas de resistencias a varios efectos como aturdimiento, infección, enfermedad, golpe mortal, movimiento, hemorragia, merma y desarmado de trampas. Finalmente, están las enfermedades que ha adquirido el héroe si no ha superado una resistencia de enfermedad.

      Las estadísticas bases y sus efectos son los siguientes:

      • Puntos de vida: son los puntos de vida del héroe. Al  iniciar una misión los héroes siempre empiezan con su nivel máximos de puntos de vida y se recuperan de forma completa al finalizar o abandonar la misión, al contrario que los puntos de esfuerzo que perduran y para reducirlos hay que utilizar la taberna o la abadía.
      • Evasión: es la esquiva del héroe, al medir el éxito de un ataque se tiene en cuenta junto con el porcentaje base de precisión y el modificador de precisión.
      • Protección: es un porcentaje que reduce el daño que sufre el héroe o enemigo al sufrir daño y calcula los puntos de vida que se pierde.
      • Velocidad: determina el momento en el que actúa el héroe teniendo en cuenta el resto de puntos de velocidad de otros héroes y enemigos. La línea de tiempo en la que actúan los héroes no aparece en pantalla lo que dificulta un poco en alguna ocasión utilizar alguna habilidad.
      • Modificador de precisión: es un modificador que se añade a la precisión base del héroe según su nivel de habilidad.
      • Probabilidad de crítico: es un porcentaje realizar ataque crítico que causa un mayor daño o cura un mayor puntos de vida de lo normal.

      Las estadísticas de resistencias y sus efectos son las siguientes:

      • Aturdimiento: el aturdimiento causa la pérdida de la acción durante una ronda. La resistencia el porcentaje para resistirse a ser aturdido.
      • Infección: es un ataque que causa daño por tiempo al igual que la hemorragia. La resistencia es el porcentaje para resistirse a ser infectado.
      • Enfermedad: es el nivel de resistencia al sufrir una enfermedad que tiene la capacidad de modificar las estadísticas de los héroes. Las enfermedades se curan en el sanatorio.
      • Golpe mortal: es la resistencia a acabar muerto del héroe al sufrir un nuevo daño cuando está en las puertas de la muerte con cero puntos de vida. La estadística base es de un 67% de resistencia.
      • Movimiento: es la resistencia a ser cambiado de posición cuando sufre un ataque de movimiento.
      • Hemorragia: la hemorragia es otro ataque que causa daño por tiempo. La resistencia es el porcentaje para resistirse a sufrir hemorragia en un ataque que lo provoque.
      • Merma: la resistencia a evitar ataques que causen efectos de merma.
      • Desarmado de trampas: es el porcentaje de éxito al intentar desarmar una trampa.

      El nivel de arma afecta al daño base, porcentaje de crítico base y velocidad base. El nivel de armadura afecta a los puntos de esquiva y puntos de vida. El nivel de habilidad afecta con porcentaje modificador al daño base, la precisión base y porcentaje de crítico base. Algunos abalorios modifican estas estadísticas, algunos aumentan unas estadísticas de forma positiva pero también pueden tener efectos negativos al mismo tiempo.

      Una estadística que no aparece en la ficha del héroe es la virtud. Cuando un héroe alcanza los 100 puntos de esfuerzo tienen un porcentaje de en vez de obtener un rasgo negativo entrar en un estado virtuoso positivo y reducir su esfuerzo a 45. La probabilidad base de virtud es 25% que se puede modificar por rasgos y abalorios. Hay varios estados de virtud diferentes algunos permiten hacer más daño, curar esfuerzo a los compañeros, tener más protección y otros potenciadores a las estadísticas.

      Ficha de héroe

      Ficha de héroe

      Rasgos y enfermedades

      Los héroes al subir de nivel con la experiencia ganada después de completar una misión tienen la posibilidad de ganar rasgos, tanto positivos como negativos. También es posible que ganen algún rasgo al activar algún curio, en la mayoría de ocasiones serán negativos y algunas pocas positivos. Por otro lado, algunos enemigos con sus ataques provocan enfermedades si el héroe no supera la tirada de resistencia a la enfermedad.

      Los rasgos positivos especialmente adecuados para el héroe es posible fijarlos en el sanatorio, los rasgos negativos es posible eliminarlos y las enfermedades es posible curarlas. Es posible fijar un rasgo positivo y eliminar un rasgo negativo al mismo tiempo.

      Algunos rasgos negativos no son especialmente graves pero hay otros que hay que quitarlos si es posible porque afecta de forma muy negativa a las estadísticas del héroe o a una estadística importante. También hay rasgos positivos buenos, fijarlos evitar perderlos si en algún momento el héroe ya tiene 5 rasgos positivos y gana otro lo que provoca que alguno no fijado sea reemplazado.

      Estos son los rasgos positivos más destacados junto con sus efectos e interesantes que los héroes poseen. Si son especialmente adecuados según su clase de héroe interesa fijarlos para que no sean reemplazados por otros.

      • Natural Eye: +5 a la precisión en ataques a distancia.
      • Natural Swing: +5 a la precisión en ataques a cuerpo a cuerpo.
      • Slugger: +10% de daño en ataques cuerpo a cuerpo.
      • Unerring: +10% de daño en ataques a distancia.
      • Eagle Eye: +3% a la posibilidad de crítico para ataques a distancia.
      • Precision Striker: +3% a la posibilidad de crítico para ataques cuerpo a cuerpo.
      • Deadly: +1% a la posibilidad de crítico.
      • Tough: +10% de vida máxima.
      • Evasive: +5 a evasión.
      • Quick Reflexes: +2 a la velocidad.
      • Hard Skinned: +10% a la protección.
      • Steady: +10% de resistencia esfuerzo.
      • Quickdraw: +4 a la velocidad durante la primera ronda de combate.
      • On Guard: +4 a la velocidad y +5 a evasión durante la primera ronda de combate.
      • Unyielding: +10% de resistencia a golpes mortales.

      Según la función del héroe estos son los rasgos positivos más beneficiosos que tengan:

      • Un héroe de ataques a distancia: Eagle Eye, Deadly, Evasive. Estos héroes están maximizados para hacer daño con un poco de mayor velocidad.
      • Un héroe de ataques cuerpo a cuerpo: Slugger, Precision Striker, Hard Skinned. Un buen incremento en el daño y protección adicional.
      • Un héroe con la función de tanque: Slugger, Tough, Hard Skinned. Más protección, vida y algo más de daño en ataques cuerpo a cuerpo.
      • Un héroe para actuar primero especialmente en la primera ronda: Quick Reflexes, Quickdraw, On Guard. Esto proporciona puntos de velocidad en la primera ronda.
      • Un héroe con la función de curar: Tough, Quick Reflexes, Hard Skinned. Algo más de vida, de protección y algo de velocidad.

      Los rasgos a evitar son los siguientes:

      • Los que reducen la velocidad: Nocturnal, Off Guard, Slowdraw, Slow Reflexes.
      • Los que reducen el daño: Light Sensitive, Scattering, Calm, Tuckered Out.
      • Los que fuerzan a los héroes a realizar acciones: Ablutomania, Bloodthirsty, Compulsive, Curious, Dacnomania, Dark Temptation, Demonomania, Dipsomania, Egomania, Guilty Conscience, Hagiomania, Hylomania, Kleptomaniac, Necromania, Paranormania, Plutomania, Sitiomania.
      • Los que causan robo de botín: Kleptomania.

      Guía de rasgos.

      Abalorios

      Los abalorios son objetos que los héroes equipan, como máximo dos, que proporciona mejoras en algunas estadísticas sin embargo muchos de ellos al mismo tiempo también tiene un efecto negativo en alguna otra estadística. Son un elemento fundamental equipar a los personajes para potenciar sus virtudes o suplir sus defectos, tambié teniendo en cuenta el resto de personajes con los que va acompañado. Los abalorios se consiguen principalmente como una recompensa en el éxito en las misiones, los jefes también entregan abalorios importantes, también se pueden comprar en el carro nómada.

      Estos son abalorios destacados para las funciones de algunos héroes, la mayoría aquellos que dan velocidad, evasión, los de daño, precisión y crítico para los que hacen daño, los de protección y resistencias para los tanques y algunos para suplir sus carencias como precisión para el leproso o potenciar su función como curaciones para los curadores.

      Héroe Abalorios Efecto
      1 2
      Abominación Ancestor's Pen Feather Crystal más velocidad, evasión, crítico y daño
      Berserk Charm Restraining Padlock más velocidad, daño, menos estrés causado en la transición a bestia
      Arbalestera Ancestor's Musket Ball Wrathful Bandana más daño y crítico en ataques a distancia
      Bull's Eye Bandana Wrathful Bandana más precisión, critico y daño
      Caza recompensas Ancestor's Pen Hunter's Talon más crítico, precisión y daño en ataques de cuerpo a cuerpo
      Hunter's Talon Wounding Helmet más crítico, precisión y daño cuerpo a cuerpo
      Cruzado Berserk Charm Legendary Bracer más velocidad y daño
      Tough Ring Berserk Charm más protección, vida y daño
      Asalta tumbas Raider's Talisman Lucky Talisman más crítico, desarmado de trampas, patrulla, evasión y precisión
      Feather Crystal Raider's Talisman más crítico, desarmado de trampas, patrulla, evasión y velocidad
      Diabla Ancestor's Pen Berserk Charm más daño, critico y velocidad
      Ancestor's Pen Legendary Bracer más daño y crítico
      Bandolero Gunslinger's Buckle Berserk Charm Charm más daño, precisión en ataques a distancia y daño
      Gunslinger's Buckle Ancestor's Musket Ball más daño y precisión en ataques a distancia
      Maestro canino Feather Crystal Camouflage Cloak más velocidad y evasión
      Spiked Colar Feather Crystal más velocidad, evasión, daño y probabilidad de causar hemorragia
      Bufon Feather Crystal Camouflage Cloak más velocidad y evasión
      Feather Crystal Ancestor's Coat más velocidad y evasión
      Leproso Focus Ring / Fortunate Armlet Tough Ring más precisión y crítico
      Focus Ring Legendary Bracer más precisión, crítico y daño
      Hombre de armas Camouflage Cloak Feather Crystal más velocidad y evasión
      Guardian's Shield Feather Crystal más velocidad y evasión, en la posición 4 más protección, evasión y curaciones recibidas
      Ocultista Feater Crystal Cleansing Crystal más velocidad, evasión y resistencias, menos probabilidad de causar hemorragia al curar
      Camouflage Cloak Cleansing Crystal más velocidad, evasión y resistencias, menos probabilidad de causar hemorragia al curar
      Doctor de plaga Blasphemous Vial Poisoned Herb más precisión y aturdir e infectar al enemigo
      Blasphemous Vial Feater Crystal más precisión y probabilidad de aturdir e infectar al enemigo, evasión y velocidad
      Vestal Sacred Scroll Tome of Holy Healing más precisión y aturdir e infectar al enemigo
      Tough Ring Haste Chalice más vida máxima, protección y velocidad

      Abalorios

      Abalorios

      Botines y curios

      Los curio son objetos que se encuentran en los pasillos y en las salas de las mazmorras, dependiendo del curio activarlos permite conseguir una recompensa en reliquias o monedas pero algunos también tienen efectos negativos como ganar un rasgo negativo o una enfermedad. Los curio se pueden activar utilizando un objeto del equipo para evitar su probabilidad de efecto negativo y obtener siempre una recompensa positiva ya sea en reliquias, monedas, potenciadores o rasgo positivo. Cada curio tiene siempre los mismos efectos y probabilidad de resultado pero muchos de ellos tienen efectos negativos con lo que siempre hay que consultar antes el objeto adecuado con el que activarlo para evitar el riesgo de obtener un efecto negativo.

      Las reliquias de la  familia son bustos, pinturas, hojas de diario y emblemas. Son los recursos con los que se mejoran los edificios. Es posible hacer conversiones entre cada uno de estos recursos.

      Botín Curio

      Botín y curio

      Misiones y combates

      Las misiones consisten en la exploración de una mazmorra, según la localización los enemigos serán unos u otros. Las misiones tienen un nivel (aprendiz, veterano, campeón), una duración (corta, mediana y larga), en función de los rasgos de los héroes un porcentaje de patrulla y un objetivo.

      Los diferentes objetivos de las misiones son los siguientes:

      • Limpieza: ganar todos las batallas de la mazmorra.
      • Explorar: explorar el 90% de las salas.
      • Matar: acabar con un  jefe.
      • Activación: activar tres altares.
      • Recolectar: recolectar 3 reliquias.

      Los héroes de un nivel se niegan a participar en misiones de un nivel inferior, los héroes con nivel inferior pueden participar en misiones de mayor nivel sin embargo es arriesgado ya que son débiles ante los enemigos de mayor nivel.

      Los mapas de las mazmorras se generan de forma aleatoria siendo de mayor tamaño para las de mayor duración. En los pasillos y salas de las mazmorras es posible encontrar curio y trampas. Cuando se llega a una sala está vacía o está ocupada y después de eliminar a los enemigos se puede producir el efecto de patrulla que permite ver cómo es la mazmorra en las ubicaciones cercanas, con la patrulla también es posible encontrar salas ocultas, evitar ataques sorpresa de los enemigos y realizar ataques sorpresa con más facilidad al inicio de los combates a los enemigos.

      Las misiones cortas no permiten acampada, las misiones medianas permiten acampar una vez y las largas dos veces para lo cual se añade un objeto en el inventario de leña, las acampadas se realizan en una sala de la mazmorra. Al acampar se emplean las habilidades de acampada, se proporcionan 12 puntos de acción a repartir entre las habilidades que se quieran utilizar, estas habilidades permiten curar puntos de vida, puntos de esfuerzo y proporcionar potenciadores que duran hasta 4 combates. Es recomendable acapar en la sala anterior antes de llegar a un jefe final para tener potenciadores que las habilidades causen mayor daño o llegar con unos niveles de esfuerzo y vida buenos. Después de acampar es posible que el grupo sea sorprendido por los enemigos y haya que realizar un combate.

      Antes de empezar una misión está la fase de aprovisionamiento en la que se compran los objetos a llevar en el inventario, entre estos objetos están principalmente las antorchas y la comida y algunos otros objetos de utilidad como las vendas para curar hemorragias o antiveneno para curar infecciones, las palas para quitar obstáculos que si no se tiene causan gran daño y esfuerzo sin ellas, el agua bendita para proporcionar resistencias. Varios de estos objetos se pueden emplear para activar curio y obtener botín sin riesgos de efectos negativos. En función de la duración de la misión se equipan más objetos o menos, intentando que sean los mínimos imprescindibles pero siempre con un margen suficiente para completar la misión. Llevar muchos objetos en el inventario permite completar la misión con menos riesgo pero si se quiere recoger botín obliga a tirar objetos que no hubiera sido necesario aprovisionarse.

      Después de los combates y con los curio se recoge un botín que ocupa lugar en el inventario, a veces hay que deshacerse de objetos para conseguir espacio para los objetos que se desean recoger. Al final de una misión hay que tratar de conseguir el mayor botín posible tanto en reliquias como en monedas para poder mejorar edificios, adquirir habilidades para los héroes o sanar a los héroes.

      Los combates se desarrollan por turnos, cada héroe y enemigo tiene una acción, algunos enemigos dos. El orden de acción se determina por la estadística de velocidad, Un héroe realiza la acción usando una habilidad que se desee entre las equipadas. Para calcular el éxito del uso de una habilidad se utiliza la estadística de precisión y la estadística de evasión del objetivo. El daño se calcula con la estadística de daño y de protección del objetivo. Al realizar un ataque existe la posibilidad de realizar un crítico con el que se hace un daño o curación superior al normal, para ello se utiliza la estadística de probabilidad de crítico. Algunos ataques causan daño por tiempo mediante hemorragia o infección al inicio de la acción del héroe o enemigo, cuando un enemigo muere a causa de estos no deja su cadáver y los enemigos posteriores se desplazan a las primeras posiciones.

      Las habilidades tiene unos requerimientos de posición en la que hay que estar para usarla y un requerimiento de posición del objetivo, por ejemplo algunas habilidades sólo son utilizables en la posición 3 y 4 contra objetivos en la posición 1 y 2.

      Las provisiones recomendadas en función de la duración de la misión y la localización son las siguientes, las básicas son la comida y las antorchas el resto son de utilidad por el tipo de enemigos y el tipos de curio que se encuentran y son empleables entre ellos. Las provisiones son consumibles que tiene diferentes efectos, se pueden usar para conseguir sus efectos o al activar los curio para conseguir botín sin riesgo.

      • Comida: permite recuperar puntos de vida, al acampar los héroes consumen comida recuperando puntos de vida y en caso de consumir muchas raciones también esfuerzo.
      • Antorchas: aumenta el nivel de luz que desciende a medida que se exploran las mazmorras.
      • Palas: permite quitar obstáculos en los pasillos, si no se dispone de una pala para quitar un obstáculo los héroes sufren grandes cantidades de daño.
      • Vendas: cura las hemorragias.
      • Antiveneno: cura las infecciones.
      • Hierbas medicinales: quitas las mermas que posea un héroe.
      • Llaves esqueleto: para activar curio y recoger botín sin riesgo.
      • Agua bendita: aumenta las resistencias.
      • Láudano: quita el efecto de horror.

      Aprovisionamiento Aprovisionamiento Acampada

      Misión, aprovisionamiento y acampada

      Combate

      Combate

      Jefes

      Entre las misiones y objetivos de juego está unas misiones especiales que implican  matar a un jefe. Son enemigos especiales más resistentes, poderosos y cada uno con sus propios comportamientos. Cada jefe es distinto, requiere de su propia estrategia y grupo de héroes para acabar con él sin que mate a ningún héroe.

      Los jefes aparecen varias veces a medida que se progresa en el nivel de las localizaciones. Al matarlos estos entregan abalorios especiales algunos de los cuales son poderosos y útiles para conseguirlos antes de entrar en la mazmorra oscura.

      Los jefes y sus estrategias de batallas son las siguientes:

      Estos son algunos jefes que hay que aniquiar para completar el juego.

      Bruja Cañon Nigromante

      Bruja, Cañon y Nigromante

      Profeta Sirena

      Profeta y Sirena

      Estrategia para completar el juego

      Este juego para completarlo requiere conocer en profundidad muchos de sus aspectos. Es difícil y a poco error que se cometa o mala suerte que se tenga un héroe puede morir incluso haciendo todo adecuadamente. Para completarlo más fácilmente los siguiente consejos de estrategia son de mucha ayuda, que héroes son los más adecuados para cada localización, qué edificios mejorar primero, que cantidades de provisiones llevar para cada misión y localización, estrategias para enfrentarse a los jefes y a la mazmorra oscura.

      Auqí solo doy unos pocos consejos generales, en intenet hay guías mucho más completas y detalladas.

      Aviso de la dificultad del juego

      Aviso de la dificultad del juego

      Estrategia de grupos de héroes

      De enviar a una misión un grupo de héroes equilibrado para la ubicación dependen en gran medida el éxito de la misión, junto con las habilidades adecuadas y las provisiones necesarias. Un buen grupo de héroes es aquel que tiene miembros para desempeñar las funciones principales de un grupo, tanque, realizador de daño, curador y soporte. Este esquema admite algunas variaciones dependiendo el objetivo. Enviar a una misión a un grupo de héroes inadecuado o desequilibrado supone sin dificultad la muerte de alguno de ellos.

      En las ruinas hay abundancia de enemigos impíos inmunes a la hemorragia, una buena cantidad de PV y ataques de daño y esfuerzo. Una opción es aturdir a los de las primeras posiciones y dañar a los de la trasera junto con infecciones, también es posible poner a los enemigos en la posición trasera en la delantera para atacarlos con ataques de cuerpo a cuerpo. El Cruzado y la Vestal tienen bonificadores de daño impíos. La Anticuaria puede incluirse si el objetivo es conseguir botín.

      • Héroes recomendados para las ruinas: Cruzado, Hombre de armas, Leproso, Doctor de plaga, Vestal, Abominación, Asaltatumbas, Bufón, Anticuaria, Caza recompensas.

      La foresta es el área más compleja. Algunos enemigos son rápidos, otros fuertes, a veces se prioriza la línea delantera y a veces la línea trasera, no hay un solo tipo de enemigos como impíos en las ruinas y eldrich en la cueva. El daño recibido proviene de infecciones, hemorragia o críticos de esfuerzo, también de movimiento. Los enemigos son vulnerables a la hemorragia mientras que la infección es inefectiva, tiene la habilidad de marcar que hace la habilidad de proteger o quitar la marca útil.

      • Héroes recomendados para la foresta: Ocultista, Bufón, Abominación, Hombre de armas, Bandolero, Leproso, Maestro canino, Caza recompensas, Diabla, Arbalestera.

      Los enemigos del laberinto son humanos y bestias, rápidos con buena evasión, con una combinación de los tres tipos de daño, físico, por hemorragia e infección y resistencias a infecciones. A diferencia de otras áreas el esfuerzo es más manejable. El aturdimiento para las posiciones traseras y las hemorragias funcionan muy bien. Las hierbas medicinales sirven para eliminar las mermas que infligen y activar curio.

      • Héroes recomendados para el laberinto: Hombre de armas, Diabla, Caza recompensas, Maestro canino, Bandolero, Asaltatumbas, Ocultista, Doctor de plaga.

      Algunos enemigos de la cala causan daño, otros esfuerzo, otros apilan daño de hemorragia rápidamente y en grandes cantidades, también tiene protección con habilidades de proteger. Los enemigos son vulnerables al aturdimiento e infecciones. Se necesita velocidad, aturdimiento, daño y cura, si se añade daño contra eldritch e impíos las misiones se pueden completar sin recivir grandes cantidades de daño.

      • Héroes recomendados para la cala: Cruzado, Hombre de armas, Doctor de plaga, Vestal, Ocultista, Abominación, Diabla, Asaltatumbas.

      Más análisis y consejos de grupos:

      Estrategia en misiones

      Al organizar una misión es necesario fijarse en cuál es el objetivo de la misión, en qué localización de desarrollar para elegir los héroes más efectivos, su duración para equipar el número de objetos en el inventario adecuados, el nivel de la misión, el porcentaje de patrulla del grupo y si algún personaje tiene fobia a esa localización o por el contrario está especiaizado en ella con ventajas.

      El primer paso de una misión es construir el grupo de héroes tratando de que haya uno que realice la función de tanque, provocador de daño, curador y soporte. Algunos héroes pueden hacer la función de otro en caso de necesidad aunque no sea su función principal. Después de una misión es recomendable hacer descansar a los héroes en caso de que hayan regresado con mucho esfuerzo y curarles los rasgos negativos y enfermedades para que en siguientes misiones no tengan penalizaciones. Un héroe con mucho esfuerzo no debería participar en un nueva misión ya que corre el riesgo de ganar un rasgo negativo si llega a 100 de esfuerzo o de morir si llega a 200 en una mala sucesión de ataques. Alguno de los héroes debería tener al menos más de 80% en la desactivación de trampas, al desactivarlas correctamente permite evitar su efecto negativo y al mismo tiempo curar un poco de esfuerzo en el héroe que la desactiva.

      El segundo paso es hacer que el resto de héroes que no participan en la misión aprovechen para que recuperen esfuerzo en la taberna o abadía, o curarles rasgos negativos y enfermedades en caso de que tengan. Los héroes de nivel bajo que tengan muchas taras no merece la pena invertir monedas en ellos, es mejor contratar un nuevo héroe que esté menos mermado.

      El tercer paso es organizar el aprovisionamiento en función de la localización, nivel y duración de la misión. Con estas recomendaciones generales en función de la duración. La comida, antorchas y palas son los objetos básicos imprescindibles comunes a todas las misioens que conviente que no falten en ningún caso, no tenerlos cuando se necesitan ocasiona en los héroes pérdida de puntos de vida y aumento en el nivel de estrés.

      Localización Ruinas Foresta Laberinto Cala
      Localización C M L C M L C M L C M L
      Comida 12 16-18 18-24
      Antorchas 8-9 14-16 16-18
      Palas 1-2 2 2-3 3 4-5 5-7 2 2-3 4 1 2-3 3
      Vendas 1 2 2-3 2 2-3 4 1-2 2 2-3 2-3 3-4 5-6
      Antiveneno 0 0 0 2 2-3 4-5 0 0-1 1 0 0 0
      Hierbas medicinales 1 2 2 1 1-2 2-3 2-3 4 5-6 1 1-2 2-3
      Llaves esqueleto 1 2 3-4 1 2 2 2 2-3 3-4 1 2 3-4
      Agua bendita 2 3 4 1 2 2-3 2 3 3-4 0 0-1 1-2

      Guías de localizaciones.

      Al finalizar una misión hay que conseguir el mayor botín posible, si no hay más hueco en el inventario y se está cerca de finalizar la misión es posible deshacerse de objetos de menor valor y que ya es seguro no son necesarios o se puede completar la misión sin ellos como llaves, vendas, agua bendita, palas e incluso comida y antorchas.

      Al finalizar la misión conviene curar los rasgos negativos que hayan conseguido por subir de nivel y las enfermedades para que en la siguiente misión en la que participen no tengan penalizaciones.

      Estrategia en los combates

      Los enemigos más peligrosos suelen estar en las posiciones 3 y 4 con lo que es recomendable eliminar estos primero o anularlos con aturdimiento o movimiento para cambiarles de posición de modo que no puedan utilizar sus habilidades más peligrosas. Para ello es necesario que en el grupo haya algún héroe capaz de hacer daño en las posiciones 3 y 4. Una vez eliminados los enemigos más peligrosos se acaba con el resto. Cuando queden un solo enemigo es posible aprovechar para utilizar habilidades de curación de puntos de vida o esfuerzo para afrontar el siguiente combate con buenos niveles, si el enemigo causa menos daño del que los héroes se curan se puede pasar algún turno sin matarlo empleándolo para curar, siempre teniendo en cuenta que no es posible pasar de esta forma muchos turnos ya que existe la posibilidad de que el enemigo llame a refuerzos.

      En el caso de las misiones que involucran jefes hay que conocer que estos están siempre en la sala más profunda de la mazmorra, con que que si se desea ir directamente a por su ubicación aunque oculta es conocida. Si es una misión mediana conviene aprovechar a descansar en la sala anterior para aplicar potenciadores a los héroes y realizar curas. Hay que tratar que los combates contra los jefes duren el menor número de turnos, a la larga ellos tiene la de vencer por las grandes cantidades de daño que hacen.

      Estrategia en edificios

      Al inicio de la partida el edificio más importante y el primero a realizar las primeras mejoras es la diligencia y el barracón para tener disponibles más héroes entre los que elegir y en un futuro tener la posibilidad de incorporar héroes de mayor nivel al cero. Los héroes incorporados con un nivel mayor que cero tiene la ventaja de que vienen con todas las habilidades adquiridas a su mayor nivel según el nivel del héroe. Mejorando los barracones es posible tener un grupo de hasta 28 héroes entre los que elegir y mejorando la diligencia permite en ocasiones tener disponibles héroes de hasta nivel 3.

      Los siguiente edificios a mejorar son la armería y gremio que permite mejorar los niveles de las habilidades de los héroes y el arma y armadura, algunas de las mejoras hacen que cuesten menos monedas las mejoras de los héroes .

      El siguiente edificio es el sanatorio para quitar rasgos negativos y enfermedades. Los siguientes serán la taberna y la abadía cuando los héroes vuelvan con cantidades importantes de esfuerzo. Los últimos edificios del poblado a mejorar son la superviviente que únicamente reduce el coste de adquirir las habilidades de acampada y el carromato nómada que permite tener disponibles más abalorios para comprar cada semana y comprar algún abalorio especialmente interesante.

      El orden de preferencia de mejora de los edificios es el siguiente: Diligencia, Gremio, Herrería, Sanitario, Superviviente, Abadía, Taberna. Los retratos es el recurso más raro de las reliquias, es preferible guardar estos que otros en caso de tener que desechar el botín.

      Los edificios se van desarrollando de forma progresiva una opción es aumentar la diligencia a 4 héroes por semana y 16 de capacidad en los barracones. El gremio y la herrería a nivel 2 es suficiente para llegar subir a nivel 3 a los héroes para hacer asequibles las mazmorras. Luego reclutas experimentados a nivel 1, es una buena mejora ya que proporciona héroes ya con las habilidades mejoradas y todas adquiridas, con una anticuaria se tarda menos en alcanzar las mejoras por sus beneficios en el botín. Barracones a 24. Gremio y herrería a nivel 3 permite reclutar héroes con nivel 2. El Sanatorio sabiendo que curios son peligrosos y cómo obtener su botín usando la provisión adecuada no es necesario, salvo alguna de los rasgos negativos a evitar no es necesario quitarlos hasta a partir del nivel 4. Para las mazmorras de nivel campeón es necesario el Gremio y la Herrería a nivel 5.

      Estrategia para el Darkest Dungeon

      Entrar al Darkest Dungeon para acabar con el mal es uno de los objetivos principales del juego, estas mazmorras son especialmente difíciles y hay que entrar con los héroes de nivel 6 con el máximo de provisiones. A diferencia de las mazmorras de otras localizaciones no son aleatorias, no hay obstáculos ni cofres que abrir con llaves, no hay probabilidad de recibir un ataque sorpresa después de descansar ni hay botín que recoger. Hay cuatro mazmorras a completar en esta localización. Los enemigos causan gran cantidad de esfuerzo por lo que no es aconsejable llevar los abalorios más poderosas que tiene penalizaciones al esfuerzo.

      Más información y wiki

      Darkest Dungeon aún con sus mecánicas aparentemente sencillas es un juego bastante complejo y con muchas combinaciones posibles con una dificultad elevada. El juego no explica muchos de sus conceptos con lo que aprender cómo funcionan requiere experiencia, en este aprendizaje a veces se comenten errores que en el peor de los casos cuesta la vida a alguno o varios de los héroes.

      Ha varias enciclopedias que recogen mucha información del juego desde las localizaciones, héroes y sus estadísticas, armas, armaduras, habilidades, abalorios, rasgos y enfermedades, enemigos y curio. Dos de estas enciclopedias más completas son la siguientes:

      Otras fuentes de información son ver jugar a otros jugadores en Twitch o algún video de YouTube en el que se aprende y explican algunas buenas combinaciones para facilitar el completar el juego. En Reddit también se encuentran hilos sobre temas específicos.

      Banda sonora original

      La banda sonora ambienta el juego de forma tétrica y decadente.

      » Leer más, comentarios, etc...

      Bitácora de Javier Gutiérrez Chamorro (Guti)

      Los archivos BGI y SVGA.BGI 4.00

      julio 03, 2021 07:31


      Seguimos hablando de retroinformática, continuando con el Turbo Anti-Virus, Central Point Anti-Virus y Microsoft Anti-Virus, pero esta vez nuevamente centrado en el mundo de la programación. Si exceptuamos El curioso bug de QB64 (on error) creo que el último fue Uptime 2 para DOS de 2017. El Borland Graphics Interface (BGI) apareció en 1987 y …

      Los archivos BGI y SVGA.BGI 4.00 Leer más »



      Artículo publicado originalmente en Bitácora de Javier Gutiérrez Chamorro (Guti)

      » Leer más, comentarios, etc...

      Variable not found

      ¡Microsoft MVP 2021-2022!

      julio 02, 2021 03:42

      MVP Award

      Es una gran alegría poder compartir con todos vosotros que Microsoft me ha reconocido por undécimo año consecutivo como Most Valuable Professional (MVP) en la comunidad de tecnologías para desarrolladores.

      En esta ocasión, la notificación del nombramiento me pilló completamente desprevenido, apagando un fuego de los gordos provocado por un error en un servidor de producción, por lo que el fragor de la batalla no me dejó celebrar apropiadamente el momento, ni disfrutar la tensa espera que lo precede. Pero ya al llegar la calma, sí que he podido sentir de nuevo la emoción y orgullo que supone seguir perteneciendo un año más a este selecto grupo de locos por el software y la tecnología. ¡Once años ya, uau!

      Muchas gracias a todos los que hacéis esto posible: a los que me estáis leyendo, porque sois los que día a día me dais motivos para continuar con este proyecto. No dudéis que sin vuestras visitas, comentarios y mensajes esto no tendría sentido. 

      Muchas gracias también al equipo del programa MVP, por su incansable labor en beneficio de la comunidad y su inestimable ayuda a los miembros del programa. 

      Y como no podía ser de otra forma, un agradecimiento infinito para mi mujer e hijas, por cederme el tiempo y espacio que necesito para desarrollar mi pasión.

      Por último, me gustaría aprovechar la ocasión para felicitar a los MVP que renuevan este año y animarlos a continuar regalándonos tanto conocimiento y entusiasmo. Y también, por supuesto, una afectuosa bienvenida a los que acabáis de recibir vuestro primer reconocimiento; disfrutad el momento, porque no lo vais a olvidar jamás :)

      ¡Nos vemos!

      Publicado en Variable not found.

      » Leer más, comentarios, etc...

      Picando Código

      Mini-Pique: Bashrc PS1 generator – genera el código para tu prompt bash con una interfaz drag & drop

      junio 28, 2021 12:00

      El sitio web basrchgenerator nos ofrece una interfaz gráfica amigable para crear el código para nuestro prompt de Bash de manera bastante sencilla:

      bash rc generator

      http://bashrcgenerator.com/

      Arrastramos los elementos de la izquierda al espacio del medio, y a la derecha vemos un preview y el código generado. Una vez generado, lo copiamos y lo pegamos en el archivo .bashrc en nuestro directorio home, y listo. Una herramienta bastante práctica, faltaría poder elegir colores para los distintos elementos y estaría súper completa.

      El post Mini-Pique: Bashrc PS1 generator – genera el código para tu prompt bash con una interfaz drag & drop fue publicado originalmente en Picando Código.

      » Leer más, comentarios, etc...

      Picando Código

      Semantic Logger – framework de logueo enriquecido para Ruby y Rails

      junio 25, 2021 09:00

      Semantic Logger es un framework enriquecido de logueo que permite producir resultados legibles para humanos y para máquinas 🤖
      Entre las características principales listan:

      • Archivos de log con texto coloreado para que los humanos lo podamos leer.
      • Archivos de log JSON con toda la información semántica para alimentar a las máquinas.
      • Escribir a múltiples destinos a la misma vez
      • Enviar los logs a un sistema centralizado de logueo, via JSON o adaptadores incluidos.

      Como implementa la interfaz estándar de logueo de Ruby, se puede reemplazar el log normal con Semantic Logging. Lo probé con el cliente oficial de Elasticsearch para ver qué tal, sólo hay que pasarlo como parámetro al inicializador:

      require 'elasticsearch'
      require 'semantic_logger'
      
      SemanticLogger.default_level = :debug
      SemanticLogger.add_appender(file_name: 'desarrollo.log', formatter: :color)
      logger = SemanticLogger['elasticsearch-ruby'] #nombre de la aplicación
      client = Elasticsearch::Client.new(logger: logger)
      client.cluster.health
      

      El archivo desarrollo.log generado por la biblioteca se muestra con colores en la terminal al leerlo con cat.

      Tiene también un Appender para Elasticsearch (que usar el cliente oficial Ruby Elasticsearch), lo que nos permite redirigir los logs directamente a Elasticsearch a un índice del formato “nombre-fecha” (por defecto "semantic_logger-"%Y.%m.%d""). Podemos especificar el nombre del índice, formato de fecha y más.

      SemanticLogger.add_appender(
        appender: :elasticsearch,
        url: 'http://localhost:9200',
        index: 'desarrollo-ruby'
      )
      logger = SemanticLogger['desarrollo-ruby']
      logger.info('¡Hola Mundo!')
      

      Con esto, podría usar el logger para una aplicación que usa el cliente Elasticsearch y redirigir los logs del cliente Elasticsearch a un clúster Elasticsearch a través del adaptador Elasticsearch que usa el cliente Elasticsearch y leer los logs con el cliente Ruby de Elasticsearch…

      Yo dawg I heard you like Elasticsearch logging

      Hay muchas cosas más para aprender de Semantic Logging, lo podemos hacer en su sitio web, o visitar el código fuente en GitHub.

      El post Semantic Logger – framework de logueo enriquecido para Ruby y Rails fue publicado originalmente en Picando Código.

      » Leer más, comentarios, etc...

      Blog Bitix

      Hemeroteca #19

      junio 24, 2021 06:00

      Blog Bitix

      Ya son casi 600 artículos los que he escrito en el blog, con este número de artículos en alguna ocasión al escribir un nuevo artículo a veces tengo que mirar si ya he escrito sobre ello o algo muy similar para no escribir lo mismo con otras palabras. Estos últimos meses muchas semanas solo he escrito un artículo en vez de dos, un poco por no dedicar tanto tiempo al blog pero más por falta de ideas que no me requieran mucho tiempo de investigación, ideas sobre cosas que me gustaría aprender y compartir tengo pero algunas de ellas para poder escribir me requerirían primero una buena cantidad de tiempo de investigación. Y algunos de esos temas sobre los que me gustaría aprender más son cosas muy específicas ni demasiado populares con lo que para el objetivo de seguir haciendo que el blog tenga más visitas no son los más adecuados.

      El trabajo de meses anteriores en los que escrito artículos con ese objetivo de hacer que el blog tenga más visitas se está notando ahora, este marzo ha sido el mejor mes en cuanto a visitas del blog hasta el momento, algunos artículos se han posicionado bien y son artículos sobre los que se hacen muchas búsquedas, ambas cosas combinadas hace que esos artículos contribuyan a aumentar el número de visitas. Haber estado publicando dos artículos durante buena parte del 2020 se ve recompensado ahora. Estos meses han sido el mejor registro de visitas y de ingresos en un mes de época no navideña, los meses de octubre, noviembre y diciembre. Los meses vacacionales siempre suelen bajar algo las visitas con lo que hasta septiembre la métrica de las visitas solo es comparable con los mismos meses de años anteriores.

      Los ingresos por publicidad de AdSense se han recuperado también e incluso superado comparado con los primeros meses de pandemia llegando a casi un 1 € diario lo que al final de mes llega a algo más de 30 € de forma holgada. Comparado con los 3 primeros años de vida del blog que no llegaba a los 5 € al mes es un aumento significativo, aún así seguro que es una cifra baja comparada con otros sitios. En el caso de mi blog en gran medida los ingresos dependen de AdSense y esto depende del número de visitas.

      Una cosas que me sorprende es que no son pocos los correos electrónicos que recibo con la intención de preguntar, querer insertar enlaces patrocinados en algún artículo o publicar un artículo ya redactado. Aunque no todos llegan a materializarse, también es otra forma de ingreso, aunque más esporádica e irregular que AdSense que continúa siendo la forma principal de ingresos. Sin embargo, alguno ya he rechazado también directamente, si el artículo patrocinado trata sobre apuestas o juegos azar son temas que tengo en mi lista de rechazados por el tema controvertido en sí y porque no están relacionados con los temas principales del blog. Aún así quizá en la siguiente solicitud de estos temas tantee a ver cuánto están dispuestos a ofrecer.

      No solo ha sido escribir nuevos artículos, también he realizado algunas pequeñas mejoras en el blog para mantenerlo actualizado. Para cargar las imágenes bajo demanda usaba la librería lozad, lo he cambiado para usar la forma estandarizada que ofrece HTML con el atributo loading=“lazy”. He actualizado las librerías con las que está construido el blog, principalmente Bootstrap para los estilos y jQuery para cierta lógica pasando a las versiones 5 y 3.6. También he realizado mejoras en la accesibilidad, alguna corrección de errores de los que informa Google Console como content layout shift. He añadido una nota informativa a los artículos en aquellos que tienen enlaces de afiliación para informar del hecho.

      Como ya comenté en hemerotecas anteriores tengo pendiente comprar un dominio propio y migrar a un hosting distinto de GitHub Pages. En caso de tener que migrar ya tengo más o menos analizado cual sería la opción, en este momento me decantaría por un servidor cloud en Linode con Nginx más Cloudflare como CDN. Esta infraestructura me permitiría aprender algunas cosas de administración de servidores, Ansible, Terraform y Lets Encrypt, quizá algo más. Pero de momento no he llegado al límite de GitHub Pages ni he recibido ninguna notificación de que deba cambiar, aún no tengo ninguna necesidad de cambiar con lo que quizá siga en GitHub Pages hasta que me lo notifiquen.

      También he seguido haciendo algunas mejoras en el script de instalación de Arch Linux. A petición de un usuario que quería soporte para el entorno de escritorio Deepin y como no es mucho esfuerzo añadir un nuevo escritorio lo he añadido. Ahora es posible configurar los módulos de mkinitcipio. Con la versión 6.0.0 del gestor de paquetes pacman de Arch Linux se le ha añadido el soporte para la descarga en paralelo de paquetes, alis ya lo soporta. Añadido paru como utilidad para los paquetes de AUR. También algunas corrección de errores y typos.

      Esta es la lista de los artículos que he publicado durante los seis primeros meses del 2021.

      Artículos sobre Java y programación.

      Artículos sobre juegos.

      Artículos sobre GNU/Linux o software libre.

      Otros.

      » Leer más, comentarios, etc...

      Picando Código

      Siete días en el Picandoverso – 4ta semana de Junio 2021 – 14 años después

      junio 23, 2021 11:00

      Pasen, pónganse cómodos y cómodas y sírvanse una taza de su brebaje de preferencia que esta semana estamos de festejo. Esta edición de siete días en el Picandoverso empieza referenciando el Big Bang, la precuela a esta saga de posts, el inicio del universo Picando Código:

      Siete días en el Picandoverso - 4ta semana de Junio 2021 - 14 años después

      Picando Código

      El 21 de junio se cumplieron 14 años desde que un joven Fernando empezaba a publicar posts sobre programación, software libre y más en un nuevo proyecto web. No sé si me hubiera imaginado que 14 años después este proyecto iba a seguir por acá*, pero esta semana lo festejamos. Iba a escribir un post aparte al respecto, pero creo que bastante autoreferencial vengo con estos posts de 7 días y otros. Así que lo festejamos así. Catorce años es bastante tiempo, me asombro a mí mismo de haber mantenido este blog como constante a pesar de habiendo vivido tantos cambios, reboots y remakes a lo largo de esta etapa.

      * El concepto de “acá” es relativo, el primer hosting del blog estuvo en Blogger, seguido de Linux Uruguay y finalmente me conseguí un servidor propio. Aunque creo que todo pasó en los primeros meses de vida del blog. Y de todas formas el “espacio virtual” que me imagino es este blog sigue siendo el mismo, se hostee donde se hostee…

      El año que viene se cumplen 15 años del blog, vamos a tener que hacerle una fiesta como es tradicional. O sorteo, o alguna otra cosa que logre de alguna forma generar más interacción o comunidad. Se aceptan sugerencias.

      Ruby

      💎 Julie Jones, desarrolladora autodidacta, viene publicando en Twitter varios piques de Ruby bajo la premisa de 100 Days Of Code. Lleva publicados 30 días, y entre sus tweets podemos encontrar piques bastante buenos sobre bloques, Procs, y varias cosas más de Ruby. Podemos ver los tweets en este enlace y seguirla en Twitter en @codewithjulie.

      💎 Nat Friedman, CEO de GitHub, twiteó que GitHub procesa 2.8 miles de millones de pedidos a su API por día, con un pico de 55.000 pedidos por segundo. La aplicación es un monolito Rails, confirmado por Raffaele Di Fazio (del equipo de plataforma de GitHub) con la biblioteca Ruby resque para procesos en segundo plano. También hacen deploy a producción entre 20 y 30 veces por día. Son datos bastante interesantes y confirman lo que ya sabemos: Ni Ruby ni Rails están muertos, y Rails sí puede escalar…

      💎 Una discusión en reddit plantea que “Ruby y Rails no habían tenido nada como Shopify hasta ahora” en lo que se refiere a soporte empresarial. Si bien tienen equipos dedicados tanto a Ruby como a Rails, otras empresas como GitHub, Stripe y Heroku (que le paga el sueldo al mismísimo Matz) también vienen dándole mucho soporte a Ruby. Pero lo interesante es que en el hilo de Reddit comentaron Peter Zhu (Ruby committer que trabaja en Shopify) y Richard Schneeman (contribuye a Rails, mantiene Puma y Sprockets, trabaja en Heroku) entre otros. Del lado de Shopify, Peter Zhu comenta que los proyectos Ruby principales en Shopify actualmente son: YJIT (más información por acá) y Variable Width Allocation (VWA) para MRI y TruffleRuby, una implementación alternativa de Ruby. También comenta que han colaborado directamente con GitHub tanto en Ruby como en Rails. Si bien es difícil medir el aporte de cada empresa, lo importante es que muchas empresas están trabajando y colaborando para hacer Ruby mejor, lo cual le asegura un futuro interesante y sano a mi lenguaje de programación favorito.

      💎 De este último hilo también un artículo interesante: Peter Zhu publicó un artículo sobre Garbage Collection en Ruby, además de tener varios artículos interesantes más sobre Ruby en su blog.

      💎 Y cuanto más compiladores JIT tengamos mejor (?). Hace tiempo Chris Seaton programó uno para Ruby, hecho en Ruby. Nunca se motivó para terminarlo, pero decidió liberarlo en GitHub. Sirve como material didáctico, en el README del proyecto mismo dice “Se supone que lo leas, no que lo uses”. Tiene varios experimentos y documentos, así que nos puede ayudar a entender cómo funciona un JIT y aprender más de Ruby y el compilador.

      💎 Textbringer es nada más y nada menos que un editor de texto inspirado en Emacs, escrito en Ruby. Dos de las mejores cosas del mundo combinadas: Emacs y Ruby 🙇

      💎 Mañana es el último jueves del mes, y hay meetup de Ruby Galaxy. Van a hablar Ramón Huidobro y Megan Tiu. En Twitter lo publican como: “MINASWAN” (Matt is nice and so we are nice – Matz es bueno así que somos buenos) ha sido parte de la comunidad Ruby desde su fundación, y una de las cosas más lindas que puedes hacer es entrenar y mentorear a alguien. Este jueves a las 19:00 UTC vamos a estar hablando con dos expertos en entrenamiento y amabilidad. Como siempre, se transmite a través de Twitch.

      Programación/Tecnología

      🗣 NotPinkCon es una conferencia de seguridad informática impartida por mujeres y realizada anualmente en la Ciudad de Buenos Aires, Argentina. El evento posee un enfoque técnico y la entrada es libre y gratuita para todas las personas que deseen formar parte. Abrieron su llamado a charlas para la cuarta edición de la conferencia, que va a realizarse online el sábado 25 de setiembre.

      👔 Graham King escribió un artículo (sátira) sobre un día en la vida de un ingeniero de software profesional: “Si eres estudiante en entrevista para un trabajo en software y preguntándote cómo es un día típico, acá van algunas notas de lo que hice el martes pasado. Fue un día bastante típico”. El artículo sigue contando tareas ridículas como dar vuelta una lista enlazada porque el turno de la noche la había puesto al revés. Por suerte tenía un pizarrón a mano para resolver el problema. Sigue burlándose de otras tantas pruebas inútiles que nos hacen en las entrevistas de trabajo. No me acuerdo dónde vi el enlace, pero el artículo es de Diciembre de 2020.

      🐳 En el sitio FAQForge se publica una referencia de comandos Docker que puede venir a mano para recordar comandos.

      📚 Humble Bundle tiene un paquete de ebooks de Data Science y Data Analytics (o Ciencia de Datos y Analítica de Datos). El paquete de Mercury Publishing incluye libros como Big Data Using Hadoop and Hive, Natural Language Processing and Machine Learning for Developers, Python 3 for Machine Learning, Artificial Intelligence, Machine Learning and Deep Learning y muchos más. Nuestro aporte apoya a las caridades Girls Who Code y Whale & Dolphin Conservation. Visita el paquete.

      📚 Otro paquete de Humble Bundle interesante: Advanced AI de Morgan & Claypool. Incluye libros como Deep Learning Systems, Efficient Processing of Deep Neural Networks, Introduction to Graph Neural Networks, Introduction to Logic Programming y más. Nuestro aporte ayuda a la caridad “It Gets Better Project”, cuya misión es comunicar a la juventud lesbiana, gay, bisexual y transgénero alrededor del mundo que las cosas se ponen mejor, y crear e inspirar los cambios necesarios para mejorar las cosas. Visita el paquete.

      Café

      ☕ En Slashdot publican que un estudio sugiere que tomar café puede reducir el riesgo de enfermedad crónica del hígado. Me alegra esta noticia, cuanto más lo estudian, más se demuestra que el café es lo mejor 🙌🏻. Asumo que en mi organismo la alta cantidad de café consumido contrarresta el daño al hígado generado por la alta cantidad de alcohol consumida…

      Cómics

      💬 Esta semana empieza Gamma Flight, la serie escrita por Al Ewing salida de las páginas de Immortal Hulk. También voy a recibir el primer número del segundo volúmen de Norse Mythology, el cómic de Dark Horse basado en el libro Norse Mythology de Neil Gaiman que se atrasó la semana pasada, y algunos títulos más. Una noticia interesante es que ya se anunció que la serie actual de Amazing Spider-Man por Nick Spencer termina en Setiembre con un número 74 (875 en la numeración original) final gigante. No he sido muy fan del escrito en esta serie, y si bien tuvo números buenos, estoy ansioso de ver quién va a seguir con el título después de Spencer. ¡Mi escritor ideal sería Chip Zdarsky!

      Y hasta acá llegamos a la vigesimosegunda semana contínua de esta saga de posts. Espero lo hayas disfrutado leer al menos tanto como yo disfruté escribiéndolo.

      Otros Siete días en el Picandoverso:

      Los posts de Picando Código pueden seguirse por:

      También estoy en Twitter y Mastodon, donde además de compartir lo que se publica en el blog publico alguna cosa más.

      El post Siete días en el Picandoverso – 4ta semana de Junio 2021 – 14 años después fue publicado originalmente en Picando Código.

      » Leer más, comentarios, etc...

      Picando Código

      Siete días en el Picandoverso: Arriba de una montaña

      junio 16, 2021 11:30

      Entre los destaques a nivel personal de los últimos siete días, el sábado pasado escalé dos munros más de Escocia. Con un amigo nos tomamos un tren a las 7 de la mañana hasta Glasgow y después Bridge of Orchy. De la estación de tren caminamos y escalamos ambas Beinn Dòrain (1076m) y Beinn an Dòthaidh (1004m). Hay 282 munros en Escocia y con estas dos llevo 5 en total, me quedan 277 por escalar. La aventura estuvo genial, tuvimos un clima relativamente bueno hasta que llegamos a ambas cimas donde la vista estaba completamente cubierta por las nubes. Pero está muy bueno alcanzar el objetivo y brindar con un tradicional trago de whisky dentro de una nube al llegar a la cima. Seguiré escalando montañas ⛰

      💉 Otro hecho a destacar es que me di la primera dosis de la vacuna contra el COVID. La vacunación viene bastante bien en Escocia y seguramente antes de fin de año hayan vacunado a toda la población. Entre el buen clima y que las restricciones se siguen flexibilizando, estoy saliendo más, tratando de aprovechar los fines de semana para dejar Edimburgo (¡estuve más de un año sin salir! 😱) y evitando usar la computadora y el teléfono. Sigo cada tanto la buena práctica de poner el teléfono en modo avión cuando ando paseando por ahí, ¡recomendado!

      El E3 fue de lo que más mantuvo mi atención en lo que respecta a información esta semana, particularmente las noticias de Nintendo, pero ya lo comenté en mi repaso de Nintendo en el E3 2021. Voy entonces a los pocos temas que estuve leyendo esta semana:

      7 días en el Picandoverso: Up on a mountain

      Ruby

      💎 Se publicó JRuby 9.2.19.0, una versión de correcciones para un problema con el flag --dev y un problema con zonas horarias en Windows.

      💎 Se publicó Rubocop 1.17 que corrige un error que hacía que Rubocop fallara usando el bot Layout/HashAlignment, soporte para pattern matching de Ruby 2.7 y mucho más.

      💎 El jueves de la semana que viene, 24 de junio es el último jueves del mes, así que hay meetup online de Ruby Galaxy. Está abierto el llamado a charlas. Veremos qué charlas se vienen en esta edición.

      Televisión

      ⚔ Vuelve He-Man en Masters of the Universe: Revelation. Es la primera vez que vemos la animación y el trailer está excelente. ¡Buena decisión musical! Me pregunto si el trailer habrá sido editado por Kevin Smith mismo:

      YouTube Video

      Se estrena en Netflix el 23 de julio. El cómic por Dark Horse que conecta la serie original de Filmation con la serie nueva sale el 7 de julio (¡mi cumpleaños!), a tiempo para ir adentrándonos en el mundo de Eternia.

      👿 Los miércoles se estrenan episodios nuevos de Loki, la nueva serie del Universo Cinematográfico de Marvel, en Disney+. El primer episodio el miércoles pasado estuvo genial, se viene con todo esta serie. Viajes en el tiempo, líneas de tiempo alternativas, full Marvel. Veremos qué tal el episodio de hoy.

      Dinosaurios

      🦖 Se confirmó que algunos dinosaurios en Jurassic World: Dominion (la tercera película de la nueva trilogía de Jurassic Park) van a tener plumas. Esto los hace un poquito más científicamente precisos.
      El paleontólogo Steve Brusatte (autor de El Auge y Caída de los Dinosaurios) estuvo trabajando en la producción de la película, ayudando a hacer que los dinosaurios fueran un poco más precisos. Tuvo una conversación en Twitter que me resultó suficientemente interesante como para reproducirla:
      “Van a tener que esperar y ver. Y recuerden es una película de taquilla y no un documental científico. La narración lleva muchos ángulos” dijo Steve. La otra persona respondió con más escepticismo y el odioso “veo mucha gente razonablemente preocupada por los efectos en el público” (¿qué gente? ¿”preocupada”? ¡Es una película ficticia sobre dinosaurios!). A lo que el paleontólogo respondió: “El público va a estar bien. A la gente le gusta las historias de origen, y esto es una forma sorprendente de contarlo. ¡Esperen y vean!”. En otro hilo cuenta un poco sobre uno de los dinosaurios nuevos de la película: Moros – un tiranosaurio del tamaño de un humano.

      Picando Código

      Me pregunto si alguien llegará a leer hasta acá además de Fernando del futuro que es el lector número uno de este blog…

      Otros Siete días en el Picandoverso:

      Los posts de Picando Código pueden seguirse por:

      También estoy en Twitter y Mastodon, donde además de compartir lo que se publica en el blog publico alguna cosa más.

      El post Siete días en el Picandoverso: Arriba de una montaña fue publicado originalmente en Picando Código.

      » Leer más, comentarios, etc...

      Picando Código

      Repaso de Nintendo en el E3 2021

      junio 15, 2021 11:11

      Nintendo E3 2021

      Terminó el E3 de este año, y por mi parte quedé conforme con las noticias de Nintendo. En el pasado 7 días en el Picandoverso comentaba algunas predicciones que me resultaban relativamente seguras, y con algunas acerté:

      algún luchador de Smash Bros., algún adelanto más de Splatoon 3 o la secuela a The Legend Of Zelda: Breath Of The Wild. Sería sorpresa ver algo de Metroid Prime 4 (o la trilogía Prime que tanto se viene rumoreando para Switch), o que revivan alguna de las propiedades intelectuales que llevan tiempo descansando como Star Fox o F-Zero. La mejor sorpresa sería que Breath Of The Wild 2 tenga fecha de lanzamiento en 2021

      Nintendo – con sus anfitriones Shinya Takahashi y Yoshiaki Koizumi – anunció varios títulos nuevos y algunos que ya conocíamos en su ya clásico formato de Nintendo Direct. No vimos nada de Splatoon 3, pero sí se anunció a Kazuya de la saga Tekken para Super Smash Bros. Ultimate.Va a haber más información en una presentación a cargo del genio ídolo mundial de Masahiro Sakurai, el 28 de junio a las 15:00 Edimburgo / 16:00 Madrid / 09:00 Ciudad de México / 11:00 Montevideo. Por más que no tengas interés en el juego o el luchador, éstas presentaciones valen la pena ver simplemente por la pasión y dedicación de Sakurai.

      Se mostró Worms Rumble, un nuevo título de la saga Worms en 3D que parece muy entretenido para jugar con más gente.

      Uno de los juegos nuevos anunciados que había visto antes del Nintendo Direct fue Guardians of the Galaxy. Se ve muy bien y me interesó bastante, pero tenía poca esperanza de verlo en Switch. Sin embargo apareció sorpresivamente durante el Nintendo Direct. Inicialmente mi reacción fue de entusiasmo. “Seguramente podría andar en Nintendo Switch con un poco de magia como hizo Panic Button con Doom”, pensé. Pero lamentablemente se trata de la versión “Cloud”, una forma de estafa que nos permite jugar desde nuestro Switch en otra computadora por internet, como lo que hace Google Stadia. Me pareció de cuarta que no especificaran en el Direct que era la versión Cloud, y tanto Google Stadia como las versiones Cloud en Switch me parecen una robada de plata gigante. De todas formas vale la pena mirar el trailer. Entre éste y el trailer de He-Man, Bonnie Tyler está cobrando buenas regalías este año 😆

      Super Monkey Ball fue un juego que tuvo bastante éxito en la época del Game Cube. Para celebrar su vigésimo aniversario, Sega va a publicar Super Monkey Ball: Banana Mania, un compilado de remasters de niveles de juegos de la serie. Y en la misma onda de “remasters”, Nintendo va a publicar Mario Party Superstars, disponible el 29 de octubre. En principio pensé que era un DLC para Mario Party de Switch, pero se trata de un título nuevo que trae de vuelta 5 tableros clásicos de los juegos Mario Party de Nintendo 64. Incluye 100 minijuegos de los títulos de Nintendo 64 y GameCube, con juego online incluido y soporte para controles con los botones (no necesariamente controles de movimiento).

      No mostraron nada de Metroid Prime 4, pero fue mencionado por Shinya Takahashi: “actualmente estamos trabajando mucho en el último título en la serie Metroid Prime: Metroid Prime 4, anunciado previamente. Pero hoy nos gustaría presentarles otra entrada nueva en la franquicia de Metroid“. Así se presentó Metroid Dread, uno de los títulos que más expectativa me generaron de esta presentación:

      YouTube Video

      Se trata del primer Metroid 2D en aproximadamente 19 años, un juego de acción basado en la exploración. El concepto nació hace 15 años, pero por falta de tecnología, se fue dejando de lado. Finalmente, tras trabajar en conjunto con Mercury Steam Entertainment en el desarrollo de Metroid: Samus Returns para Nintendo 3DS, decidieron implementarlo. Podemos ver más sobre la historia de su desarrollo en el video Metroid Dread – Development History.

      La historia pretende cerrar el arco iniciado en el primer título Metroid para NES, continuado por Metroid II: Return of Samus, seguido de Super Metroid, y finalmente Metroid Fusion para Game Boy Advance. La idea es que a medida que avanza la historia, los jugadores nos preguntemos qué significa esto del “final de la historia”. Pero a pesar de hacer hincapié en que la historia es importante, el juego está enfocado tanto a seguidores de la saga como a jugadores nuevos. Incluye un prólogo que va a informar sobre todo por lo que ha pasado Samus hasta ahora a todas aquellas personas que no hayan jugado un Metroid antes.

      El gameplay se basa en y mejora la idea original con algunas características nuevas. Entre otras cosas el traje de Samus cuenta con un dispositivo de camuflaje, la posibilidad de deslizarse al mejor estilo Mega Man en sus primeros títulos de NES y un imán para pegarse a paredes. El juego sale el 8 de octubre de 2021, va tener una edición especial con artbook, cartas con arte holográfico y más (estoy esperando que suban a la tienda oficial de Nintendo por estos lados para pre-ordenarlo). También va a estar acompañado de un set de dos amiibo (Samus y E.M.M.I). Increíble que Nintendo siga apostando por amiibo. Personalmente me gustan por lo coleccionable y la calidad es bastante buena, pero rara vez los uso con los juegos.

      Cruis’n Blast, un juego de carreras licenciado por Nintendo para las maquinitas (o arcades) en la saga Cruis’n originalmente desarrollada por Midway para Nintendo Ultra 64. Más de 30 pistas, hasta 4 jugadores a la vez, y las imágenes muestran una pista con dinosaurios, así que puede estar interesante…

      DLC para Doom Eternal: The Ancient Gods Part One, un juego que no ha tenido versión física para Nintendo Switch, y ocupa unos cuantos gigas en su versión digital. No lo he comprado por esto mismo, me niego a comprarlo en versión digital… Pero me encantó Doom 2016 y me pareció un excelente port en Nintendo Switch a pesar de los gráficos inferiores en relación a otras consolas más poderosas. Ojalá con el DLC disponible, eventualmente saquen una versión física donde el cartucho incluya todo el contenido.

      Se mostró también Tony Hawk’s Pro Skate 1 + 2, un juego que ya tengo pre-ordenado y sale el 25 de junio. Se ve excelente, ni siquiera necesito esos gráficos mejorados para querer jugarlo en Switch. Si lo sacaban con los gráficos y el soundtrack originales, igual lo iba a comprar 😬 . Es irracional frustrarme porque gastaran tiempo en mostrar juegos que ya habían mostrado, pero es el momento, y si no mostraron más es porque no querían mostrar otras cosas. De esa misma forma mostraron algo de Mario + Rabbids Sparks of Hope. Me compré el primer Mario + Rabbids para Switch y lo terminé cambiando por otro juego porque me aburrió, así que no le di mucha atención a éste título.

      Una noticia bastante interesante es que vuelve Advance Wars, una saga que pensé que Nintendo tenía abandonada (¡lo que implica que todavía hay esperanza para F-Zero y Star Fox!). En diciembre vamos a poder jugar los primeros dos títulos de esta serie con Advance Wars 1 + 2 Re-Boot Camp en Nintendo Switch.

      Finalmente llegamos al último segmento del Nintendo Direct con lo que muchos esperábamos: Zelda. Este año se celebran 35 años de la leyenda, y hace como 2 años que vimos por primera y última vez algo de la secuela a uno de los mejores juegos de la historia: The Legend Of Zelda: Breath of the Wild. Era obvio que Nintendo nos iba a hacer sufrir para verlo, y primero nos mostró un poco del contenido descargable de Hyrule Warriors: Age Of Calamity, la precuela de BOTW. Lo que vi me dieron ganas de volver a jugarlo. Creo que le dediqué unas pocas horas en su momento y no lo volví a tocar. El 18 de junio se publica el primer DLC que entre otras cosas nos permite usar a un Guardian y a Zelda en una moto!

      Acto seguido, Eiji Aonuma nos mostró un poco de The Legend Of Zelda: Skyward Sword HD, disponible el 16 de julio en Nintendo Switch. Siguió con un Game & Watch especial que conmemora los 35 años de La Leyenda de Zelda. Incluye los primeros dos juegos de NES: The Legend Of Zelda, Zelda 2: The Adventure of Link, la versión original de Game Boy de The Legend Of Zelda: Link’s Awakening (¡mi primer Zelda! ❤), un juego original de Game & Watch con Link y ¡UN RELOJ!

      Finalmente, por fin pudimos ver un poco de material de la secuela a The Legend Of Zelda: Breath of the Wild:

      YouTube Video

      Link está cambiado, ya veremos cuántos años han pasado. Parece tener un brazo poseído por la malicia de Ganon que le permite usar las runas desde el brazo. La princesa Zelda está en problemas (la vemos caer a un pozo), así que dudo que sea un personaje jugable como muchos fans vienen pidiendo. Hay una conexión con Skyward Sword, además de andar por Hyrule, el mundo se expande a los cielos y de las primeras escenas que vemos es Link cayendo por los cielos. Parece haber algún tipo de manipulación del tiempo, vemos una gota de agua retrocedes en el tiempo y la música que parece en ese segmento tiene voces en reversa.

      El trailer termina con unas notas de Zelda’s Lullaby y Eiji Aonuma nos pide que esperemos un poco más y que la fecha esperada es 2022. Por mi parte me quedo bastante contento de poder jugarlo en 2022. El original me dió muchísimas horas de entretenimiento y todavía hoy puedo volver y pasar unas horas de corrido recorriendo este nuevo Hyrule. Así que no tengo tanto apuro por jugarlo, Hyrule me espera…

      Otros dos títulos que personalmente me interesaron bastante del E3, pero fueron anunciados previamente fueron la secuela de River City Girls: River City Girls 2 y River City Girls Zero, un port del título Shin Nekketsu Koha: Kunio-tachi no Banka de Super Famicom publicado en 1994 con un nuevo opening anime, escenas manga y nueva canción, localizado por primera vez fuera de Japón y disponible en su gloria de 16-bits para Nintendo Switch este año. Ambos juegos van a estar disponibles en formato físico a través de Limited Run Games, lo que va a hacer crecer mi colección de juegos de Limited Run Games (creo que hasta ahora TODOS del género beat ’em up).

      Otros títulos anunciados durante el Nintendo Direct que o ya habían sido anunciados o en lo personal no me llaman tanto la atención como para comentarlos específicamente:

      Life is Strange Remastered Collection (más tarde este año), Life is Strange True colors (Sep 10), Dear Villagers, Two Point Campus, un Just Dance (¿sigue saliendo para Wii este juego?), Dragon Ball Z: Kakarot + A new power Awakens set – un RPG de acción que se ve bastante bien y seguro sea el sueño de seguidores de la saga, Mario Golf Super Rush, Monster Hunter Stories 2: Wings of Ruin, WarioWare: Get it together! (me encantaban los Wario Land, pero éste tipo de juego de Wario no me resulta tan entretenido), Shin Megami Tensei V, Danganropa, Fatal Frame: Maiden of Black Water, Strange Brigade.

      El post Repaso de Nintendo en el E3 2021 fue publicado originalmente en Picando Código.

      » Leer más, comentarios, etc...

      Navegapolis

      El 90% de la energía humana depende del compromiso, no del cumplimiento.

      junio 13, 2021 04:47

      Las empresas enfocadas en mejorar los objetivos de las personas y los departamentos, desarrollan culturas "naranjas" con tensiones competitivas y ambientes de trabajo tóxicos.

      Las que trabajan para mejorar el sistema en su conjunto, desarrollan culturas más planas con relaciones igualitarias. Resultan menos eficientes y crean ambientes sociales de buenrollismo, pero con resultados pobres.

      Lo curioso es que ambas teorías tienen sentido, y sin embargo ninguna funciona.

      La primera se basa en la teoría de equilibrio general de Gerard Debreu, premio nobel de economía de 1983: "Si tienes agentes autointeresados con información privada sobre su tipo, su esfuerzo y su entorno, la única forma de incentivarlos es mediante KPIs. Mediante compensaciones relativas a sus objetivos sectoriales".

      La segunda se basa en la teoría de la agencia de Jean Tirole, premio Nobel de economía de 2014 y afirma lo contrario: "Tienes que optimizar el sistema. No uses KPIs porque la optimización de los subsistemas, desoptimiza el sistema".

      Este dilema, según explica Fred Kofman en su conferencia magistral del CIEE 2018(1), no tiene solución y no se puede resolver en el ámbito en el que se analiza: en el del cumplimiento de objetivos; sin embargo se puede gestionar en el ámbito del compromiso de las personas. Si te interesa el tema, no te puedes perder la conferencia completa: Fred Kofman, Conferencia Magistral CIIE 2018, de la que estos 3 minutos son un sabroso aperitivo:

       

      (1) Congreso Internacional de Innovación Educativa 2018. Instituto Tecnológico y de Estudios Superiores de Monterrey.

       

       

      » Leer más, comentarios, etc...

      Header Files

      Apple M1 y productividad

      junio 08, 2021 10:00

      Ayer comienzó la WWDC 2021, la conferencia de desarrolladores de Apple y, como no, también tuvo lugar la tan esperada keynote en la que se anunciaron las siguientes versiones de sus principales sistemas operativos (aún estoy digiriendo todo lo presentado).

      En la WWDC 2020 Apple también anunció que dejaría de usar los procesadores de Intel para migrar a sus propios chips (los que luego se llamarían M1), basados en su experiencia con el desarrollo de procesadores para iPhone, iPad, Apple Watch, etc. Estos procesadores cuentan con un diseño tipo SoC (system on a chip), es decir, que integran en un única bloque gran cantidad de componentes como puede ser la CPU, GPU, controlador de memoria, controlador de disco, etc. Además, en lo que concierne a la CPU, consta de cuatro núcleos de alto rendimiento (llamados Firestorm 🔥), y cuatro de alta eficiencia energética (Icestorm 🧊); los primeros son mucho más potentes pero consumen 10 veces más energía (13,8 W vs 1,3 W).

      Los M1 logran superar con diferencia a las versiones más potentes de los i5 e i7 de Intel, e incluso llegan a igualar a los i9 en algunas pruebas de rendimiento. Tradicionalmente estos benchmarks miden la potencia bruta de un sistema (tales como operaciones por segundo, tiempo en tareas sintéticas), y al final resumen los resultados en un único valor fácilmente comparable. Ahora bien, de las cosas más impresionantes no es sólo que los M1 son rápidos, muy rápidos en estos tests sintéticos, sino que además parecen rápidos, y esto es el núcleo de este artículo (😉).

      El día a día de la gran mayoría de los usuarios no es llevar el sistema al límite todo el tiempo, tal y como hacen estos benchmarks. El usuario no suele medir cuántas cosas puedo hacer en X segundos, sino más bien cuánto tiene que esperar para que lo que está haciendo termine. El flujo más habitual se parece a un conjunto de ráfagas pico en medio de valles más largos de menos exigencia computacional. Y es precisamente en la transición de valle a pico donde el usuario percibe la velocidad del sistema, donde la juzga y, hasta cierto punto, donde le importa: que su equipo puede con lo que se le pide. Podemos tener 11 aplicaciones abiertas tranquilamente, pero es cuando abrimos la número 12, o accedemos a un vídeo en el navegador, o guardamos un documento, o aplicamos un efecto a una imagen, es en el pico cuando decimos que el sistema no puede más 😰.

      Acá es cuando el sistema operativo, macOS Big Sur (y el ayer anunciado macOS Monterey), toma la potencia bruta y el diseño del M1 y los convierte en experiencia de usuario: siempre que puede trata de tener los Firestorm (núcleos de alto rendimiento) libres, sin hacer nada, mientras que los Icestorm llevan toda la carga. ¡Y es perfecto! porque esos núcleos tienen capacidad de sobra para gestionar las tareas de los valles (y consumiendo muy poca energía), a la vez que dejan a los titanes libres para reaccionar rápidamente ante cualquier demanda inesperada. Con esto, el sistema no sólo es rápido, sino que lo parece, responde al usuario cuando lo necesita, lo mantiene con tiempo de espera 0 👍🏼.

      Hace poco leí un artículo muy interesante sobre gestión de proyectos que trataba un problema parecido (dejo el enlace al final): personalmente todos tenemos 11 tareas entre manos, y vamos bien, hasta que una de ellas entra en un pico: un problema difícil de resolver o que requiere un estudio más profundo, una reunión inesperada, un e-mail delicado, un bug de última hora, un ticket que se ha complicado y generado 7 nuevas tareas. Y a nivel de equipo pasa lo mismo: todos con varias responsabilidades, deadlines, funcionalidades que añadir, errores que resolver. Cuando nosotros o nuestro equipo estamos siempre al 100% (o más, que no es raro) no tenemos margen de maniobra y colapsamos: retraso en las tareas anteriores, estrés, impacto negativo en otros miembros del equipo). Como he dicho, esto aplica tanto a nivel de equipo (existen Icestorm y Firestorm para distintas tareas) como a nivel personal.

      Cuando se planifica un proyecto a largo plazo es normal dejar un margen de maniobra, un colchón de recursos (tiempo, dinero, personal) para imprevistos. ¿Por qué, entonces, esta estrategia se diluye y desaparece cuando pasamos a las tareas específicas y su planificación a corto plazo? 😳

      En estas situaciones de saturación el equipo tampoco puede dar un poquitico más porque simplemente no tiene tiempo para tonterías y tiene que hacer otras cosas antes de que se vaya a casa. Ese poquitico más es lo que muchas veces diferencia a lo mediocre de lo bueno, y a lo bueno de lo excelente, lo que marca la diferencia. Y terminan considerándose tonterías porque no son importantes, lo importante es estar a tope.

      Por último, hay que tener en cuenta que nuestra eficacia y eficiciencia no es uniforme: cada persona tiene sus ritmos, sus fortalezas y debilidades, la productividad cambia durante el día, cambia dependiendo de la tarea que estemos haciendo, de cuál hayamos hecho, de la reunión de la que venimos o la que vamos a tener y que nos tiene ocupado un hilo en segundo plano. Estar a tope hace que no podamos organizar nuestras tareas de la forma más adecuada, con tiempo para despejarnos, limpiar el escritorio el mental, re-organizar ideas, reiniciar el sistema (y sí, hablo también del ordenador, que no pocos requieren un borrón y cuenta nueva de vez en cuando también para funcionar mejor).

      Si dejamos que el equipo no esté al 100% será mucho más productivo, se sentirá mejor, tendrá más empodaramiento sobre sus tareas, no habrá retrasos porque se habrá planificado sobre ese sub-100%, se podrán adelantar trabajos, hacer tonterías que den valor añadido, innovar en ideas para las que nunca hay tiempo y que pueden ser el siguiente hit.

      ¿Cómo planificamos la gestión de nuestros núcleos? ¿Los usamos todos a tope para todo? ¿O dejamos que los Icestorm del equipo y personales estén disponibles para, entonces sí, darlo todo 💪🏼?

      Para quien quiera profundizar en este tema os recomiendo la lectura de Exigir el 100% de ocupación de las personas esta destruyendo la eficiencia de tu equipo y organización .

      » Leer más, comentarios, etc...

      info.xailer.com

      Importantes novedades en Xailer Profesional

      junio 07, 2021 02:20

      Después de haber convertido en un producto completamente gratuito nuestra versión base de Xailer y haber sido un completo éxito debido a su estupenda acogida. Ahora llega el turno de promocionar la versión profesional para hacerla muchísimo más interesante.

      A partir de la publicación de este artículo Xailer Profesional recibe dos importantes novedades:

      • La inclusión de muchos controles que hasta ahora estaban presentes únicamente en la versión ‘Enterprise’, como son:
        • Acceso nativo a servidores de bases de datos MySQL, MariaDB y SQLite.
        • Acceso a escáner WIA.
        • Acceso al editor de imágenes.
      • Una importantísima reducción en su precio, que pasa de 395 € a 245 €

      Y además como oferta de lanzamiento de esta nueva versión. Ofrecemos Xailer Profesional a precio de actualización, es decir, 145 € hasta el 30 de junio de 2021.

      Se acabaron las excusas para no empezar a utilizar bases de datos SQL y abandonar definitivamente los ficheros DBF. Además los DataControls de Xailer, incluidos en la versión profesional hacen que el proceso sea muy fácil e intuitivo. Nunca es tarde para hacer el cambio.

      Puede realizar su pedido directamente desde este enlace:

      https://www.xailer.com/wp/pedido-de-xailer/

      Comparativa de las distintas versiones de Xailer

      Xailer Personal Profesional Enterprise
      Librería de clases y funciones para Windows
      Entorno integrado de desarrollo
      Documentación eletrónica completa y en español
      Soporte técnico no preferencial
      Soporte técnico preferencial mediante foro privado
      Un año de soporte técnico y actualizaciones
      Código fuente de las clases que integran la librería gráfica, excepto algunos componentes internos
      Acceso a escaner WIA
      Acceso a editor de imágenes
      Componentes DataSets para acceso a distintos formatos de datos, DBF, ADS y SQL
      Soporte de componentes ActiveX
      Soporte nativo para MySQL y MariaDB
      Soporte nativo para MySQlite
      Soporte de bases de datos cifradas para MySQlite
      Soporte para descargas desde internet mediante multihilos
      Soporte de multihilos
      Control Web browser nativo basado Webkit
      Control Web browser nativo basado Microsoft Edge
      Acceso a bases de datos externas via WEB
      Controles avanzados y con estilo Windows 10
      Animaciones avanzadas en controles

      » Leer más, comentarios, etc...

      Header Files

      Introducción a boost::program_options

      mayo 07, 2021 03:16

      Introducción

      Toda aplicación de C++ (y C) tienen una función que sirve de punto de entrada, es decir, es la función que el sistema operativo llama cuando ha terminado de preparar al proceso y va a comenzar la ejecución del código propiamente dicho (puede que ocurran algunas cosas antes, pero no entraremos en eso). Esta función es la tradicionalmente conocida como main, y tiene la siguiente sintaxis base:

      int main(int argc, char *argv[]) {}
      

      Donde el valor de retorno es el valor de retorno del proceso, y los dos parámetros son el número de argumentos recibidos, y una lista de dichos argumentos previamente separados por espacios. El primer argumento es el nombre del ejecutable (aunque puede variar dependiendo cómo haya sido lanzado el proceso).

      C y C++ no limitan la forma de utilizar los argumentos de entrada, pero cada sistema operativo tiene sus estándares (costumbres). Por ejemplo, en Windows lo normal es usar / como prefijo para indicar opciones, mientras que Unix y Linux usan - para opciones en formato corto (una sola letra), y -- para el formato largo. Pero de nuevo, cada programador es libre de usar el formato que desee, aunque lo mejor es adherirse al estándar del sistema.

      La forma de extraer e interpretar los argumentos también se deja a merced de cada programador, y normalmente es un proceso tedioso ya que hay que lidiar con listas de opciones, formato de cada una, comandos no reconocidos, argumentos inválidos, etc. Por suerte, hay algunas ayudas como getopt en sistemas GNU, QCommandLineParser de Qt, y boost::program_options, que es mi preferida y de la que hablaré hoy. Aunque no pueda cubrirla al 100%, ya que es bastante extensa, trataré de indicar algunos de los casos de uso más frecuentes.

      Gestión de opciones

      Antes que nada, comentar una metodología de trabajo habitual cuando se desarrolla una aplicación con argumentos por línea de comandos: delegar todo este trabajo en una clase. Esto reduce la cantidad de código en el main (recomendable), desacopla la gestión de parámetros de su interpretación, abstrae de los detalles de implementación (nombre del parámetro, biblioteca para interpretarlos, tipo de dato, gestión de errores, etc.), y centraliza toda la variabilidad propia de los parámetros de ejecución. Así, un ejemplo (aleatorio) sería:

      // command_line_options.h
      #include <string>
      
      struct CommandLineOptions {
          std::string input_path;
          std::string output_path;
      
          std::string lang;
      
          int error_level = 0;
          bool verbose = false;
      
          bool parse(int argc, char* argv[]);
      };
      
      #include "command_line_options.h"
      
      int main(int argc, char* argv[]) {
          CommandLineOptions options;
      
          if (!options.parse(argc, argv)) { return 1; }
      
          // Use 'options'
          setLanguage(options.lang);
          initLog(options.error_level, options.verbose);
          // ...
      }
      

      boost::program_options

      Boost, como en muchas cosas, es la gran navaja suiza de C++ (otro tanto es la biblioteca Poco, que la dejo para quien no la conozca, así como mi querido Qt). De entre todos sus módulos, suelo sacar mucho provecho de program_options, que simplifica la gestión de argumentos de entrada de un programa. A lo largo del artículo usaré el alias po para referirme a este espacio de nombres.

      Su funcionamiento podríamos dividirlo en tres partes:

      • Definición de opciones
      • Análisis de los argumentos
      • Uso de las opciones

      Definición de opciones

      Acá listaremos todas las opciones que nuestra aplicación reconoce, indicando su nombre, tipo y descripción. Para ello usamos la clase options_description.

      En el siguiente ejemplo definimos los posibles comandos -? / --help, --input / -i, --output / -o, --language, --error-level, -v / --verbose:

      po::options_description po_desc("Allowed options");
      po_desc.add_options()
        ("help,?", "shows this help message")
        ("input,i", po::value<std::string>()->required(), "input path")
        ("output,o", po::value<std::string>()->required(), "output path")
        ("language", po::value<std::string>()->default_value("en"), "UI language")
        ("error-level", po::value<int>()->default_value(0), "error level")
        ("verbose,v", po::bool_switch()->default_value(false), "show verbose log")
        ;
      

      Cada opción se define con el nombre de la misma, pudiendo añadir el formato corto. A continuación se puede especificar el tipo (con un valor por defecto si fuese el caso), o si es obligatoria. Por último, se añade una descripción de la opción, que será la mostrada en la línea de comandos al solicitar la ayuda.

      En lo particular me gusta darle valores por defecto a las opciones no obligatorias; de esta forma se simplifica el flujo posterior, la validación de la entrada y hace nuestro código un poco más robusto ante omisiones.

      Un ejemplo de argumentos para nuestra aplicación anterior sería: app --input file.txt -o output.txt --error-level 5 -v.

      Parámetros ocultos

      Es posible definir los parámetros en diversos options_description. Los principales usos son el de poder discriminar cuáles se usan (por ejemplo, en base a la versión del sistema anfitrión, licencia del cliente, variables de entorno, etc.), o el de definir parámetros ocultos (ya veremos a qué me refiero).

      Boost sólo puede interpretar un único conjunto de opciones, así que la solución pasa por unificar las que necesitemos como paso previo a la interpretación:

      po::options_description po_desc_hidden("Hidden options");
      po_desc_hidden.add_options()
        ("gold", po::bool_switch()->default_value(false), "give you a lot of gold")
      ;
      
      po::options_description cmdline_options;
      cmdline_options.add(po_desc).add(po_desc_hidden);
      

      Sugerencia👀

      Como nota de experiencia, sugiero desactivar el formateo automático de código para esta sección, a fin de mantener cada opción en una línea. Esto mejora la lectura del código y mantiene más limpio el historial de cambios en el repositorio. Por ejemplo, si usáis clang-format se puede hacer de la siguiente forma:

      // clang-format off
      po_desc.add_options()
        // ...
        ;
      // clang-format on
      

      Análisis de los argumentos

      El siguiente paso es parsear (analizar gramaticalmente) la línea de comandos:

      po::variables_map po_vm;
      try {
        po::store(po::command_line_parser(argc, argv).options(cmdline_options).run(), po_vm);
        po::notify(po_vm);
      } catch (po::error &e) {
        std::cout << e.what() << '\n';
        std::cout << po_desc << '\n';
        return false;
      } catch (...) {
        std::cout << "Unknown error\n";
        std::cout << po_desc << '\n';
        return false;
      }
      

      Si la línea de comandos tiene algún error (normalmente parámetros desconocidos o formato incorrecto), capturaremos la excepción (mostrando el error si lo conocemos) y luego mostramos una ayuda para que el usuario sepa cuál es la sintaxis correcta (std::cout << po_desc << '\n'). Nótese que en esta línea no usamos cmdline_options sino po_desc, que es la que contiene la lista de opciones pública; si mostrásemos cmdline_options estaríamos revelando todas las opciones del programa (y en este ejemplo no nos interesa). Por último, indicamos que la función parse ha fallado devolviendo un false.

      Uso de las opciones

      Ahora tenemos la línea de comandos descompuesta en las opciones que hemos definido, y almacenadas en la variable po_vm; solamente nos queda poner los valores correctos a las variables.

      Existen varias formas de acceder a estas opciones, aunque las tres más comunes son:

      • Verificando si la opción ha sido escrita por el usuario: po_vm.count("option_name") > 0.
      • Sabiendo que existe (bien por el método anterior, o porque hemos indicado que siempre tenga un valor por defecto), podemos acceder a su valor: po_vm["option_name"].as<T>, donde T es el tipo de datos que hemos indicado en la definición. Aviso⚠: acceder de esta forma a una opción no definida o sin valor lanza una excepción. Por mi parte, en lo posible trato de que todas las opciones no obligatorias tengan un valor por defecto.
      • Asociando una opción a una variable: esta opción es muy práctica, aunque no la suelo usar simplemente porque me gusta separar mentalmente el análisis de la interpretación, sabiendo que no tengo valores a medias en caso de error. Para asociar una opción a una variable solamente tenemos que indicarlo en la definición de la opción: ("language", po::value<std::string>(&lang), "UI language").
      if (po_vm.count("help")) {
        std::cout << po_desc << '\n';
        return false;
      }
      
      input_path = po_vm["input"].as<std::string>();
      output_path = po_vm["output"].as<std::string>();
      
      lang = po_vm["language"].as<std::string>();
      
      error_level = po_vm["error-level"].as<int>();
      verbose = po_vm["verbose"].as<bool>();
      

      Otros tópicos

      Argumentos posicionales

      Los argumentos posicionales son aquellos cuya semántica viene dada por su posición en la lista de argumentos. Por ejemplo app input.txt output.txt podría tener dos argumentos posicionales, donde el primero representa al ruta del fichero de entrada y el segundo la ruta del de salida.

      De nuestro ejemplo anterior, supongamos queremos que el fichero de entrada y el de salida sean posicionales:

      po::positional_options_description po_pos;
      po_pos.add("input", 1);
      po_pos.add("output", 1);
      

      Los argumentos se seleccionan en el orden en el que se definen, y se asocian a la opción que se indica. El número después del nombre indica cuántos argumentos de ese tipo se esperan, donde -1 indica ilimitados (como sugiere la lógica, no se pueden definir nuevos argumentos posicionales una vez se define uno ilimitado).

      Por último, es necesario añadirlos al analizador:

      po::store(po::command_line_parser(argc, argv).options(cmdline_options).positional(po_pos).run(), po_vm);
      

      Argumentos en UNICODE

      Me gustaría hacer un comentario aparte acerca de cuando los argumentos no usan una codificación ANSI: si necesitamos leer un fichero y su ruta (path) viene dado como argumento de la línea de comandos, es probable que dicha ruta contenga caracteres fuera del espectro de ANSI: vocales acentuadas, la española Ñ, caracteres en cirílico, un nombre de usuario en chino tradicional, etc. Por supuesto, aunque este quizá sea el escenario más tradicional, podríamos encontrar el mismo problema en muchos otros.

      Este problema lo planteé en Stack Overflow hace ya un tiempo; expongo acá la respuesta como complemento del artículo. Importante⚠: esta solución está enfocada a Windows.

      • Cambiar el punto de entrada para que acepte cadenas de texto en UNICODE: int wmain(int argc, wchar_t* argv[]).
      • Usar boost::program_options::wvalue en lugar de boost::program_options::value cuando el argumento espere valores en UNICODE.
      • Usar un tipo de datos std::wstring para estos argumentos.
      • Usar boost::program_options::wcommand_line_parser en lugar de boost::program_options::command_line_parser para aceptar la lista de argumentos en wchar_t*.

      Aun más

      Este breve tutorial deja por fuera algunas otras opciones, que enumero a continuación:

      • Uso de sintaxis no estándar).
      • Permitir argumentos no registrados (por ejemplo, para re-enviarlos a otro comando).
      • Uso de argumentos provenientes del punto de entrada WinMain.
      • Validadores personalizados (por ejemplo, que sea requiera un e-mail y sea el propio Boost el que compruebe que la entrada corresponde con un formato de e-mail válido).

      Estos tópicos están documentados en este anexo de Boost.

      Ejemplo completo

      Se puede probar la mayoría del código de este artículo en vivo.

      » Leer más, comentarios, etc...

      Meta-Info

      ¿Que es?

      Planeta Código es un agregador de weblogs sobre programación y desarrollo en castellano. Si eres lector te permite seguirlos de modo cómodo en esta misma página o mediante el fichero de subscripción.

      rss subscripción

      Puedes utilizar las siguientes imagenes para enlazar PlanetaCodigo:
      planetacodigo

      planetacodigo

      Si tienes un weblog de programación y quieres ser añadido aquí, envíame un email solicitándolo.

      Idea: Juanjo Navarro

      Diseño: Albin