Buenas!
Hoy empezamos con el diseño; esta sección durará dos entradas: una dedicada al diseño de algoritmos, y otra dedicada al diseño de pantallas.
En la entrada anterior explicamos el comportamiento básico de nuestro juego de las minas. En esta profundizaremos en ello hasta conseguir buenos algoritmos en
pseudo-código.
El
modus operandi que uso para generar algoritmos se basa en funciones de profundidad. Es decir: creo una base muy simple (dos o tres instrucciones), y después para cada instrucción voy ampliando el nivel de profundidad del algoritmo. Con esta técnica se consiguen algoritmos compactos y muy modulares.
Empezaremos con la parte del
backend, es decir, dibujar minas, colocarlas...:
[Carga la pantalla]
muestra(pantalla_inicial)
si gen = 0 cierra
sino
carga()
dibuja()
fin
Tenemos el aspecto general de carga terminado. Una anotación: los
token del
pseudo-código los he puesto en
rojo y negrita; las variables en
azul y las funciones propias en
verde.
Sigamos ahora con el algoritmo de la función
carga() (no os preocupéis si veis variables "inconexas"; luego lo juntamos todo).
[Función carga]
carga(){
min = 10
fil = 9
col = 9
img =Random(min, max)
si gen = 2 img += max
imF = Imagen(concat(ruta, string(img), '.jpg'))
anc = width(imF)
alt = height(imF)
si dif = 1
min = 40
fil = 16
col = 16
sino si dif = 2
min = 99
fil = 16
col = 30
fin
anB = anc / col
alB = alt / fil
pos = LlenarMinas(fil*col, min)
res = ComprobarMinas(fil*col, col, pos, min)
minas = min
cuadros = (fil * col) - min
}
Revisemos rápidamente como funciona esta función:
Primero asumimos los valores para la dificultad fácil (tres primeras líneas). Acto seguido creamos un aleatorio para la imágen de fondo. Si la opción es 2, le sumamos max (esto es porque hay los mismos fondos de la primera opción como de la segunda; los fondos están numerados. Si es la segunda opción, el número más bajo será max, o lo que es lo mismo, el primer fondo de la opción 2).
Lo siguiente es crear la imágen en imF, y capturar su ancho y largo.
Seguidamente consultamos la dificultad. Si no está en fácil variamos los datos pertinentes.
Finalmente, calculamos el ancho y el largo de los controles (anB y alB), llenamos las minas y las comprobamos.
Las funciones
LlenarMinas() y
ComprobarMinas() las veremos después de la función
dibuja(), la cual veremos a continuación:
[Función dibuja]
dibuja(){
max = fil * col
h = alB
w = anB
c = col
f = fil
con = array(max*2)
de 0 hasta max-1 usando i
o = label()
p = button()
top(o) = t
left(o) = l
width(o) = w
height(o) = h
name(o) = concat('L', string(i))
visible(o) = 1
backColor(o) = color('transparent')
font(o) = font('Times new Roman', 10, 'regular', 'pixel')
top(p) = t
left(p) = l
width(p) = w
height(p) = h
name(p) = concat('B', string(i))
visible(p) = 1
Evento(p.MouseDown)
Evento(o.MouseEnter)
Evento(o.MouseLeave)
con(i) = p
con(i+max) = o
si i%c = c-1
l = 0
t += h
sino
l += w
fin
sigue
add(ventana, con)
height(ventana) = alt + 57
width(ventana) = anc + 13
maxSize(ventana) = size(ventana)
minSize(ventana) = size(ventana)
backgroundImage(ventana) = imF
text(strip1) = string(min)
}
Si en la anterior función predominaban la asignación de variables, en esta predominan las asignaciones a controles. Explico rápidamente el algoritmo:
Se reocgen datos (ya se que parece que sean inútiles las asignaciones del principio).
Después se entra en un bucle, en el que vamos definiendo, en cada vuelta, un button y un label. Les damos altura, anchura, color, posición... y los agregamos al array, de manera que quedan agrupados todos los button con los button y las label con las label.
El siguiente paso es calcular la siguiente posición, y volver a empezar.
Cuando termina el bucle, incluimos todos los controles en la ventana y le fijamos un ancho y un alto. Después hacemos que no se pueda modificar el tamaño, y ponemos el número de minas abajo (en un strip).
El siguiente paso es definir la función
LlenarMinas():
[Función LlenarMinas]
LlenarMinas(){
de 0 hasta min-1 usando i
pass = 1
num = Random(0, max+1)
de 0 hasta length(pos)-1 usando j
si pos(j) = num pass = 0
sigue
si pass pos(i) = num
sino i -= 1
sigue
}
Este algoritmo es corto y conciso: hace "min-1" iteraciones, en cada una de la cual busca un aleatorio que no esté en "pos". En caso de estar, retrocedemos en una unidad el contador, para volver a la misma posición.
Para el
backend solo falta definir el algoritmo
ComprobarMinas(). Pero, además, faltan los eventos que definimos en
dibuja(). Éstos entrarán en juego cuando el usuario pulse un button, entre en una label o salga de ella.
Por ahora, vamos a por
ComprobarMinas():
[Función ComprobarMinas]
ComprobarMinas(){
de 0 hasta max-1 usando i
de 0 hasta min-1 usando j
si i%col = 0
si (pos(j) = i - col o pos(j) = i - (col - 1) o
pos(j) = i + 1 o pos(j) = i + col o pos(j) = i + (col + 1))
num++
sino
num = 10
break
fin
sino si i%col = col - 1
si (pos(j) = i - (col + 1) o pos(j) = i - col o
pos(j) = i - 1 o pos(j) = i + (col - 1) o pos(j) = i + col)
num++
sino
num = 10
break
fin
sino
si (pos(j) = i - (col + 1) o pos(j) = i - col o pos(j) = i - (col - 1) o
pos(j) = i - 1 o
pos(j) = i + 1 o pos(j) = i + (col - 1) o
pos(j) = i + col o
pos(j) = i + (col + 1))
num++
sino
num = 10
break
fin
fin
sigue
ret(i) = num
num = 0
sigue
}
Esta función parece complicada, pero es bastante sencilla: recorremos el array "pos", y por cada posición buscamos dentro de él los lugares colindantes (como si fuera un array de dos dimensiones pero en uno). El complejo de condiciones simplemente sirven para delimitar los "bordes" de la martiz.
Entonces, si encuentra una coincidencia, incrementa "num". En caso contrario deja "num" con un valor de "10" (ya veremos para qué sirve esto).
NOTA: Sé que no es precisamente óptimo, pero esto está hecho de hace un año, y, sinceramente, como funciona no me atrevo a tocarlo...
Finalmente nos faltan los
pseudo-códigos de los eventos (tranquilos, ya acabo :-P). Empezamos por los eventos de las label (más cortos):
[Label::mouseEnter]
backColor(s) = color('white')
text(s) = coM(substring(name(s), 1))
Simplemente le ponemos un fondo de color blanco y un texto con las minas que tiene alrededor.
Ahora el otro evento de la Label:
[Label::mouseLeave]
text(s) = ''
backColor(s) = color('transparent')
Aquí volvemos a dejar la label en el mismo estado que cuando la definimos.
Por último, el evento del button:
[Button::mouseDown]
si button(e) = mouseButtons(left)
cuadros--
si cuadros = 0
msg('Has ganado')
clear(ventana, controls)
fin
visible(s) = 0
si coM(substring(name(s), 1)) = 10
msg('PUM!')
muestra(fail)
fin
sino si button(e) = mousebuttons(right)
si text(s) = 'X'
text(s) = ''
backColor(s) = Color('white')
minas++
sino
text(s) = 'X'
backColor(s) = Color('red')
minas--
fin
text(strip) = string(minas)
fin
Como escribí en la anterior entrada; comprueba el click hecho (izquierdo o derecho). Si es derecho, permuta entre un texto "X" y un color, indicando que está marcado o no como mina. También modifica el texto del strip (que lleva la cuenta de las minas que nos faltan por marcar).
En cambio, si el click es izquierdo, destapa el cuadro, y busca el valor de la label.
OJO he marcado el "10" en negrita para que lo veáis; es el misterioso "10" de
CompruebaMinas(). ¿Cómo funciona? Sencillo:
Si os fijáis los buttons y los labels tienen nombres del estilo "B1", "L25". En el evento se extrae el número, y se coteja con el array (por eso es necesariamente unidimensional) y recoge el número del array que
CompruebaMinas() introjudo (que es el valor de las minas que tiene alrededor; 10 si es mina). ¡Volia!
Ahora, por fin tenemos todos los algoritmos necesarios. Solo nos falta diseñar las pantallas. Esto nos llevará más bien poco, ya que la mitad del trabajo de diseño está aquí incluido (función
dibuja()).
Os dejo, como siempre,
Hasta la próxima!