El destino está escrito

30 enero, 2014

A medida que los juegos han ganado en complejidad, tanto técnica como narrativa, una opción que ha tomado gran relevancia es la de poder grabar la partida. Si bien esto no es algo directamente relacionado con los más recientes avances tecnológicos, ya que en la época de los 8 bits ya era posible guardar partidas en algunos juegos, sí que a fecha de hoy es casi impensable no poder hacerlo. No es factible tener que acabarse un juego de una sentada, o dejando el ordenador encendido todo el fin de semana para no perder el progreso. Sobretodo, si hablamos de juegos de gran duración o basados en mundos virtuales, multijugador o no.

La posibilidad de guardar la partida impacta enormemente en la dificultad del juego desde una perspectiva general, ya que ofrece un mayor margen de error a la hora de superar dificultades.  Escoger cómo y cuándo se puede guardar es una decisión de diseño que debe cuidarse muy bien. En ese sentido, a mi me parece especialmente interesante la opción de «guardado/carga rápido» (quicksave/quickload), ya sea de manera intrínseca, o debida al hecho de crear capturas del estado de memoria en un emulador. Cuando esta opción es posible, es fácil caer en el vicio del «savescumming» (¿escoria guardadora?): guardar cada 10 segundos, constantemente, de modo que puedas recuperar el estado del juego al menor error. Ataco al enemigo. Si fallo, recupero y vuelvo a intentar. Si acierto, guardo partida de nuevo. Tomo una decisión importante en la narrativa. Si no me gusta el resultado, recupero y reintento, en vez de aceptarla orgánicamente. El margen de recuperación de errores ya no es a nivel de etapa o reto, sino que se microsegmenta a acción por acción.

VATS
35% + quickload + paciencia = 100%. Imagen del videojuego Fallout 3, propiedad de Bethesda Softworks utilizada bajo criterios de Fair use.

En ciertos juegos, esta táctica lleva a situaciones muy interesantes. Hablo de juegos donde las acciones no se resuelven en base a la habilidad del jugador, sino en base a probabilidades. Normalmente hablamos de juegos de rol (e.g. Fallouts, Knights of the Old Republic, toda la família basada en Dragones y Mazmorras, etc.) o algunos juegos de estrategia (e.g. Fire Emblem, Advance Wars, etc.). En todos ellos, la posibilidad de éxito en una acción (e.g. ataque, defensa, uso de una habilidad, etc.) depende exclusivamente de una tirada de dados. La solución lógica no debería variar mucho: antes de cada «tirada», guardas partida. Si fallas, recuperas y reintentas. Si aciertas, guardas y adelante. Con ello, el hecho de que el personaje disponga de niveles de habilidad se vuelve irrelevante, ya que con mucha paciencia, todo es posible aunque las posibilidades de éxito se calculen en un 5%. Sin embargo, más de uno se lleva la sorpresa: haces una una tirada con un 95% probabilidad de éxito, pero por más que recuperas la partida, fallas siempre ¿Es posible tener tan mala suerte? ¿El ordenador me tiene manía?

El motivo se debe al modo como los ordenadores trabajan con números al azar. Simular un porcentaje de probabilidad es tan simple como hacer que el ordenador genere un número al azar entre 1 y 100. Si el valor es igual o inferior al porcentaje, tienes éxito. En caso contrario, fallas. Sin embargo, esto tan evidente no es tan sencillo para un ordenador, ya que generar valores que son realmente al azar es una tarea complicada, no solo para el ordenador, sino en el mundo real. Ni siquiera los humanos somos buenos generando valores aleatorios, ya que tenemos un sesgo a compensar valores que llevan mucho tiempo sin aparecer.

En un ordenador hay distintas opciones, pero incluso aquellas que pueden parecer satisfactorias a simple vista, como basarse en la temperatura de la CPU, o coger los bits de menos peso del reloj del sistema, en realidad no son muy buenas, ya que no distribuyen bien el rango de valores posibles y tienden a la repetición. En la mayoría de aplicaciones, los ordenadores generan valores a partir de los llamados «generadores pseudoaleatorios» (PRNG, Pseudo Random Number Generator). Estos generan secuencias de valores que semejan aleatoriedad, pero no lo son realmente. Para hacerlo, se basan en un valor base, llamado «semilla» (seed). A partir del valor de dicha semilla, se va aplicando sucesivas veces una función matemática sobre cada nuevo valor, creando una secuencia de valores. Por lo tanto, dada una semilla, esta siempre generará la misma secuencia de valores pseudoaleatorios (de ahí lo de pseudo).

Existen diferentes aproximaciones para crear PRNG. En la figura, se muestra un ejemplo de un tipo muy popular y simple llamado LFSR (Linear Feed Shift Register, Registro de de Desplazamiento con Realimentación Lineal). Este PRNG no es perfecto, pero sirve para tener una idea del concepto secuencia de valores pseudoaleatorios.

LFSR
Generación de valores entre 1 y 255 mediante un LFSR de 8 bits

