Juglar

Motor de juegos narrativos y educativos
para aprender programación web

Arrastrar elementos

Hasta ahora hemos interactuado con los puzles simplemente pulsando, pero en algunas ocasiones pueden ser interesantes otras formas de interacción. Por ejemplo, arrastrando los elementos a nuevas posiciones. Vamos a probar esto con otro tipo de puzle de imágenes:

Como otras veces tenemos las diferentes piezas, pero en esta ocasión además vamos a poner como atributos la posición correcta de la pieza, y la posición actual:


<puzle-deslizante>
	<button data-escena="menú-juego">Volver</button>
	
	<lista-piezas>
		<pieza-puzle x-correcta="1" y-correcta="1"  x-actual="1" y-actual="1" ></pieza-puzle>
		<pieza-puzle x-correcta="2" y-correcta="1"  x-actual="2" y-actual="1" id="pieza" ></pieza-puzle>
		<pieza-puzle x-correcta="1" y-correcta="2"  x-actual="1" y-actual="2" ></pieza-puzle>
		<pieza-puzle x-correcta="2" y-correcta="2"  x-actual="2" y-actual="2" ></pieza-puzle>
		<pieza-puzle x-correcta="3" y-correcta="2"  x-actual="3" y-actual="2" ></pieza-puzle>
		<pieza-puzle x-correcta="1" y-correcta="3"  x-actual="1" y-actual="3" class="hueco" ></pieza-puzle>
		<pieza-puzle x-correcta="2" y-correcta="3"  x-actual="2" y-actual="3" ></pieza-puzle>
		<pieza-puzle x-correcta="3" y-correcta="3"  x-actual="3" y-actual="3" >></pieza-puzle>
	</lista-piezas>
	
	<script src="puzle_deslizante.js"></script>
	<link rel="stylesheet" href="puzle_deslizante.css">
</puzle-deslizante>

Como en el caso anterior, usaremos la posición correcta de la pieza para escoger que parte de la imagen mostramos. Pero además usaremos la posición actual para mostrar dónde se encuentra, usando posicionamiento absoluto. Lo más interesante de esto es que podremos mover la pieza dentro del puzle, e incluso animar ese movimiento:


lista-piezas{
	background:black;
	width:100%;
	max-width:20rem;
	aspect-ratio:1 / 1;
	margin:auto;
	position:relative;
	
	pieza-puzle{
		background:url(img/tortugas.jpg);
		width:calc(100% / 3);
		height:calc(100% / 3);
		display:block;
		position:absolute;
		background-size:300%;
		scale:0.98;
		transition:top 0.2s, left 0.2s;
		cursor:pointer;
		
		&[x-actual="1"]{left:0;}
		&[x-actual="2"]{left:calc(100% / 3);}
		&[x-actual="3"]{left:calc(200% / 3);}
		
		&[y-actual="1"]{top:0;}
		&[y-actual="2"]{top:calc(100% / 3);}
		&[y-actual="3"]{top:calc(200% / 3);}
		
		&[x-correcta="1"]{background-position-x:0;}
		&[x-correcta="2"]{background-position-x:50%;}
		&[x-correcta="3"]{background-position-x:100%;}
		
		&[y-correcta="1"]{background-position-y:0;}
		&[y-correcta="2"]{background-position-y:50%;}
		&[y-correcta="3"]{background-position-y:100%;}
		
		&.hueco{
			opacity:0;
			pointer-events:none;
		}
		
		&.cogida{
			transition:none;
			z-index:10;
		}
	}
	
	&[puzle-completado]{
		pointer-events:none;
		
		pieza-puzle{
			scale:1;
			transition:top 0.2s, left 0.2s, scale 0.4s;
			
			&.hueco{
				opacity:1;
				transition:opacity 0.5s;
			}
		}
	}
}

