Hola
Pues la verdad es que la unica vez que he visto algo parecido ha sido para hacer un juego de ajedrez, por lo que mucha experiencia no tengo

. Pero se me ocurre una idea inspirada en el ajedrez: usar un arbol.
Necesitarias una funcion que generara los posibles movimientos que se puedan realizar en un estado concreto del juego. Es decir, todos los posibles movimientos de cajas y los ordenas en un arbol. Empiezas por el primero y sigues esa rama, cambia el estado del juego y vuelves a ejecutar la funcion que obtiene los nuevos posibles movimientos.
La clave esta en seguir la rama hasta dar con una situacion en la que no haya mas movimientos posibles o se haya resuelto el puzle. Si ya no se puede hacer nada y no se ha resuelto, cambiariamos de rama del arbol y se probaria otra solucion.
Este metodo se podria mejorar añadiendo una funcion que detectara si una caja ya no puede ser movida y descartaria esa rama directamente.
No se si es una buena solucion, pero se podria empezar por ahi jejeje. Saludos!