Algoritmo de generación de laberintos en el desierto

GreyShock

Algoritmo de generación de laberintos en el desierto

Hola hamijos! Estoy trabajando en un algoritmo de generación de laberintos en el desierto (sea lo que sea eso) y me lo he pasado tan bien creándolo que no he podido resistirme a compartirlo con vosotros.

Aún está muy verde y se podría seguir puliendo y ampliando hasta la saciedad, pero para mis propósitos malignos me sirve perfectamente. Lo libero al mundo por si a alguien le es útil, os apetece dar ideas para mejorarlo, o para reiros de mí. Al gusto.

Características de un laberinto en el desierto

A nivel de Game Design, el reto de este laberinto no es solo resolverlo (de hecho, es bastante fácil de recorrer del inicio a la salida) si no sobrevivir a él.

Para ello, hay que sobrevivir al cansancio, a la sed y a los enemigos. Moverse una casilla en el laberinto cuesta 1 hora, y eso aumenta la sed y el cansancio (no entraré en detalles de diseño ya que lo que importa aquí es la generación en sí), y derrotar enemigos consume munición y energía/sed. Además, hay un tiempo límite de horas para resolver el laberinto.

Así que este laberinto no solo presenta muros si no una serie de tipos de terreno:

Normal - Arena pura y dura
Dunas - Arena difícil de atravesar (cuesta el doble de horas)
Cactus - Se puede pinchar para obtener algo de agua
Pozo - Inviertiendo unas horas en cavarlo puedes conseguir agua en abundancia
Sombra - Para descansar (no se puede descansar bajo el sol o mueres)
Restos de batalla - Para rapiñar recursos (munición y agua)
Abismo - No transitable (paredes del laberinto)
Patrulla - Enemigo de bajo nivel
Campamento - Enemigo de alto nivel

Código (sintaxis GML)

Al iniciar el nivel lo único que hago es...

//set stage
terrain = p5_generate_terrain(30,30);

A esta función le pasamos el ancho y el alto en casillas que queremos que tenga el laberinto.

Después, esta función se pone manos a la obra para devolvernos un laberinto montado en un array de 2 dimensiones.

///p5_generate_terrain(wide,height);
//generates the desert map

//wide x height
var wide = argument0;
var height = argument1;

var t = 0; //var for holding the entire terrain information

//generation
for(var i = 0;i<height;i++){
    for(var j = 0;j<wide;j++){
        t[i,j] = p5_get_square();
    }
}

//fix terrain depending on setting rules
t = p5_terrain_check(t);

//generated terrain
return t;

Básicamente un bucle anidado que va rellenando las casillas con tipos de terrenos.
Así es como se crea una casilla:

///p5_get_square();

//type of terrain rate
var normal = 11;
var dune = 10;
var cactus = 10;
var water = 6;
var shadow = 14;
var scavenge = 7;
var abyss = 20;
var patrol = 13;
var camp = 13;

//set scale of success 0-15, 15-25, 25-34...
dune+=normal;
cactus+=dune;
water+=cactus;
shadow+=water;
scavenge+=shadow;
abyss+=scavenge;
patrol+=abyss;
camp+=patrol;

var type = 'normal';

//random terrain selection
var square = irandom(camp);//select the higher value for the roll
//normal
if(square<normal){
    type = 'normal';
}else
//dune
if(square<dune){
    type = 'dune';
}else
//cactus
if(square<cactus){
    type = 'cactus';
}else
//water
if(square<water){
    type = 'water';
}else
//shadow
if(square<shadow){
    type = 'shadow';
}else
//scavenge
if(square<scavenge){
    type = 'scavenge';
}else
//abyss
if(square<abyss){
    type = 'abyss';
}else
//patrol
if(square<patrol){
    type = 'patrol';
}else
//camp
if(square<camp){
    type = 'camp';
}

//return terrain type (string)
return type;

Este código lo que hace es tirar un dado y devolver un tipo de terreno al azar, pero la frecuencia en la que aparece cada tipo de casilla está delimitada al principio con ese puñado de variables.

Si os fijáis, no es un sistema porcentual (al menos sobre 100), si no que son valores relativos entre ellos, por eso se suman todos, y se utiliza el valor más alto para determinar "las caras del dado" que usaremos para decidir como rellenamos esa casilla.

Así que si ahora pusiéramos Cactus = 50, aumentaríamos notablemente la cantidad de cactus en este desierto.

Una vez tenemos el terreno "crudo", completamente aleatorio, empezamos a pasarlo por una serie de filtros o reglas que van a asegurarse de que el laberinto esté bien equilibrado y sea resoluble -en la medida de lo posible, este es el punto más mejorable de mi algoritmo-

Movidas para arreglar este desierto

Sin enrollarme mucho... los pocos arreglos que hago son:

  • Añadir un punto de inicio y una meta en cuadrantes opuestos en el mapa de forma aleatoria.

  • Que el punto de inicio esté rodeado de terreno "normal" para no empezar con sorpresas alrededor.

  • Que la meta sea accesible y no esté rodeada de abismo.

  • Que los recursos naturales no se aglomeren comprobando que no haya ninguno repetido en una zona de 9 casillas en torno al mismo.

  • Que no haya más de 50% de "densidad" de abismo por cada fila para no cortar el mapa de una forma intransitable.

Ahora mismo, estoy exportando estos mapas de 2 dimensiones en CSV para comparar y nivelar el game design y conseguir un poco a prueba y error la mejor experiencia posible al jugarlo. Google Drive se encarga de darle color en una hoja de cálculo con estilos para saborear bien el mapita xD