Lo primero que hay que hacer es desordenar el puzle, y para ello haremos un buen número de movimentos al azar, buscando siempre cuales son las piezas adyacente al hueco:


const puzle = selecciona("puzle-deslizante")

const lista_piezas = puzle.selecciona("lista-piezas")
const piezas = puzle.selecciona("pieza-puzle")
const hueco = puzle.selecciona("pieza-puzle.hueco")

const número_de_movimientos_para_desordenar = 100

desordena()


function desordena(){
	for (let i = 0; i < número_de_movimientos_para_desordenar; i++) {
		let pieza = obtén_piezas_adyacentes_a(hueco).escoge_al_azar()
		intercambia_posiciones(hueco, pieza)
	}
}

function obtén_piezas_adyacentes_a(piezaA){
	let adyacentes = []
	piezas.para_cada(piezaB=>{
		if(son_adyacentes(piezaA, piezaB)) adyacentes.push(piezaB)
	})
	return adyacentes
}

function son_adyacentes(piezaA, piezaB){
	const posiciónA = obtén_posición(piezaA)
	const posiciónB = obtén_posición(piezaB)
	return 1 == posiciónA.distancia(posiciónB)
}

function intercambia_posiciones(piezaA, piezaB){
	const posiciónA = obtén_posición(piezaA)
	const posiciónB = obtén_posición(piezaB)
	
	cambia_posición(piezaA, posiciónB)
	cambia_posición(piezaB, posiciónA)
}

function obtén_posición(pieza){
	return geometría.crea_punto(pieza.atributo.x_actual, pieza.atributo.y_actual)
}

function cambia_posición(pieza,nueva_posición){
	pieza.atributo.x_actual = nueva_posición.x
	pieza.atributo.y_actual = nueva_posición.y
}

Ahora toca mover las piezas, con lo que necesitamos detectar donde hemos presionado, y luego seguir el movimiento hasta que soltamos. Solo hay una cosa más que tener en cuenta: mirar donde está el hueco para limitar el movimiento en dicha dirección:



let pieza_cogida
let posición_original
let posición_original_puntero

const límites_x = [0,0]
const límites_y = [1,1]
piezas.al_coger((pieza, posición,evento)=>{
	if(!son_adyacentes(pieza, hueco)) return
	
	pieza_cogida = pieza
	pieza.clase.cogida = true
	pieza.al_mover(mover_pieza)
	pieza.al_soltar(soltar_pieza)
	
	posición_original = pieza.posición_relativa
	posición_original_puntero = posición
})

function mover_pieza(pieza, posición_actual_puntero){
	const desplazamiento = posición_actual_puntero.menos(posición_original_puntero)
	
	const posición_actual = posición_original.más(desplazamiento)
	
	posición_actual.coloca_entre(posición_original,hueco.posición_relativa)
	
	pieza_cogida.estilo.left = posición_actual.x + "px"
	pieza_cogida.estilo.top = posición_actual.y + "px"
}

function soltar_pieza(pieza, posición_actual_puntero, evento){
	pieza_cogida.estilo.left = null
	pieza_cogida.estilo.top = null
	pieza.clase.cogida = false
	
	const distancia_original = pieza_cogida.posición_relativa.distancia(posición_original)
	const distancia_hueco = pieza_cogida.posición_relativa.distancia(hueco.posición_relativa)
	
	if(distancia_hueco < distancia_original){
		intercambia_posiciones(pieza_cogida, hueco)	
		if(el_puzle_es_correcto()) lista_piezas.atributo.puzle_completado = true
	}
}

function el_puzle_es_correcto(){
	return piezas.todos_cumplen(posición_correcta)
}

function posición_correcta(pieza){
	return pieza.atributo.x_actual == pieza.atributo.x_correcta &&
			pieza.atributo.y_actual == pieza.atributo.y_correcta
}

Como siempre, tras mover una pieza, toca comprobar si el puzle ya está correcto, con todas las piezas en su lugar.