{
  "id": "P054",
  "slug": "sliding-puzzle",
  "key": "P054-sliding-puzzle",
  "title": "Sliding puzzle",
  "summary": "The 3 x 3 and 4 x 4 sliding puzzles: shuffled boards that are always solvable, typed boards checked by counting the pairs out of order, several tiles slid in one line, and for 3 x 3 the fewest moves found by breadth-first search from both ends.",
  "entry": "main.eml",
  "ui": "terminal",
  "readme": "# P054 - Sliding puzzle\n\nNumbered tiles in a 3 x 3 or 4 x 4 frame with one gap. Slide a tile beside\nthe gap into it, again and again, until the tiles read 1, 2, 3 ... in order\nwith the gap last. Shuffled boards are always solvable. A board you type in\nis checked first, and a 3 x 3 board can be solved in the fewest moves.\n\n- `main.eml` - the menu, shuffling, sliding several tiles in one line,\n  typing in a board, and the hints\n- `board.eml` - boards: the tiles beside the gap, sliding one, counting the\n  pairs out of order, whether a board can be solved, and the drawing\n- `solver.eml` - the fewest moves for a 3 x 3 board\n- `rng.eml` - random numbers written in EML, the generator of P008\n\nHow each part works:\n\n- Only half of all orders of the tiles can be reached. Count the pairs of\n  tiles that are out of order, reading row by row and skipping the gap.\n  A slide along a row changes no pair. A slide along a column moves one\n  tile past the n - 1 tiles between, so it changes n - 1 pairs.\n  - On a 3 x 3 board that is 2 pairs, so the count stays odd or even, and\n    the solved board has 0: only an even count can be solved.\n  - On a 4 x 4 board it is 3 pairs, so the count flips between odd and\n    even, while the gap moves one row. The count plus the gap's row (from\n    the bottom) keeps its parity, which is odd on the solved board.\n- A shuffle puts the tiles in a random order with Fisher-Yates. When the\n  order cannot be solved, the first two tiles trade places. That changes\n  the count by one and pairs every unsolvable order with one solvable\n  order, so every solvable board is still equally likely.\n- The fewest moves come from breadth-first search, as in the corpus case\n  `graph-bfs-traversal`: every board one slide away is a neighbour, and\n  boards are found one distance at a time. It searches from both ends at\n  once - from the board and from the solved board - one level at a time,\n  always on the smaller side, and stops in the first level where the two\n  meet. A 3 x 3 board is at most 31 moves from solved, so each side looks\n  only about half as deep, at a few thousand boards instead of up to\n  181,440.\n- A 4 x 4 board has over ten trillion positions, so it gets no search -\n  only the solvability check and the play.\n\nWhat is checked: menu choices 1 to 7; tiles beside the gap (several in a\nline slide one after another, stopping at the first that is not); a typed\nboard of 9 or 16 numbers using each of 0 to 8, or 0 to 15, once; size 3 or\n4 and a seed from 0 to 999999999 for a new puzzle. An empty answer cancels.\n\nSessions: `sessions/basic.in` starts from the default board (seed 2026).\nA hint says it can be solved in 16 moves. After 4 1 2, slid in one line,\n13 are left, and the solution's 13 tiles, typed in one line, solve it in\n16 moves - the fewest. Then a 4 x 4 board (seed 5) gets two slides and\nasks for a hint, which only a 3 x 3 board has.\n\n`sessions/bad-input.in` gives:\n- menu choices 0 and x;\n- an empty slide, 99, a, \"4 99\" (the 4 slides, then it stops at 99) and\n  \"x y\";\n- typed boards: three numbers; a board with 8 twice; 3 x 3 with 8 and 7\n  swapped (1 pair out of order, so it cannot be solved); and 4 x 4 with 15\n  and 14 swapped (1 pair, plus the gap in row 1, adds up to an even 2);\n- a typed board one slide from solved, solved with 8, then a slide and a\n  hint on the solved board;\n- a new puzzle that first gets sizes 5 and 2, then is cancelled at each\n  question.\n\nBuilt on the verified corpus case `graph-bfs-traversal` (breadth-first\ntraversal with a queue and a dict of the nodes already visited).\n",
  "modules": [
    {
      "name": "main.eml",
      "eml": "# P054 sliding puzzle: slide the tiles into the gap until they read in\n# order, on a 3 x 3 or 4 x 4 board. Shuffled boards are always solvable;\n# a board you type is checked first, and a 3 x 3 board can be solved in the\n# fewest moves by breadth-first search.\nimport board\nimport solver\nimport rng\n\ndef trim(s):\n    0 => i\n    len(s) => j\n    while i < j and s[i] == \" \":\n        i + 1 => i\n    while j > i and s[j - 1] == \" \":\n        j - 1 => j\n    return s[i:j]\n\ndef words(s):\n    [] => out\n    \"\" => word\n    for c in s + \" \":\n        if c == \" \" or c == \",\":\n            if word != \"\":\n                out + [word] => out\n            \"\" => word\n        else:\n            word + c => word\n    return out\n\ndef number(s):\n    if s == \"\" or len(s) > 9:\n        return -1\n    0 => n\n    for c in s:\n        if not (c >= \"0\" and c <= \"9\"):\n            return -1\n        n * 10 + int(c) => n\n    return n\n\ndef counted(n, one, many):\n    if n == 1:\n        return \"1 \" + one\n    return str(n) + \" \" + many\n\ndef joined(xs):\n    \"\" => s\n    for x in xs:\n        if s != \"\":\n            s + \" \" => s\n        s + str(x) => s\n    return s\n\ndef show(p):\n    for line in board.drawn(p[0], p[1]):\n        line ^0\n    if p[0] == board.solved(p[1]):\n        (\"Solved in \" + counted(p[2], \"move\", \"moves\") + \".\") ^0\n    else:\n        (\"Moves \" + str(p[2]) + \". Slide one of: \" + joined(board.slides(p[0], p[1])) + \".\") ^0\n\ndef shuffled(n, g):\n    # A random order of the tiles and the gap (Fisher-Yates on EML's own\n    # random numbers). Half of all orders cannot be solved; for those, two\n    # tiles trade places - that changes the inversions by one and makes the\n    # board solvable, the same as lifting two tiles out and putting them back\n    # the other way round.\n    board.solved(n) => b\n    for k in [0:n * n - 2]:\n        n * n - 1 - k => i\n        g.below(i + 1) => j\n        b[i] => t\n        b[j] => b[i]\n        t => b[j]\n    if not board.solvable(b, n):\n        -1 => a\n        -1 => c\n        for i in [0:n * n - 1]:\n            if b[i] != 0:\n                if a == -1:\n                    i => a\n                elif c == -1:\n                    i => c\n        b[a] => t\n        b[c] => b[a]\n        t => b[c]\n    return b\n\ndef ask_number(prompt, low, high):\n    while True:\n        trim(input(prompt + \" (\" + str(low) + \" to \" + str(high) + \")> \")) => answer\n        if answer == \"\":\n            return -1\n        number(answer) => n\n        if n >= low and n <= high:\n            return n\n        (\"Type a number from \" + str(low) + \" to \" + str(high) + \".\") ^0\n\ndef new_puzzle(p):\n    ask_number(\"size\", 3, 4) => n\n    if n == -1:\n        \"Cancelled.\" ^0\n        return p\n    ask_number(\"seed\", 0, 999999999) => seed\n    if seed == -1:\n        \"Cancelled.\" ^0\n        return p\n    [shuffled(n, rng.Rng(seed)), n, 0] => p\n    (\"A shuffled \" + str(n) + \" x \" + str(n) + \" board, seed \" + str(seed) + \".\") ^0\n    show(p)\n    return p\n\ndef slide(p):\n    if p[0] == board.solved(p[1]):\n        \"The board is solved - start a new one or type one in.\" ^0\n        return\n    words(trim(input(\"tiles to slide, in order> \"))) => ws\n    if len(ws) == 0:\n        \"Cancelled.\" ^0\n        return\n    0 => done\n    for w in ws:\n        number(w) => tile\n        False => ok\n        for t in board.slides(p[0], p[1]):\n            if t == tile:\n                True => ok\n        if not ok:\n            if done > 0:\n                (\"Slid \" + str(done) + \", then stopped: \" + w + \" is not beside the gap.\") ^0\n            else:\n                (w + \" is not beside the gap.\") ^0\n            show(p)\n            return\n        board.slid(p[0], tile) => p[0]\n        p[2] + 1 => p[2]\n        done + 1 => done\n        if p[0] == board.solved(p[1]):\n            show(p)\n            return\n    show(p)\n\ndef hint(p, all_moves):\n    if p[1] != 3:\n        \"Only a 3 x 3 board can be searched - a 4 x 4 one has over ten trillion positions.\" ^0\n        return\n    if p[0] == board.solved(3):\n        \"The board is solved.\" ^0\n        return\n    solver.solution(p[0]) => way\n    if all_moves:\n        (\"The fewest moves from here: \" + str(len(way)) + \" - slide \" + joined(way) + \".\") ^0\n    else:\n        (\"Slide \" + str(way[0]) + \": from here the board can be solved in \" + counted(len(way), \"move\", \"moves\") + \".\") ^0\n\ndef typed(p):\n    trim(input(\"the board, row by row, 0 for the gap> \")) => answer\n    if answer == \"\":\n        \"Cancelled.\" ^0\n        return p\n    words(answer) => ws\n    if len(ws) != 9 and len(ws) != 16:\n        \"Type 9 numbers for 3 x 3 or 16 for 4 x 4.\" ^0\n        return p\n    3 => n\n    if len(ws) == 16:\n        4 => n\n    [] => b\n    [False] * (n * n) => seen\n    for w in ws:\n        number(w) => x\n        if x < 0 or x >= n * n or seen[x]:\n            (\"Use each number from 0 to \" + str(n * n - 1) + \" once.\") ^0\n            return p\n        True => seen[x]\n        b + [x] => b\n    board.inversions(b) => inv\n    if not board.solvable(b, n):\n        if n == 3:\n            (\"That board cannot be solved: it has \" + counted(inv, \"pair\", \"pairs\") + \" of tiles out of order, an odd number, and every slide keeps that number odd or even.\") ^0\n        else:\n            (\"That board cannot be solved: its \" + counted(inv, \"pair\", \"pairs\") + \" out of order and the gap's row from the bottom, \" + str(n - int(board.gap(b) / n)) + \", add up to an even number, and every slide keeps that sum odd or even.\") ^0\n        return p\n    [b, n, 0] => p\n    \"That board can be solved.\" ^0\n    show(p)\n    return p\n\n\"== Sliding puzzle ==\" ^0\n\"Slide tiles into the gap until they read in order with the gap last.\" ^0\n[shuffled(3, rng.Rng(2026)), 3, 0] => p\n\"A shuffled 3 x 3 board, seed 2026.\" ^0\nshow(p)\nTrue => running\nwhile running:\n    \"\" ^0\n    \"1) slide  2) hint  3) solve  4) new puzzle  5) type a board  6) show  7) quit\" ^0\n    trim(input(\"choice> \")) => choice\n    if choice == \"1\":\n        slide(p)\n    elif choice == \"2\":\n        hint(p, False)\n    elif choice == \"3\":\n        hint(p, True)\n    elif choice == \"4\":\n        new_puzzle(p) => p\n    elif choice == \"5\":\n        typed(p) => p\n    elif choice == \"6\":\n        show(p)\n    elif choice == \"7\":\n        False => running\n    else:\n        \"Pick a number from 1 to 7.\" ^0\n\"Bye.\" ^0\n",
      "python": "import board\nimport solver\nimport rng\n\ndef trim(s):\n    i = 0\n    j = len(s)\n    while i < j and s[i] == \" \":\n        i = i + 1\n    while j > i and s[j - 1] == \" \":\n        j = j - 1\n    return s[i:j]\n\ndef words(s):\n    out = []\n    word = \"\"\n    for c in s + \" \":\n        if c == \" \" or c == \",\":\n            if word != \"\":\n                out = out + [word]\n            word = \"\"\n        else:\n            word = word + c\n    return out\n\ndef number(s):\n    if s == \"\" or len(s) > 9:\n        return -1\n    n = 0\n    for c in s:\n        if not (c >= \"0\" and c <= \"9\"):\n            return -1\n        n = n * 10 + int(c)\n    return n\n\ndef counted(n, one, many):\n    if n == 1:\n        return \"1 \" + one\n    return str(n) + \" \" + many\n\ndef joined(xs):\n    s = \"\"\n    for x in xs:\n        if s != \"\":\n            s = s + \" \"\n        s = s + str(x)\n    return s\n\ndef show(p):\n    for line in board.drawn(p[0], p[1]):\n        print(line)\n    if p[0] == board.solved(p[1]):\n        print(\"Solved in \" + counted(p[2], \"move\", \"moves\") + \".\")\n    else:\n        print(\"Moves \" + str(p[2]) + \". Slide one of: \" + joined(board.slides(p[0], p[1])) + \".\")\n\ndef shuffled(n, g):\n    b = board.solved(n)\n    for k in range(0, n * n - 2+1):\n        i = n * n - 1 - k\n        j = g.below(i + 1)\n        t = b[i]\n        b[i] = b[j]\n        b[j] = t\n    if not board.solvable(b, n):\n        a = -1\n        c = -1\n        for i in range(0, n * n):\n            if b[i] != 0:\n                if a == -1:\n                    a = i\n                elif c == -1:\n                    c = i\n        t = b[a]\n        b[a] = b[c]\n        b[c] = t\n    return b\n\ndef ask_number(prompt, low, high):\n    while True:\n        answer = trim(input(prompt + \" (\" + str(low) + \" to \" + str(high) + \")> \"))\n        if answer == \"\":\n            return -1\n        n = number(answer)\n        if n >= low and n <= high:\n            return n\n        print(\"Type a number from \" + str(low) + \" to \" + str(high) + \".\")\n\ndef new_puzzle(p):\n    n = ask_number(\"size\", 3, 4)\n    if n == -1:\n        print(\"Cancelled.\")\n        return p\n    seed = ask_number(\"seed\", 0, 999999999)\n    if seed == -1:\n        print(\"Cancelled.\")\n        return p\n    p = [shuffled(n, rng.Rng(seed)), n, 0]\n    print(\"A shuffled \" + str(n) + \" x \" + str(n) + \" board, seed \" + str(seed) + \".\")\n    show(p)\n    return p\n\ndef slide(p):\n    if p[0] == board.solved(p[1]):\n        print(\"The board is solved - start a new one or type one in.\")\n        return\n    ws = words(trim(input(\"tiles to slide, in order> \")))\n    if len(ws) == 0:\n        print(\"Cancelled.\")\n        return\n    done = 0\n    for w in ws:\n        tile = number(w)\n        ok = False\n        for t in board.slides(p[0], p[1]):\n            if t == tile:\n                ok = True\n        if not ok:\n            if done > 0:\n                print(\"Slid \" + str(done) + \", then stopped: \" + w + \" is not beside the gap.\")\n            else:\n                print(w + \" is not beside the gap.\")\n            show(p)\n            return\n        p[0] = board.slid(p[0], tile)\n        p[2] = p[2] + 1\n        done = done + 1\n        if p[0] == board.solved(p[1]):\n            show(p)\n            return\n    show(p)\n\ndef hint(p, all_moves):\n    if p[1] != 3:\n        print(\"Only a 3 x 3 board can be searched - a 4 x 4 one has over ten trillion positions.\")\n        return\n    if p[0] == board.solved(3):\n        print(\"The board is solved.\")\n        return\n    way = solver.solution(p[0])\n    if all_moves:\n        print(\"The fewest moves from here: \" + str(len(way)) + \" - slide \" + joined(way) + \".\")\n    else:\n        print(\"Slide \" + str(way[0]) + \": from here the board can be solved in \" + counted(len(way), \"move\", \"moves\") + \".\")\n\ndef typed(p):\n    answer = trim(input(\"the board, row by row, 0 for the gap> \"))\n    if answer == \"\":\n        print(\"Cancelled.\")\n        return p\n    ws = words(answer)\n    if len(ws) != 9 and len(ws) != 16:\n        print(\"Type 9 numbers for 3 x 3 or 16 for 4 x 4.\")\n        return p\n    n = 3\n    if len(ws) == 16:\n        n = 4\n    b = []\n    seen = [False] * (n * n)\n    for w in ws:\n        x = number(w)\n        if x < 0 or x >= n * n or seen[x]:\n            print(\"Use each number from 0 to \" + str(n * n - 1) + \" once.\")\n            return p\n        seen[x] = True\n        b = b + [x]\n    inv = board.inversions(b)\n    if not board.solvable(b, n):\n        if n == 3:\n            print(\"That board cannot be solved: it has \" + counted(inv, \"pair\", \"pairs\") + \" of tiles out of order, an odd number, and every slide keeps that number odd or even.\")\n        else:\n            print(\"That board cannot be solved: its \" + counted(inv, \"pair\", \"pairs\") + \" out of order and the gap's row from the bottom, \" + str(n - int(board.gap(b) / n)) + \", add up to an even number, and every slide keeps that sum odd or even.\")\n        return p\n    p = [b, n, 0]\n    print(\"That board can be solved.\")\n    show(p)\n    return p\n\nprint(\"== Sliding puzzle ==\")\nprint(\"Slide tiles into the gap until they read in order with the gap last.\")\np = [shuffled(3, rng.Rng(2026)), 3, 0]\nprint(\"A shuffled 3 x 3 board, seed 2026.\")\nshow(p)\nrunning = True\nwhile running:\n    print(\"\")\n    print(\"1) slide  2) hint  3) solve  4) new puzzle  5) type a board  6) show  7) quit\")\n    choice = trim(input(\"choice> \"))\n    if choice == \"1\":\n        slide(p)\n    elif choice == \"2\":\n        hint(p, False)\n    elif choice == \"3\":\n        hint(p, True)\n    elif choice == \"4\":\n        p = new_puzzle(p)\n    elif choice == \"5\":\n        p = typed(p)\n    elif choice == \"6\":\n        show(p)\n    elif choice == \"7\":\n        running = False\n    else:\n        print(\"Pick a number from 1 to 7.\")\nprint(\"Bye.\")\n"
    },
    {
      "name": "board.eml",
      "eml": "# P054 sliding puzzle - boards. A board of size n is a list of n * n\n# numbers, row by row, 0 for the gap; it is solved when it reads 1, 2, ...,\n# n * n - 1 with the gap last.\n\ndef solved(n):\n    [] => b\n    for k in [1:n * n - 1]:\n        b + [k] => b\n    return b + [0]\n\ndef gap(b):\n    for i in [0:len(b) - 1]:\n        if b[i] == 0:\n            return i\n    return -1\n\ndef slides(b, n):\n    # The tiles that can slide into the gap: the ones beside it, above it or\n    # below it - in the order up, left, right, down from the gap.\n    gap(b) => g\n    int(g / n) => r\n    g - r * n => c\n    [] => out\n    if r > 0:\n        out + [b[g - n]] => out\n    if c > 0:\n        out + [b[g - 1]] => out\n    if c < n - 1:\n        out + [b[g + 1]] => out\n    if r < n - 1:\n        out + [b[g + n]] => out\n    return out\n\ndef slid(b, tile):\n    # A new board with tile moved into the gap (tile must be beside it).\n    b[0:len(b)] => nb\n    gap(b) => g\n    for i in [0:len(b) - 1]:\n        if b[i] == tile:\n            0 => nb[i]\n    tile => nb[g]\n    return nb\n\ndef inversions(b):\n    # Pairs of tiles in the wrong order, reading row by row and skipping\n    # the gap.\n    0 => count\n    for i in [0:len(b) - 1]:\n        if b[i] != 0:\n            for j in [i + 1:len(b) - 1]:\n                if b[j] != 0 and b[j] < b[i]:\n                    count + 1 => count\n    return count\n\ndef solvable(b, n):\n    # A slide along a row changes no pair's order. A slide along a column\n    # jumps one tile over n - 1 others, changing the order of n - 1 pairs:\n    # for odd n that keeps the parity of the inversions, so only boards with\n    # an even count can reach the solved board (0 inversions). For even n\n    # every vertical slide flips the parity and moves the gap one row, so\n    # inversions + the gap's row counted from the bottom (1 for the bottom\n    # row) keeps its parity, which is odd on the solved board.\n    inversions(b) => inv\n    if n % 2 == 1:\n        return inv % 2 == 0\n    int(gap(b) / n) => r\n    return (inv + n - r) % 2 == 1\n\ndef key(b):\n    # A board as text, one character a tile (3 x 3 only: tiles 0 to 8).\n    \"\" => s\n    for x in b:\n        s + str(x) => s\n    return s\n\ndef board_of(s):\n    [] => b\n    for ch in s:\n        b + [int(ch)] => b\n    return b\n\ndef drawn(b, n):\n    [] => lines\n    \"+\" + \"----\" * n + \"+\" => rule\n    lines + [rule] => lines\n    for r in [0:n - 1]:\n        \"|\" => line\n        for c in [0:n - 1]:\n            b[r * n + c] => x\n            \"  \" => cell\n            if x != 0:\n                str(x) => cell\n                if len(cell) == 1:\n                    \" \" + cell => cell\n            line + \" \" + cell + \" \" => line\n        lines + [line + \"|\"] => lines\n    lines + [rule] => lines\n    return lines\n",
      "python": "def solved(n):\n    b = []\n    for k in range(1, n * n):\n        b = b + [k]\n    return b + [0]\n\ndef gap(b):\n    for i in range(0, len(b)):\n        if b[i] == 0:\n            return i\n    return -1\n\ndef slides(b, n):\n    g = gap(b)\n    r = int(g / n)\n    c = g - r * n\n    out = []\n    if r > 0:\n        out = out + [b[g - n]]\n    if c > 0:\n        out = out + [b[g - 1]]\n    if c < n - 1:\n        out = out + [b[g + 1]]\n    if r < n - 1:\n        out = out + [b[g + n]]\n    return out\n\ndef slid(b, tile):\n    nb = b[0:len(b)]\n    g = gap(b)\n    for i in range(0, len(b)):\n        if b[i] == tile:\n            nb[i] = 0\n    nb[g] = tile\n    return nb\n\ndef inversions(b):\n    count = 0\n    for i in range(0, len(b)):\n        if b[i] != 0:\n            for j in range(i + 1, len(b)):\n                if b[j] != 0 and b[j] < b[i]:\n                    count = count + 1\n    return count\n\ndef solvable(b, n):\n    inv = inversions(b)\n    if n % 2 == 1:\n        return inv % 2 == 0\n    r = int(gap(b) / n)\n    return (inv + n - r) % 2 == 1\n\ndef key(b):\n    s = \"\"\n    for x in b:\n        s = s + str(x)\n    return s\n\ndef board_of(s):\n    b = []\n    for ch in s:\n        b = b + [int(ch)]\n    return b\n\ndef drawn(b, n):\n    lines = []\n    rule = \"+\" + \"----\" * n + \"+\"\n    lines = lines + [rule]\n    for r in range(0, n):\n        line = \"|\"\n        for c in range(0, n):\n            x = b[r * n + c]\n            cell = \"  \"\n            if x != 0:\n                cell = str(x)\n                if len(cell) == 1:\n                    cell = \" \" + cell\n            line = line + \" \" + cell + \" \"\n        lines = lines + [line + \"|\"]\n    lines = lines + [rule]\n    return lines\n"
    },
    {
      "name": "solver.eml",
      "eml": "# P054 sliding puzzle - the fewest moves for a 3 x 3 board, by breadth-first\n# search as in the corpus case graph-bfs-traversal: every board one slide\n# away is a neighbour, and the search finds every board at distance 1, then\n# 2, and so on, so the first time it reaches the goal it has a shortest way.\n#\n# It searches from both ends at once - from the puzzle and from the solved\n# board - a whole level at a time, always widening the smaller side, and\n# stops when the two meet. A 3 x 3 board is at most 31 moves from solved,\n# so each side only goes about half as deep, and sees a few thousand boards\n# instead of up to 181,440.\nimport board\n\ndef moves(s):\n    # [tile, the board after sliding it] for every tile beside the gap, a\n    # board as its text key.\n    0 => g\n    while s[g] != \"0\":\n        g + 1 => g\n    int(g / 3) => r\n    g - r * 3 => c\n    [] => targets\n    if r > 0:\n        targets + [g - 3] => targets\n    if c > 0:\n        targets + [g - 1] => targets\n    if c < 2:\n        targets + [g + 1] => targets\n    if r < 2:\n        targets + [g + 3] => targets\n    [] => out\n    for t in targets:\n        \"\" => ns\n        for i in [0:8]:\n            if i == g:\n                ns + s[t] => ns\n            elif i == t:\n                ns + \"0\" => ns\n            else:\n                ns + s[i] => ns\n        out + [[int(s[t]), ns]] => out\n    return out\n\ndef widened(frontier, mine, other):\n    # One level outward from frontier. mine maps each board this side has\n    # seen to [the board it came from, the tile slid, its distance]. Returns\n    # [the next level, the boards in it the other side has seen too].\n    [] => next_level\n    [] => met\n    for s in frontier:\n        mine[s][2] + 1 => d\n        for m in moves(s):\n            m[1] => t\n            if not (t in mine):\n                [s, m[0], d] => mine[t]\n                next_level + [t] => next_level\n                if t in other:\n                    met + [t] => met\n    return [next_level, met]\n\ndef solution(b):\n    # The tiles to slide, in order, for the fewest moves - [] when b is\n    # solved. b must be solvable.\n    board.key(b) => start\n    \"123456780\" => goal\n    if start == goal:\n        return []\n    {} => fwd\n    [\"\", 0, 0] => fwd[start]\n    {} => bwd\n    [\"\", 0, 0] => bwd[goal]\n    [start] => fa\n    [goal] => fb\n    [] => met\n    while len(met) == 0:\n        if len(fa) <= len(fb):\n            widened(fa, fwd, bwd) => res\n            res[0] => fa\n        else:\n            widened(fb, bwd, fwd) => res\n            res[0] => fb\n        res[1] => met\n    # Every board met in this level is the same distance from the side just\n    # widened; the shortest way goes through the one nearest the other end.\n    met[0] => best\n    for t in met:\n        if fwd[t][2] + bwd[t][2] < fwd[best][2] + bwd[best][2]:\n            t => best\n    [] => first_half\n    best => s\n    while s != start:\n        [fwd[s][1]] + first_half => first_half\n        fwd[s][0] => s\n    [] => second_half\n    best => s\n    while s != goal:\n        second_half + [bwd[s][1]] => second_half\n        bwd[s][0] => s\n    return first_half + second_half\n",
      "python": "import board\n\ndef moves(s):\n    g = 0\n    while s[g] != \"0\":\n        g = g + 1\n    r = int(g / 3)\n    c = g - r * 3\n    targets = []\n    if r > 0:\n        targets = targets + [g - 3]\n    if c > 0:\n        targets = targets + [g - 1]\n    if c < 2:\n        targets = targets + [g + 1]\n    if r < 2:\n        targets = targets + [g + 3]\n    out = []\n    for t in targets:\n        ns = \"\"\n        for i in range(0, 9):\n            if i == g:\n                ns = ns + s[t]\n            elif i == t:\n                ns = ns + \"0\"\n            else:\n                ns = ns + s[i]\n        out = out + [[int(s[t]), ns]]\n    return out\n\ndef widened(frontier, mine, other):\n    next_level = []\n    met = []\n    for s in frontier:\n        d = mine[s][2] + 1\n        for m in moves(s):\n            t = m[1]\n            if not t in mine:\n                mine[t] = [s, m[0], d]\n                next_level = next_level + [t]\n                if t in other:\n                    met = met + [t]\n    return [next_level, met]\n\ndef solution(b):\n    start = board.key(b)\n    goal = \"123456780\"\n    if start == goal:\n        return []\n    fwd = {}\n    fwd[start] = [\"\", 0, 0]\n    bwd = {}\n    bwd[goal] = [\"\", 0, 0]\n    fa = [start]\n    fb = [goal]\n    met = []\n    while len(met) == 0:\n        if len(fa) <= len(fb):\n            res = widened(fa, fwd, bwd)\n            fa = res[0]\n        else:\n            res = widened(fb, bwd, fwd)\n            fb = res[0]\n        met = res[1]\n    best = met[0]\n    for t in met:\n        if fwd[t][2] + bwd[t][2] < fwd[best][2] + bwd[best][2]:\n            best = t\n    first_half = []\n    s = best\n    while s != start:\n        first_half = [fwd[s][1]] + first_half\n        s = fwd[s][0]\n    second_half = []\n    s = best\n    while s != goal:\n        second_half = second_half + [bwd[s][1]]\n        s = bwd[s][0]\n    return first_half + second_half\n"
    },
    {
      "name": "rng.eml",
      "eml": "# P054 sliding puzzle - random numbers written in EML: the linear congruential\n# generator of P008 (number guessing), with the constants of the C\n# standard's example rand(). Python's random module is not used, so a seed\n# gives the same board on every machine, and the interpreter can check a\n# whole session byte for byte.\n\nclass Rng:\n    def __init__(self, seed):\n        seed % 2147483648 => self.state\n\n    def step(self):\n        (1103515245 * self.state + 12345) % 2147483648 => self.state\n        return self.state\n\n    def below(self, n):\n        # A number from 0 to n - 1, taken from the high bits of the state: the\n        # low bits of this generator repeat with short periods. Dividing by\n        # 65536 is exact in a float for a state below 2^31.\n        return int(self.step() / 65536) % n\n",
      "python": "class Rng:\n    def __init__(self, seed):\n        self.state = seed % 2147483648\n    def step(self):\n        self.state = (1103515245 * self.state + 12345) % 2147483648\n        return self.state\n    def below(self, n):\n        return int(self.step() / 65536) % n\n"
    }
  ],
  "sessions": [
    {
      "name": "bad-input",
      "input": "0\nx\n1\n\n1\n99\n1\na\n1\n4 99\n1\nx y\n5\n1 2 3\n5\n1 2 3 4 5 6 7 8 8\n5\n1 2 3 4 5 6 8 7 0\n5\n1 2 3 4 5 6 7 8 9 10 11 12 13 15 14 0\n5\n1 2 3 4 5 6 7 0 8\n1\n8\n1\n2\n4\n5\n2\n\n4\n3\n\n7\n",
      "screen": "== Sliding puzzle ==\nSlide tiles into the gap until they read in order with the gap last.\nA shuffled 3 x 3 board, seed 2026.\n+------------+\n|  1   2   6 |\n|  4   8   3 |\n|      7   5 |\n+------------+\nMoves 0. Slide one of: 4 7.\n\n1) slide  2) hint  3) solve  4) new puzzle  5) type a board  6) show  7) quit\nchoice> 0\nPick a number from 1 to 7.\n\n1) slide  2) hint  3) solve  4) new puzzle  5) type a board  6) show  7) quit\nchoice> x\nPick a number from 1 to 7.\n\n1) slide  2) hint  3) solve  4) new puzzle  5) type a board  6) show  7) quit\nchoice> 1\ntiles to slide, in order> \nCancelled.\n\n1) slide  2) hint  3) solve  4) new puzzle  5) type a board  6) show  7) quit\nchoice> 1\ntiles to slide, in order> 99\n99 is not beside the gap.\n+------------+\n|  1   2   6 |\n|  4   8   3 |\n|      7   5 |\n+------------+\nMoves 0. Slide one of: 4 7.\n\n1) slide  2) hint  3) solve  4) new puzzle  5) type a board  6) show  7) quit\nchoice> 1\ntiles to slide, in order> a\na is not beside the gap.\n+------------+\n|  1   2   6 |\n|  4   8   3 |\n|      7   5 |\n+------------+\nMoves 0. Slide one of: 4 7.\n\n1) slide  2) hint  3) solve  4) new puzzle  5) type a board  6) show  7) quit\nchoice> 1\ntiles to slide, in order> 4 99\nSlid 1, then stopped: 99 is not beside the gap.\n+------------+\n|  1   2   6 |\n|      8   3 |\n|  4   7   5 |\n+------------+\nMoves 1. Slide one of: 1 8 4.\n\n1) slide  2) hint  3) solve  4) new puzzle  5) type a board  6) show  7) quit\nchoice> 1\ntiles to slide, in order> x y\nx is not beside the gap.\n+------------+\n|  1   2   6 |\n|      8   3 |\n|  4   7   5 |\n+------------+\nMoves 1. Slide one of: 1 8 4.\n\n1) slide  2) hint  3) solve  4) new puzzle  5) type a board  6) show  7) quit\nchoice> 5\nthe board, row by row, 0 for the gap> 1 2 3\nType 9 numbers for 3 x 3 or 16 for 4 x 4.\n\n1) slide  2) hint  3) solve  4) new puzzle  5) type a board  6) show  7) quit\nchoice> 5\nthe board, row by row, 0 for the gap> 1 2 3 4 5 6 7 8 8\nUse each number from 0 to 8 once.\n\n1) slide  2) hint  3) solve  4) new puzzle  5) type a board  6) show  7) quit\nchoice> 5\nthe board, row by row, 0 for the gap> 1 2 3 4 5 6 8 7 0\nThat board cannot be solved: it has 1 pair of tiles out of order, an odd number, and every slide keeps that number odd or even.\n\n1) slide  2) hint  3) solve  4) new puzzle  5) type a board  6) show  7) quit\nchoice> 5\nthe board, row by row, 0 for the gap> 1 2 3 4 5 6 7 8 9 10 11 12 13 15 14 0\nThat board cannot be solved: its 1 pair out of order and the gap's row from the bottom, 1, add up to an even number, and every slide keeps that sum odd or even.\n\n1) slide  2) hint  3) solve  4) new puzzle  5) type a board  6) show  7) quit\nchoice> 5\nthe board, row by row, 0 for the gap> 1 2 3 4 5 6 7 0 8\nThat board can be solved.\n+------------+\n|  1   2   3 |\n|  4   5   6 |\n|  7       8 |\n+------------+\nMoves 0. Slide one of: 5 7 8.\n\n1) slide  2) hint  3) solve  4) new puzzle  5) type a board  6) show  7) quit\nchoice> 1\ntiles to slide, in order> 8\n+------------+\n|  1   2   3 |\n|  4   5   6 |\n|  7   8     |\n+------------+\nSolved in 1 move.\n\n1) slide  2) hint  3) solve  4) new puzzle  5) type a board  6) show  7) quit\nchoice> 1\nThe board is solved - start a new one or type one in.\n\n1) slide  2) hint  3) solve  4) new puzzle  5) type a board  6) show  7) quit\nchoice> 2\nThe board is solved.\n\n1) slide  2) hint  3) solve  4) new puzzle  5) type a board  6) show  7) quit\nchoice> 4\nsize (3 to 4)> 5\nType a number from 3 to 4.\nsize (3 to 4)> 2\nType a number from 3 to 4.\nsize (3 to 4)> \nCancelled.\n\n1) slide  2) hint  3) solve  4) new puzzle  5) type a board  6) show  7) quit\nchoice> 4\nsize (3 to 4)> 3\nseed (0 to 999999999)> \nCancelled.\n\n1) slide  2) hint  3) solve  4) new puzzle  5) type a board  6) show  7) quit\nchoice> 7\nBye.\n",
      "interpreter": "equal"
    },
    {
      "name": "basic",
      "input": "2\n1\n4 1 2\n2\n3\n1\n6 3 8 6 2 1 4 7 5 8 6 5 8\n4\n4\n5\n1\n13 5\n2\n6\n7\n",
      "screen": "== Sliding puzzle ==\nSlide tiles into the gap until they read in order with the gap last.\nA shuffled 3 x 3 board, seed 2026.\n+------------+\n|  1   2   6 |\n|  4   8   3 |\n|      7   5 |\n+------------+\nMoves 0. Slide one of: 4 7.\n\n1) slide  2) hint  3) solve  4) new puzzle  5) type a board  6) show  7) quit\nchoice> 2\nSlide 4: from here the board can be solved in 16 moves.\n\n1) slide  2) hint  3) solve  4) new puzzle  5) type a board  6) show  7) quit\nchoice> 1\ntiles to slide, in order> 4 1 2\n+------------+\n|  2       6 |\n|  1   8   3 |\n|  4   7   5 |\n+------------+\nMoves 3. Slide one of: 2 6 8.\n\n1) slide  2) hint  3) solve  4) new puzzle  5) type a board  6) show  7) quit\nchoice> 2\nSlide 6: from here the board can be solved in 13 moves.\n\n1) slide  2) hint  3) solve  4) new puzzle  5) type a board  6) show  7) quit\nchoice> 3\nThe fewest moves from here: 13 - slide 6 3 8 6 2 1 4 7 5 8 6 5 8.\n\n1) slide  2) hint  3) solve  4) new puzzle  5) type a board  6) show  7) quit\nchoice> 1\ntiles to slide, in order> 6 3 8 6 2 1 4 7 5 8 6 5 8\n+------------+\n|  1   2   3 |\n|  4   5   6 |\n|  7   8     |\n+------------+\nSolved in 16 moves.\n\n1) slide  2) hint  3) solve  4) new puzzle  5) type a board  6) show  7) quit\nchoice> 4\nsize (3 to 4)> 4\nseed (0 to 999999999)> 5\nA shuffled 4 x 4 board, seed 5.\n+----------------+\n| 12   1   9  15 |\n|  8   4   3  10 |\n|  7  11  14   6 |\n|  2   5  13     |\n+----------------+\nMoves 0. Slide one of: 6 13.\n\n1) slide  2) hint  3) solve  4) new puzzle  5) type a board  6) show  7) quit\nchoice> 1\ntiles to slide, in order> 13 5\n+----------------+\n| 12   1   9  15 |\n|  8   4   3  10 |\n|  7  11  14   6 |\n|  2       5  13 |\n+----------------+\nMoves 2. Slide one of: 11 2 5.\n\n1) slide  2) hint  3) solve  4) new puzzle  5) type a board  6) show  7) quit\nchoice> 2\nOnly a 3 x 3 board can be searched - a 4 x 4 one has over ten trillion positions.\n\n1) slide  2) hint  3) solve  4) new puzzle  5) type a board  6) show  7) quit\nchoice> 6\n+----------------+\n| 12   1   9  15 |\n|  8   4   3  10 |\n|  7  11  14   6 |\n|  2       5  13 |\n+----------------+\nMoves 2. Slide one of: 11 2 5.\n\n1) slide  2) hint  3) solve  4) new puzzle  5) type a board  6) show  7) quit\nchoice> 7\nBye.\n",
      "interpreter": "equal"
    }
  ],
  "builtOn": [
    {
      "slug": "graph-bfs-traversal",
      "caseId": "085-graph-bfs-traversal",
      "title": "Graph BFS traversal"
    }
  ],
  "updated": "2026-10-10"
}
