Ejecutemos el layout. Si la condición está bien formada, cuando se arrestre y suelte una pieza comenzarán a parpadear las que había alrededor del "hueco" que ha formado, diciéndonos que quiere comprobar dónde están estas piezas.
Y eso es lo que nos queda por hacer: comprobar dónde están.
Están las piezas en el lugar adecuado?
Ahora tenemos que probar si estas piezas están suficientemente cerca como para encajar con la que se acaba de soltar. Añadamos las siguientes acciones bajo ActualPiece.Flash:
Set IdealX to (ActualPiece.PlaceX-CompareX)*PieceStepX+MyX
Set IdealY to (ActualPiece.PlaceY-CompareY)*PieceStepY+MyY
Estas dos variables almacenarán dónde se espera que estén los vecinos de la pieza que se acaba de soltar.
Si el vecino está a la izquierda (ActualPiece.PlaceX-CompareX=-1), la pieza estará PieceStepX píxeles a la izquierda de MyX; por lo tanto, sus coordenadas serán (PieceStepX * -1) + MyX = -PieceStepX + MyX = MyX-PieceStepX.
En cambio, Si el vecino está a la derecha, la pieza estará PiecestepX píxeles a la derecha (ActualPiece.PlaceX-CompareX=+1, estaremos sumando en lugar de restar).
Si el vecino está encima o debajo, se realizarán cálculos similares para IdealY.
Añadimos otro sub evento (un sub-subevento) con la siguiente condición:
Distance(ActualPiece.X, ActualPiece.Y, Ideal.X, Ideal.Y) <5
Así, se comprueba si las piezas están, como mucho, a 5 píxeles de distancia. Puede cambiarse "5" por el margen de error que se desee. Cuanto mayor, más fácil será el juego.
En el cálculo anterior se usa distance() como una manera fácil de calcular distancias entre piezas. Personalmente creo que usar abs() y restas será algo más rápido (distance() calcula una raíz cuadrada) pero, después de todos los cálculos anteriores, creo que será un respiro para el lector esta fórmula más simple.
Finalmente, movemos la acción "Flash" al último subevento y ejecutamos el proyecto. Ahora, las piezas deberían parpadear solamente cuando "encajen" en el lugar correcto.
Encajando
El punto anterior sebería ser más difícil que lo que queda por hacer. Ya solo queda unir las piezas con la "behaviour" Pin. Pero, antes de usar "Pin", hay que mover un poco las piezas para llevarlas al lugar donde encajen mejor. ¿Cuál es el mejor lugar? Ya lo hemos calculado antes: (IdealX, IdealY).
Así, bajo la acción "Flash" añadimos:
ActualPiece=>Set position (IdealX, IdealY)
Queda otro obstáculo. Imaginemos que añaimos el siguiente código al proyecto:ct:
ActualPiece=>Pin to another object =>...
Enseguida aflorarán dos dificultades:
La primera, que no sabemos a qué objeto "Pinchar" (pin) el objeto ActualPiece. Es necesario almacenar una UID a la instancia de ActualPiece que acaba de ser soltada. ¡Así que "Pinchar" dos instancias del mismo objeto no es fácil!
La segunda, que incluso si se "pinchan" (pin) dos objetos ActualPiece juntos, el primero moverá el segundo, pero el segundo no moverá el primero (por lo menos, en las pruebas que hice tiempo atrás). Por eso son necesarios los "grupos".
Añadimos lo siguiente al código:
Entre el bloque de variables locales bajo "Actualpiece.On dragdrop drop", añadimos una variable local llamada "MyGroup", con valor 0.
En el evento vacío que hay tras las variables locales, bajo "Set compareY to ActualPiece.PlaceY", añadimos:
System=>Set Value MyGroup to ActualPiece.Group
Después, en el subevento de abajo, tras la acción "ActualPiece.SetPosition", añadimos esta otra:
ActualPiece=>Set Group to ActualPiece.Group.
Ahora vayamos a la vista de Layout e insertemos un sprita vacío (sin imagen) llamado Pin. En el ejemplo, hemos hecho que fuera visible y de color azul para poder verlo, pero en un entorno de producción debería ser invisible para el usuario.
Añadimos el comportamiento ("behaviour") "Pin" al sprite "Pin".
Volvamos después al evento "ActualPiece.On Drag" y añadamos:
Pin.Destroy
System.Create Object Pin at (ActualPiece.X,ActualPiece.Y)
Pin Pin to ActualPiece (Position and angle)
Destruimos el objeto Pin antes de pincharlo a la pieza porque, de esa manera, si ya tenemos algo "pinchado" en él, lo "soltaremos" (es más rápido destruirlo y volverlo a crear que averiguar qué hay "pinchado" en él).
Debajo, añadimos un sub-evento vacío. Encima del sub-evento vacío, añadimos las siguientes variables locales:
MyUID (valor:0). Aquí guardaremos la UID del objeto ActualPiece actual.
MyGroup (valor:0). Aquí guardaremos el grupo del objeto ActualPiece actual.
En el subevento vacío, añadimos:
Set MyUID to ActualPiece.UID
Set MyGroup to ActualPiece.Group
Añadimos un sub-subevento al subevento vacío, con estas condiciones:
System.Pick all Actualpiece
ActualPiece. Compare instance Variable Group with MyGroup
System. Pick ActualPiece by evaluating ActualPiece.UID<>MyUID.
Pongamos en él las siguientes acciones:
Si ejecutamos ahora el proyecto, veremos que se puede arrastrar una pieza y pegarla a otra. Pero hay conductas indeseadas, como que en el grupo arrastrado sólo se "pegará" la pieza que estábamos arrastrando. Para arreglarlo, debemos emplear un bucle para comprobar todas las piezas del grupo "arrastrado" contra las piezas de fuera del grupo.
Intentaremos hacerlo en un tutorial posterior.
Siguiente en la serie
Haciendo un puzzle- Cuarta parte: Manejando grupos de piezas explica cómo comprobar el grupo entero en busca de piezas encajadas y cómo unir grupos entre sí. Asemás, incorpora pequeños detalles como la adición de sonido, el "barajado" de piezas o el salto a una rutina especial cuando se completa el puzzle.