rubendelblanco / openrpg

Rolemaster manager
5 stars 3 forks source link

Implementación para un simulador de dados #6

Closed danirod closed 5 years ago

danirod commented 5 years ago

⚠️ Partamos de la base de que todo en este endpoint podría estar mal y que por ello debería ser cautelosamente revisado para comprobar que sigue las normas o que no hace alguna pirula extraña que deba ser corregida.

Resumen

Este Pull Request incorpora una acción para simular el lanzamiento de dados.

Funcionamiento de la API

El endpoint está declarado como /api/roll y acepta una cadena de dados a procesar a través del query param q. Este query param es validado y debe tener como valor un string con el formato adecuado, usando la notación estandar para dados de rol sin modificadores.

Sea una secuencia un string de la forma xDy, cuyo significado es lanzar x dados de y caras, el parámetro q puede ser una secuencia, o una lista separada por comas de secuencias. Si lo que se le proporciona no es válido, el endpoint devuelve un error directamente.

Ejemplos de llamadas válidas:

Ejemplos de llamadas inválidas:

Cuando la llamada es válida, el endpoint procesa los lanzamientos de los dados y devuelve un objeto con el siguiente formato:

{
    "dices": 2,
    "outcomes": [5, 4],
    "size": [5, 6],
    "sum": 9
}

en el que:

Los arrays outcomes y sizes coinciden en ordenación. En otras palabras, para cada dado i que se ha tirado, size[i] es el número de caras del dado i, y outcomes[i] es el valor obtenido en el dado i.

No he considerado apropiado usar una estructura más refinada para preservar el orden, al considerar que en la vida real, la entropía propia del hecho de lanzar un dado en una mesa hace que sea impredecible la posición final de cada uno de estos, así que nada te puede garantizar que de izquierda a derecha, los dados que hayas lanzado coincidan en orden con la secuencia planeada.

Cuando la llamada no es válida, el endpoint devuelve un objeto JSON con un único key denominado errors, que es un array con la representación de los distintos errores que se han obtenido:

{
    "errors": [
        "You must provide a roll set."
    ]
}

Detalles de implementación

Tal como se comenta en #1, es importantísimo que si se procesa una secuencia de la forma 2d10, como es una manera simbólica de representar lo mismo que 1d10,1d10, se tiren realmente dos dados en el rango [1,10]. Lanzar un único dado en el rango [2,20] estaría mal.

El lanzamiento de dados se hace en este momento mediante llamadas directas a la función random_int, ya que la documentación aclara que en PHP 7 esta función es criptográficamente segura para generar números pseudo-aleatorios tales como desordenar una baraja de cartas, al usar /dev/urandom o APIs similares.

Puntos flacos de este código

Testeo aquello que sea determinista. Por ejemplo, que si q=2d5,1d4, se cumpla que dices equivale a 3, y que sizes equivale a [5, 5, 4]. Testear código pseudoaleatorio es frágil de naturaleza.

Sin embargo, hacer un test de estrés que simule el lanzamiento de 1000 dados y que compruebe que se obtienen unos resultados estadísticamente aceptables podría estar bien. Por ejemplo, que si haces un lanzamiento 1000d10 y agrupas los resultados por valor, cada uno de los valores debe rondar el 10% de ocurrencias dentro de la distribución.

Por otra parte, se podrían hacer tests de propiedades que aseguren que al código no se le va a ir y va a devolver un outcome superior al número de caras del dado. Sin embargo, normalmente los lenguajes de programación y las librerías de testeo tradicionales para tests unitarios no incluyen la API para hacer fáciles de redactar este tipo de tests.

Al margen de esto, los nombres de las variables y de las funciones también pueden ser mejorables, ya que no conozco si existen mejores palabras que $command para referirse al acto de lanzar dados, por ejemplo.