El generador cambia de valor en base a aplicar una función XOR sobre algunos de los bits que contiene. Para generar un nuevo valor, se elimina el bit menos significativo, se desplaza el resto, y el bit más significativo lo ocupa el resultado de aplicar la función XOR sobre el valor original. La particularidad que hay que tener presente es que, dado que los sucesivos valores que se generan están relacionados entre si (dado un valor X, siempre generará el mismo valor Y a continuación), tarde o temprano se repetirá la secuencia. También, hay que vigiliar con el valor todo 0’s, ya que es un estado absorbente que hay que evitar. Si se llega a este valor, el registro se quedará encallado allí para siempre. Sin embargo, si el registro está bien diseñado, se puede garantizar que se agotarán todos los valores posibles antes de que suceda, volviendo al valor de la semilla, sin pasar nunca por el cero. Para ello, las funciones XOR deben cumplir una condición: formar un polinomio primitivo. El ejemplo se basa en el polinomio primitivo de grado 8 representado como 100011101 en formato cadena, pero podéis buscar otros aquí.

El resultado es que, dado que la secuencia de valores pseudoaleatorios o la propia semilla se guardan con la partida, el destino está escrito. Recuperar la partida no vale para nada. Sólo puede salvarte llevar a cabo alguna acción que modifique el estado actual del generador y lo haga avanzar en la secuencia de valores. Quizá retrasando el ataque, o llevando a cabo alguna otra acción intermedia. Pero simplemente recuperar la partida no funcionará, ya que dada la misma secuencia de movimientos por parte del jugador, el resultado de sus acciones siempre será el mismo de manera determinista. En algunos juegos esto pasa, en otros no, y podemos ganar por fuerza bruta a base «savescumming«.

Pero a veces, hay que aceptar el destino. O tener varias partidas guardadas…

Joan Arnedo es profesor de los estudios de Informática, Multimedia y Telecomunicaciones en la UOC. Director académico del Postgrado de Diseño y Programación de Videojuegos e investigador en el campo de la ludificación y los juegos serios en el eLearn Center. Su experiencia se remonta a cuando los ordenadores MSX poblaban la Tierra…

(Visited 34 times, 1 visits today)
Autor / Autora
Joan Arnedo Moreno
Comentarios
Víctor30 enero, 2014 a las 6:06 pm

Recuerdo que cuando leí por primera vez un libro para aprender Visual Basic utilizando la versión 6.0 (allá por el año 2000) había, además de la instrucción rnd(), otra función llamada randomize() que cambiaba la semilla para tener un sistema totalmente aleatorio. Sigo programando en VB (ahora .NET 2010) pero no sé si seguirá vigente ese sistema… curioso post, sin duda.

Responder
Joan4 febrero, 2014 a las 12:43 pm

Mi primer contacto es con el RND del MSX-Basic (por cierto, también de Microsoft) 🙂

Ir cambiando la semilla de vez en cuando, y hacer un «reset» del generador, es útil, ya que si generamos muchos valores pseudoaleatorios, al final se volverá a repetir la secuencia (daremos la «vuelta al marcador»). Y en consecuencia, los futuros valores serían predecibles en posteriores iteraciones.

Claro que lo que uno ha de decidir entonces es como cambiar la semilla: Que lo ideal sería elegir un valor aleatoriamente, pero entonces tenemos el pez que se muerde la cola… («randomize» por defecto usa el reloj del sistema).

Responder
Víctor4 febrero, 2014 a las 3:12 pm

Yo no le encuentro problema alguno respecto a la aleatoriedad. No soy ningún experto en algoritmos de números aleatorios, pero el randomize() generaría una nueva semilla a partir del reloj del sistema, y con esa semilla se irían generando toda una serie de números aleatorios con el rnd(). El problema, a mi parecer, vendría si el randomize generara la misma semilla cada cierto tiempo con una frecuencia fija. Por ejemplo, que 3 días después (es un suponer) sale la misma semilla, pero esa probabilidad la veo muy baja aún cuando el usuario ejecute el programa a la misma hora porque con la precisión que puede tener el reloj del sistema, con sólo ejecutar el algoritmo 1 milésima de segundo después puede generar una semilla distinta.
Ya digo que no soy un experto en este tema y no he visto la implementación de estas dos instrucciones, pero a mi me parecía que la aleatoriedad de estos números estaba más que asegurada.

Responder
Joan5 febrero, 2014 a las 5:21 pm

Sí, en el fondo tienes razón. Lo que hay que tener en cuenta cuando hablamos de aleatoriedad es que nos podemos referir al término desde un punto de vista matemático (¿Cumplen los valores creados por este generador los criterios de aleatoriedad?) o de un modo más coloquial (¿Es posible para un observador casual prever el próximo valor de la secuencia?).

Cumplir la primera premisa no es trivial, y es a la que hacía alusión. La segunda ya no es tan compleja, y desde el punto de vista práctica, ya es lo que dices.

Por ejemplo, está demostrado que los humanos somos muy malos generando números aleatorios, pero va a ser difícil que si me voy inventando números, alguien acierte el siguiente que diré.

Precisamente, sobre este tema en Corsera hay un curso muy interesante ahora mismo en el que se habla de estos temas y matices:

https://www.coursera.org/course/randomness

Responder
Víctor6 febrero, 2014 a las 11:45 am

Me quedo más tranquilo después de ver que la aleatoriedad está asegurada, aunque luego resulte que no es tal aleatoriedad.

Responder
César10 febrero, 2014 a las 12:59 pm
Deja un comentario