15 puzzle solver: Difference between revisions

Content added Content deleted
No edit summary
(Scala solution, quite slow using A* with some trial and error weighting.)
Line 4,604: Line 4,604:
{{out}}
{{out}}
<pre>Solution found in 52 moves: rrrulddluuuldrurdddrullulurrrddldluurddlulurruldrdrd</pre>
<pre>Solution found in 52 moves: rrrulddluuuldrurdddrullulurrrddldluurddlulurruldrdrd</pre>

=={{header|Scala}}==

<lang Scala>
import scala.collection.mutable
import Board._
case class Board(table: Array[Int], r: Int, c: Int, parent: List[String] = List()) {
def cloneSwap(r: Int, c: Int, rr: Int, cc: Int) = {
val cTable = table.clone
cTable(r * 4 + c) = table(rr * 4 + cc)
cTable(rr * 4 + cc) = table(r * 4 + c)
cTable
}
def up() =
if (r > 0) { Some(Board(cloneSwap(r, c, r - 1, c), r - 1, c, "u" :: parent))
} else None
def down() =
if (r < 3) { Some(Board(cloneSwap(r, c, r + 1, c), r + 1, c, "d" :: parent))
} else None
def left() =
if (c > 0) { Some(Board(cloneSwap(r, c, r, c - 1), r, c - 1, "l" :: parent))
} else None
def right() =
if (c < 3) { Some(Board(cloneSwap(r, c, r, c + 1), r, c + 1, "r" :: parent))
} else None
def format: String = {
table.sliding(4, 4).toList.map(_.map(i ⇒ f"$i%4d").mkString).mkString("\n")
}
// Manhattan distance
def heuristic() = {
val posF = finalBoard.positionMap
val posT = positionMap

var res = 0;
for (i ← 0 to 15) {
val (x, y) = posF(i)
val (xx, yy) = posT(i)
res += (Math.abs(x - xx) + Math.abs(y - yy))
}
res
}
def key = table.mkString(",")
def travelled() = parent.length
// Playing with weight of heuristic vs travelled so far. Giving equal weight take too long.
// Heuristic only will converge too fast on a longer than 52 steps solution.
def aStar() = heuristic() * 5 + travelled() * 4
// Find children/neighbour that is not himself.
def children: List[Board] = List(this.up(), this.down(), this.right(), this.left()).flatten
// Map number to positions
lazy val positionMap: Map[Int, (Int, Int)] = {
val res = mutable.Map[Int, (Int, Int)]()
for (x ← 0 to 3; y ← 0 to 3) {
res(table(x * 4 + y)) = (x, y)
}
res.toMap
}
}
object Board {
val finalBoard = Board(Array(Array(1, 2, 3, 4), Array(5, 6, 7, 8), Array(9, 10, 11, 12), Array(13, 14, 15, 0)).flatten, 3, 3)
val startEasy = Board(Array(Array(15, 14, 1, 6), Array(9, 11, 4, 12), Array(0, 10, 7, 3), Array(13, 8, 5, 2)).flatten, 2, 0)
val startHard = Board(Array(Array(0, 12, 9, 13), Array(15, 11, 10, 14), Array(3, 7, 2, 5), Array(4, 8, 6, 1)).flatten, 0, 0)
}

object Solver extends App {
implicit val ob = new Ordering[Board] { override def compare(x: Board, y: Board) = { y.aStar() - x.aStar() } }

def solve(initBoard: Board) {
val start = System.currentTimeMillis()
var found = false
var head = initBoard
val pq: mutable.PriorityQueue[Board] = mutable.PriorityQueue.from(List(head))
val visited = mutable.HashSet[String]()
var explore = 0

while (pq.nonEmpty && !found) {
head = pq.dequeue()
visited.add(head.key)
if (!head.key.equals(finalBoard.key)) {
val newChildren = head.children.filter(b ⇒ !visited.contains(b.key))
visited.addAll(newChildren.map(_.key))
pq.enqueue(newChildren: _*)
if (explore % 10000 == 0)
println(s"# $explore: A* ${head.aStar()} g:${head.travelled()} h:${head.heuristic()} q.size = ${pq.size}")
explore += 1
} else found = true
}
if (found) {
println(s"Exploring $explore states, found solution with length ${head.parent.length} for board:")
println(initBoard.format)
println(head.parent.mkString.reverse)
}
println("Total time: " + (System.currentTimeMillis() - start) / 1000.0 + " seconds")
}
solve(startEasy)
}
</lang>

{{out}}
<pre>
Exploring 698181 states, found solution with length 52 for board:
15 14 1 6
9 11 4 12
0 10 7 3
13 8 5 2
rrruldluuldrurdddluulurrrdlddruldluurddlulurruldrrdd
Total time: 44.648 seconds
</pre>