|
|
Línea 1: |
Línea 1: |
| <source lang = "prolog"> | | <source lang = "prolog"> |
− | % El objetivo será diseñar un nivel de juego en el que el jugador | + | % Definimos las dimensiones del tablero: |
− | % empieza en la casilla de arriba y a la izquierdada del tablero, encuentra
| + | #const n = 5. |
− | % una gema en el muro de la mazmorra, la lleva a un altar central donde
| |
− | % mágicamente desbloquea la salida, y luego va hasta la salida, situada
| |
− | % abajo y a la derecha.
| |
− | | |
− | #const n=10. | |
− | param("n",n).
| |
− | | |
| dim(1..n). | | dim(1..n). |
| | | |
− | % Definimos las casillas, a partir de la dimensión de nuestro tablero. | + | % Para cada casilla distinta a (1,1) le asignamos un solo parent: izquierda, |
− | casilla((X,Y)) :- dim(X),dim(Y).
| + | % derecha, arriba o abajo. |
| + | 1 {parent(X,Y, 0,-1); parent(X,Y, 1,0); |
| + | parent(X,Y, -1,0); parent(X,Y, 0,1)} 1 :- dim(X), dim(Y), (X,Y) != (1,1). |
| | | |
− | % Dos casillas son consecutivas o adyacentes si |X1-X2| + |Y1-Y2| == 1. | + | % Definimos la propiedad alcanzable. Esta se cumple trivialmente en la casilla |
− | adyacente((X1,Y1),(X2,Y2)):- casilla((X1,Y1)), casilla((X2,Y2)),
| + | % (1,1), y cualquier casilla desde la cual se haga un movimiento a una casilla |
− | X1-X2 == 1, Y1-Y2 == 0.
| + | % alcanzable también será alcanzable: |
− | adyacente((X1,Y1),(X2,Y2)):- casilla((X1,Y1)), casilla((X2,Y2)),
| + | alcanzable(1,1). |
− | X2-X1 == 1, Y1-Y2 == 0.
| + | alcanzable(X,Y) :- parent(X,Y,DX,DY), alcanzable(X+DX,Y+DY). |
− | adyacente((X1,Y1),(X2,Y2)):- casilla((X1,Y1)), casilla((X2,Y2)),
| |
− | X1-X2 == 0, Y1-Y2 == 1.
| |
− | adyacente((X1,Y1),(X2,Y2)):- casilla((X1,Y1)), casilla((X2,Y2)),
| |
− | X2-X1 == 0, Y2-Y1 == 1.
| |
| | | |
− | % La entrada al laberinto es la casilla (1,1), y la salida es la opuesta en | + | % La raíz del árbol debe ser alcanzable desde cualquier casilla. |
− | % el tablero.
| + | :- dim(X), dim(Y), not alcanzable(X,Y). |
− | salida((1,1)).
| |
− | llegada((n,n)).
| |
| | | |
− | % Las casillas tienen a lo sumo una marca: | + | % Minimizamos el número de movimientos verticales: |
− | 0 { marca(T,pared); marca(T,gema); marca(T,altar) } 1 :- casilla(T).
| + | vertical(X,Y) :- parent(X,Y,0,1). |
− | | + | vertical(X,Y) :- parent(X,Y,0,-1). |
− | % Hay exactamente un altar y una gema en todo el laberinto:
| + | #minimize {X,Y: vertical(X,Y) }. |
− | :- not 1 {marca(T,altar)} 1.
| |
− | :- not 1 {marca(T,gema)} 1. | |
− | | |
− | % Para obtener una mazmorra interesante debe de haber muchas paredes.
| |
− | % Por tanto, al menos la mitad de las casillas deben ser paredes:
| |
− | :- not ((n*n)/2) {marca(T,pared)}.
| |
− | | |
− | % Un altar debe tener alrededor algunas casillas en blanco.
| |
− | % Luego, un altar no puede tener paredes circundantes a menos de dos casillas.
| |
− | 0 {marca(T3,pared): adyacente(T1,T2), adyacente(T2,T3)} 0 :- marca(T1,altar).
| |
− | | |
− | % Los altares no pueden estar en el borde del mapa. Para ello imponemos que
| |
− | % tenga 4 casillas adyacentes:
| |
− | :- marca(T1,altar), not 4 {adyacente(T1,T2)}.
| |
− | | |
− | % Las gemas deben estar adheridas a las paredes circundantes.
| |
− | 3 {marca(T2,pared): adyacente(T1,T2)} :- marca(T1,gema).
| |
− | | |
− | % Una pared empieza a tener aspecto de muro cuando va seguido de otras
| |
− | % paredes. Luego imponemos que toda pared tenga al menos dos paredes
| |
− | % vecinos:
| |
− | 2 {marca(T2,pared): adyacente(T1,T2)} :- marca(T1,pared).
| |
| | | |
| % Presentación: | | % Presentación: |
− | #show marca/2. | + | #show parent/4. |
− | | |
− | | |
− | % Vemos que efectivamente, la gema aparece rodeada de muchos bloques.
| |
− | % Pero no tenemos que olvidar que el nivel debe ser jugable.
| |
− | | |
− | | |
− | % REPRESENTACIÓN DEL ESPACIO DE SOLUCIONES VÁLIDAS:
| |
− | | |
− | % Para ello vamos a definir tres estados:
| |
− | % Estado 1: inicial
| |
− | % Estado 2: después de coger la gema
| |
− | % Estado 3: después de llevar la gema al altar
| |
− | | |
− | % Comenzamos pues en el estado 1.
| |
− | % El predicado sobre describe sobre que marca (o casilla blanca) se espera
| |
− | % que el jugador esté en cada etapa para pasarse el nivel.
| |
− | % En el estado 1, el jugador empieza en la casilla de inicio:
| |
− | sobre(T,1) :- salida(T).
| |
− | | |
− | % Además, si estamos sobre una casilla, el jugador debe poder pasar a estar
| |
− | % sobre otra casilla adyacente a esta sin cambiar de estado. Si el jugador
| |
− | % está sobre la marca de una gema o altar, entonces puede cambiar al siguiente
| |
− | % estado.
| |
− | { sobre(T2,2) : adyacente(T1,T2) } :- sobre(T1,1), marca(T1,gema).
| |
− | { sobre(T2,3) : adyacente(T1,T2) } :- sobre(T1,2), marca(T1,altar).
| |
− | { sobre(T2,S) : adyacente(T1,T2) } :- sobre(T1,S).
| |
− | | |
− | % El jugador no puede estar nunca sobre la marca de una pared.
| |
− | :- marca(T,pared), sobre(T,S).
| |
− | | |
− | % La casilla final debe tocarse en la etapa 3. Luego el predicado completo
| |
− | % se verifica solo si se está sobre la casilla en la que hay que acabar y
| |
− | % y estamos en la etapa 3.
| |
− | completo :- llegada(T), sobre(T,3).
| |
− | :- not completo.
| |
| | | |
− | % Finalmente, vamos a imponer que si se cumplen las restricciones de las marcas,
| |
− | % entonces es seguro que toda solución debe pasar por al menos n casillas,
| |
− | % siendo n la anchura del tablero:
| |
− | __level_design(marca(T,Nombre)) :- marca(T,Nombre).
| |
− | __concept :- n { sobre(T,1) },
| |
− | n { sobre(T,2) },
| |
− | n { sobre(T,3) }.
| |
| | | |
| | | |
| </source> | | </source> |