Ejemplos generados con el algoritmo del ritmo

Leyenda:
-sta: inicio
-violeta brillante: meta
-amarillo claro/fuerte: terreno normal/ terreno con dunas
-rojo claro/fuerte: enemigo ligero/enemigo fuerte
-verde: cactus
-azul: pozo de agua
-gris: sombra para descansar
-negro: abismo/pared
-morado oscuro: restos para carroñar

Tocando la proporción de terrenos y especificando el tamaño del laberinto en cuestión de segundos podemos testear muchos laberintos diferentes :)

Mapa 30x30 - Difícil

Mapa 30x30 - Difícil (inicio/meta invertidos)

Mapa 25x25 - Fácil

¡Gracias!

Eso es todo, espero no haberos aburrido, y los consejos sobre como mejorarlo serán bienvenidísimos!

Código entero arregladito.

9
Jastro

menuda locura mas guapa tienes por aqui #1 :3

1
p0stm4n

Yo de programación de juegos tengo más bien poca idea (solo he hecho un prototipo de Zelda con lo básico), pero diría que tienes que mirarte generación procedural de mazmorras (dungeon procedural generation), y usar las texturas de un desierto.

Hay mucho ejemplo por hay suelto, pero claro, no es sencillo y que meterle bastantes horas, para poder adaptar el algoritmo a lo que quieres.

Spacelord

Joder, qué casualidad. Yo llevo un par de días rompiéndome la cabeza intentando hacer un generador aleatorio de mazmorras 2D en Java. XD

sergilazaro

Mirar estos colores y tratar de entenderlo me recuerda a matrix:


"al cabo del tiempo llegas a aprender a leerlo; ya no ves código: ves una rubia, una morena, una pelirroja..."

Supongo que lo de los colores es lo más rápido para prototipar, pero yo casi me hubiera hecho un script rápido que creara un mapa para Tiled o otro editor de tiles, y poderlo visualizar allí con sprites aunque sean cutres. Almenos no me volvería loco mirando el código de matrix XD

Has pensado en el modo Spelunky de generación de niveles? Rollo, tienes una serie de bloques de 4x4 o similar, ya hechos, los colocas de forma random en el mapa uno al lado del otro, colocas los tiles de principio y final, arreglas problemas de no poder llegar del principio a final, si los hay (cavando un camino o lo que sea), etc. Así en vez de ser totalmente aleatorio puedes crear a mano pequeñas situaciones concretas en los bloques grandes para tener más por la mano el diseño, más que solo tocar números a piñón.

1 1 respuesta
MaikelOrtega

#1 Grey, yo te recomendaría que modularizaras un poco, usando métodos que te ahorren escribir lo mismo y dificultan la lectura del código. En plan un método para chequear si el tile está rodeado de un igual y regenerarlo, en lugar de meter ese tochaco en cada caso xD.

Pero vaya, eso son cosas de cada uno. Entrando más en el algoritmo:

Tal y como lo tienes, el mapa está restringido a tener inicio/meta en los bordes. Quizás eso te basta, pero si quieres puedes eliminar esa restricción, usando algo de pathfinding para generar la meta una vez tienes el inicio. Algo tipo:

1)Pones el inicio en un sitio aleatorio
2)Pones abyss en el mapa según tu densidad
3)Pones la meta en un sitio aleatorio
4)Usas A* o un algoritmo del estilo para ver si existe un camino. Si no existe, quita la meta y repite. Aquí puedes incluso ver si el camino es el más corto y qué longitud tiene (a lo mejor quieres una distancia fija).
5) Rellena todo lo que no sea Abyss, Meta o Start de enemigos, cactus.. etc...

Y por cierto, me encanta lo del google drive xD

1 1 respuesta
GreyShock

Gracias por los consejos!

#5 Google Drive lleva diseñando conmigo mucho tiempo, como bien dices, yo solo veo rubias o morenas ya xDDD El método de creación de niveles de Spelunky es genial. Si veo que quedan demasiado "salvajes" los escenarios miraré de implementar un sistema de chunks con retos concretos diseñados a manija.

#6 Modularizar más aúuuun?... jo.. vaale.

Tiene todo el sentido del mundo el método que propones. De hecho, es bastante mejor que la m***da que me he marcado xD Si veo que este generador no produce laberintos interesantes me paso a tu sabio consejo :)

B

Llevo un rato y no veo la meta, o sea el violeta brillante.

En ninguna imagen.

1 3 respuestas
Spacelord

#8 En la última está en (5,7), por ejemplo.

L

#8 tranquilo yo tampoco.

Pd: hay pocos cactus :___

1 respuesta
GreyShock

#8 #10 Siento daros la mala noticia pero... En realidad no son laberintos, es un generador de tests de daltonismo. Sos ciegos al violeta.

radimov

¿Por que no utilizas colores con mucho más contraste y más fáciles de identificar? A niveles de programación supongo que sería lo mismo y a la hora de interpretar los niveles creo que sería mucho más rapido de identificar cada elemento.

1 respuesta
GreyShock

#12 No están elegidos con mucho tino los colores eh..? Pero bueno, no pasa nada, esos excels son para calibrar la generación y pulir el game design, no es nada que vaya a ver el jugador. El juego tiene un entorno desértico pixel art bien bonico :)

B

Tras ver uno ya he visto el resto. Joder lo que costó xD

1
HellTiger

Genial y yo daltonico....xD

Usuarios habituales