15 puzzle solver

From Rosetta Code
Task
15 puzzle solver
You are encouraged to solve this task according to the task description, using any language you may know.

Your task is to write a program that finds a solution in the fewest moves possible single moves to a random Fifteen Puzzle Game.
For this task you will be using the following puzzle:

15 14  1  6
 9 11  4 12
 0 10  7  3
13  8  5  2


Solution:
 1  2  3  4
 5  6  7  8
 9 10 11 12
13 14 15  0

The output must show the moves' directions, like so: left, left, left, down, right... and so on.
There are two solutions, of fifty-two moves:
rrrulddluuuldrurdddrullulurrrddldluurddlulurruldrdrd
rrruldluuldrurdddluulurrrdlddruldluurddlulurruldrrdd
see: Pretty Print of Optimal Solution

Finding either one, or both is an acceptable result.

Extra credit.

Solve the following problem:

  0 12  9 13
 15 11 10 14
  3  7  2  5
  4  8  6  1


Related Task



11l

Translation of: Nim
-V
   nr = [3, 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3]
   nc = [3, 0, 1, 2, 3, 0, 1, 2, 3, 0, 1, 2, 3, 0, 1, 2]

T Solver
   n = 0
   np = 0
   n0 = [0] * 100
   n2 = [UInt64(0)] * 100
   n3 = [Char("\0")] * 100
   n4 = [0] * 100

   F (values)
      .n0[0] = values.index(0)

      UInt64 tmp = 0
      L(val) values
         tmp = (tmp << 4) [|] val
      .n2[0] = tmp

   F fI()
      V n = .n
      V g = (11 - .n0[n]) * 4
      V a = .n2[n] [&] (UInt64(15) << g)
      .n0[n + 1] = .n0[n] + 4
      .n2[n + 1] = .n2[n] - a + (a << 16)
      .n3[n + 1] = Char(‘d’)
      .n4[n + 1] = .n4[n] + Int(:nr[Int(a >> g)] > .n0[n] I/ 4)

   F fG()
      V n = .n
      V g = (19 - .n0[n]) * 4
      V a = .n2[n] [&] (UInt64(15) << g)
      .n0[n + 1] = .n0[n] - 4
      .n2[n + 1] = .n2[n] - a + (a >> 16)
      .n3[n + 1] = Char(‘u’)
      .n4[n + 1] = .n4[n] + Int(:nr[Int(a >> g)] < .n0[n] I/ 4)

   F fE()
      V n = .n
      V g = (14 - .n0[n]) * 4
      V a = .n2[n] [&] (UInt64(15) << g)
      .n0[n + 1] = .n0[n] + 1
      .n2[n + 1] = .n2[n] - a + (a << 4)
      .n3[n + 1] = Char(‘r’)
      .n4[n + 1] = .n4[n] + Int(:nc[Int(a >> g)] > .n0[n] % 4)

   F fL()
      V n = .n
      V g = (16 - .n0[n]) * 4
      V a = .n2[n] [&] (UInt64(15) << g)
      .n0[n + 1] = .n0[n] - 1
      .n2[n + 1] = .n2[n] - a + (a >> 4)
      .n3[n + 1] = Char(‘l’)
      .n4[n + 1] = .n4[n] + Int(:nc[Int(a >> g)] < .n0[n] % 4)

   F fY()
      I .n2[.n] == 1234'5678'9ABC'DEF0
         R 1B
      I .n4[.n] <= .np
         R .fN()
      R 0B

   F fN() -> Bool
      V n = .n
      I .n3[n] != ‘u’ & .n0[n] I/ 4 < 3 {.fI(); .n++; I .fY() {R 1B}; .n--}
      I .n3[n] != ‘d’ & .n0[n] I/ 4 > 0 {.fG(); .n++; I .fY() {R 1B}; .n--}
      I .n3[n] != ‘l’ & .n0[n]  % 4 < 3 {.fE(); .n++; I .fY() {R 1B}; .n--}
      I .n3[n] != ‘r’ & .n0[n]  % 4 > 0 {.fL(); .n++; I .fY() {R 1B}; .n--}
      R 0B

   F run()
      L !.fY()
         .np++
      print(‘Solution found with ’(.n)‘ moves: ’, end' ‘’)
      L(g) 1 .. .n
         print(.n3[g], end' ‘’)
      print(‘.’)

V solver = Solver([15, 14,  1,  6,
                    9, 11,  4, 12,
                    0, 10,  7,  3,
                   13,  8,  5,  2])
solver.run()
Output:
Solution found with 52 moves: rrrulddluuuldrurdddrullulurrrddldluurddlulurruldrdrd.

AArch64 Assembly

Works with: as version Raspberry Pi 3B version Buster 64 bits
/* ARM assembly AARCH64 Raspberry PI 3B */
/*  program puzzle15solvex64.s   */
/* this program is a adaptation algorithme C++ and go rosetta code */
/* thanck for the creators */ 
/* 1 byte by box on game board */

/* create a file with nano  */
/*  15,  2,   3,   4
    5,   6,   7,   1
    9,   10,  8,   11
   13,  14,  12, 0     */
   
/*   Run this programm : puzzle15solver64 <file name> */
/*   wait several minutes for résult */

/*******************************************/
/* Constantes file                         */
/*******************************************/
/* for this file see task include a file in language AArch64 assembly*/
.include "../includeConstantesARM64.inc"

.equ TRUE, 1
.equ FALSE, 0

.equ SIZE,           4
.equ NBBOX,          SIZE * SIZE
.equ TAILLEBUFFER,   100
.equ NBMAXIELEMENTS, 100

.equ CONST_I,    1
.equ CONST_G,    8
.equ CONST_E,    2
.equ CONST_L,    4

/*********************************/
/* Initialized data              */
/*********************************/
.data
szMessTitre:           .asciz "File name : "
sMessResult:           .ascii " "
sMessValeur:           .fill 22, 1, ' '             // size => 21
szCarriageReturn:      .asciz "\n"
szMessCounterSolution: .asciz "Solution in @ moves : \n"

szMessErreur:          .asciz "Error detected.\n"
szMessImpossible:      .asciz "!!! Impossible solution !!!\n"
szMessErrBuffer:       .asciz "buffer size too less !!"
szMessSpaces:          .asciz "    "

qTabNr:  .quad 3, 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3
qTabNc:  .quad 3, 0, 1, 2, 3, 0, 1, 2, 3, 0, 1, 2, 3, 0, 1, 2
/*********************************/
/* UnInitialized data            */
/*********************************/
.bss
.align 8
sZoneConv:      .skip 24
qAdrHeap:       .skip 8
tbBox:          .skip SIZE * SIZE           // game boxes
qAdrFicName:    .skip 8
qTabN0:         .skip 8 * NBMAXIELEMENTS    // empty box
qTabN3:         .skip 8 * NBMAXIELEMENTS    // moves
qTabN4:         .skip 8 * NBMAXIELEMENTS    // ????
qTabN2:         .skip 8 * NBMAXIELEMENTS    // table game address
sBuffer:        .skip TAILLEBUFFER
/*********************************/
/*  code section                 */
/*********************************/
.text
.global main 
main:                            // INFO: main
    mov x0,sp                    // stack address for load parameter
    bl traitFic                  // read file and store value in array
    cmp x0,#-1
    beq 100f                     // error ?

    ldr x0,qAdrtbBox
    bl displayGame               // display array game
    
    ldr x0,qAdrtbBox              // control if solution exists
    bl controlSolution
    cmp x0,#TRUE
    beq 1f
    ldr x0,qAdrszMessImpossible  // no solution !!!
    bl affichageMess
    b 100f

1:
 
    ldr x0,qAdrtbBox
    ldr x9,qAdrqTabN2
    str x0,[x9]                  // N2 address global
    mov x10,#0                   // variable _n global
    mov x12,#0                   // variable n global 
    bl searchSolution
    cmp x0,#TRUE
    bne 100f                     // no solution ?
    ldr x3,qAdrqTabN2
    ldr x0,[x3,x12,lsl #3]       // visual solution control
    bl displayGame
    mov x0,x12                   // move counter
    ldr x1,qAdrsZoneConv
    bl conversion10              // conversion counter
    ldr x0,qAdrszMessCounterSolution
    bl strInsertAtCharInc
    ldr x1,qAdrsZoneConv
    bl affichageMess
    ldr x5,qAdrqTabN3
    ldr x3,qAdrsBuffer
    mov x2,#1
    mov x4,#0
2:                                // loop solution display 
    ldr x1,[x5,x2,lsl 3]
    cmp x2,#TAILLEBUFFER
    bge 99f
    strb w1,[x3,x4]
    add x4,x4,#1
    add x2,x2,#1
    cmp x2,x12
    ble 2b
    mov x1,#0
    strb w1,[x3,x4]                 // zéro final
    mov x0,x3
    bl affichageMess
    ldr x0,qAdrszCarriageReturn
    bl affichageMess
    
    b 100f

99:
    ldr x0,qAdrszMessErrBuffer
    bl affichageMess
100:                                 // standard end of the program 
    mov x0, #0                       // return code
    mov x8, #EXIT                    // request to exit program
    svc #0                           // perform the system call
 
qAdrtbBox:                  .quad tbBox
qAdrqTabN0:                .quad qTabN0
qAdrqTabN2:                .quad qTabN2
qAdrqTabN3:                .quad qTabN3
qAdrqTabN4:                .quad qTabN4
qAdrszMessCounterSolution: .quad szMessCounterSolution
qAdrszMessImpossible:      .quad szMessImpossible
qAdrszMessErrBuffer:       .quad szMessErrBuffer
qAdrsZoneConv:             .quad sZoneConv
/******************************************************************/
/*      search    Solution                                        */ 
/******************************************************************/
searchSolution:                  // INFO: searchSolution
    stp x1,lr,[sp,-16]!          // save  registres
    stp x2,x3,[sp,-16]!          // save  registres
    stp x4,x5,[sp,-16]!          // save  registres
    stp x6,x7,[sp,-16]!          // save  registres
    stp x8,x9,[sp,-16]!          // save  registres
                                 // address allocation place on the heap
    mov x0,#0                    // allocation place heap
    mov x8,BRK                   // call system 'brk'
    svc #0
    cmp x0,#-1                   // allocation error
    beq 99f
    ldr x1,qAdrqAdrHeap
    str x0,[x1]                  // store heap address
    bl functionFN
    ldr x3,qAdrqTabN2
    ldr x0,[x3,x12,lsl #3]       // last current game
    bl gameOK                    // it is Ok ?
    cmp x0,#TRUE
    beq 100f                     // yes --> end

    ldr x1,qAdrqAdrHeap          // free up resources
    ldr x0,[x1]                  // restaur start address heap
    mov x8,BRK                   // call system 'brk'
    svc #0
    cmp x0,#-1                   // allocation error
    beq 99f
    add x10,x10,#1               // _n
    mov x12,#0                   // n
    bl searchSolution            // next recursif call
    b 100f
99:
    ldr x0,qAdrszMessErreur
    bl affichageMess
100:
    ldp x8,x9,[sp],16            // restaur des  2 registres
    ldp x6,x7,[sp],16            // restaur des  2 registres
    ldp x4,x5,[sp],16            // restaur des  2 registres
    ldp x2,x3,[sp],16            // restaur des  2 registres
    ldp x1,lr,[sp],16            // restaur des  2 registres
    ret
qAdrszMessErreur:           .quad szMessErreur
qAdrqAdrHeap:               .quad qAdrHeap
/******************************************************************/
/*     Fonction FN                                                */ 
/******************************************************************/
functionFN:                      // INFO: functionFN
    stp x1,lr,[sp,-16]!          // save  registres
    ldr x4,qAdrqTabN3
    ldr x3,[x4,x12,lsl #3]
    ldr x5,qAdrqTabN0            // load position empty box
    ldr x6,[x5,x12,lsl #3]
    cmp x6,#15                   // last box
    bne 2f
    cmp x3,#'R'
    bne 11f
    mov x0,#CONST_G
    bl functionFZ
    b 100f
11:
    cmp x3,#'D'
    bne 12f
    mov x0,#CONST_L
    bl functionFZ
    b 100f
12:
    mov x0,#CONST_G + CONST_L
    bl functionFZ
    b 100f
    
2:
    cmp x6,#12
    bne 3f
    cmp x3,#'L'
    bne 21f
    mov x0,#CONST_G
    bl functionFZ
    b 100f
21:
    cmp x3,#'D'
    bne 22f
    mov x0,#CONST_E
    bl functionFZ
    b 100f
22:
    mov x0,#CONST_E + CONST_G
    bl functionFZ
    b 100f
3:
    cmp x6,#13
    beq 30f
    cmp x6,#14
    bne 4f
30:
    cmp x3,#'L'
    bne 31f
    mov x0,#CONST_G + CONST_L
    bl functionFZ
    b 100f
31:
    cmp x3,#'R'
    bne 32f
    mov x0,#CONST_G + CONST_E
    bl functionFZ
    b 100f
32:
    cmp x3,#'D'
    bne 33f
    mov x0,#CONST_E + CONST_L
    bl functionFZ
    b 100f
33:
    mov x0,#CONST_L + CONST_E + CONST_G
    bl functionFZ
    b 100f
4:
    cmp x6,#3
    bne 5f
    cmp x3,#'R'
    bne 41f
    mov x0,#CONST_I
    bl functionFZ
    b 100f
41:
    cmp x3,#'U'
    bne 42f
    mov x0,#CONST_L
    bl functionFZ
    b 100f
42:
    mov x0,#CONST_I + CONST_L
    bl functionFZ
    b 100f
5:
    cmp x6,#0
    bne 6f
    cmp x3,#'L'
    bne 51f
    mov x0,#CONST_I
    bl functionFZ
    b 100f
51:
    cmp x3,#'U'
    bne 52f
    mov x0,#CONST_E
    bl functionFZ
    b 100f
52:
    mov x0,#CONST_I + CONST_E
    bl functionFZ
    b 100f
6:
    cmp x6,#1
    beq 60f
    cmp x6,#2
    bne 7f
60:
    cmp x3,#'L'
    bne 61f
    mov x0,#CONST_I + CONST_L
    bl functionFZ
    b 100f
61:
    cmp x3,#'R'
    bne 62f
    mov x0,#CONST_E + CONST_I
    bl functionFZ
    b 100f
62:
    cmp x3,#'U'
    bne 63f
    mov x0,#CONST_E + CONST_L
    bl functionFZ
    b 100f
63:
    mov x0,#CONST_I + CONST_E + CONST_L
    bl functionFZ
    b 100f
7:
    cmp x6,#7
    beq 70f
    cmp x6,#11
    bne 8f
70:
    cmp x3,#'R'
    bne 71f
    mov x0,#CONST_I + CONST_G
    bl functionFZ
    b 100f
71:
    cmp x3,#'U'
    bne 72f
    mov x0,#CONST_G + CONST_L
    bl functionFZ
    b 100f
72:
    cmp x3,#'D'
    bne 73f
    mov x0,#CONST_I + CONST_L
    bl functionFZ
    b 100f
73:
    mov x0,#CONST_I + CONST_G + CONST_L
    bl functionFZ
    b 100f
8:
    cmp x6,#4
    beq 80f
    cmp x6,#8
    bne 9f
80:
    cmp x3,#'D'
    bne 81f
    mov x0,#CONST_I + CONST_E
    bl functionFZ
    b 100f
81:
    cmp x3,#'U'
    bne 82f
    mov x0,#CONST_G + CONST_E
    bl functionFZ
    b 100f
82:
    cmp x3,#'L'
    bne 83f
    mov x0,#CONST_I + CONST_G
    bl functionFZ
    b 100f
83:
    mov x0,#CONST_G + CONST_E + CONST_I
    bl functionFZ
    b 100f
9:
    cmp x3,#'D'
    bne 91f
    mov x0,#CONST_I + CONST_E + CONST_L
    bl functionFZ
    b 100f
91:
    cmp x3,#'L'
    bne 92f
    mov x0,#CONST_I + CONST_G + CONST_L
    bl functionFZ
    b 100f
92:
    cmp x3,#'R'
    bne 93f
    mov x0,#CONST_I + CONST_G + CONST_E
    bl functionFZ
    b 100f
93:
    cmp x3,#'U'
    bne 94f
    mov x0,#CONST_G + CONST_E +  CONST_L
    bl functionFZ
    b 100f
94:
    mov x0,#CONST_G + CONST_L + CONST_I + CONST_E
    bl functionFZ
    b 100f

99:                             // error
    ldr x0,qAdrszMessErreur
    bl affichageMess
100:
    ldp x1,lr,[sp],16           // restaur des  2 registres
    ret

/******************************************************************/
/*     function FZ                           */ 
/*                                                 */
/***************************************************************/
/* x0 contains variable w           */
functionFZ:                    // INFO: functionFZ
    stp x1,lr,[sp,-16]!        // save  registres
    stp x2,x3,[sp,-16]!        // save  registres
    mov x2,x0 
    and x1,x2,#CONST_I
    cmp x1,#0
    ble 1f
    bl functionFI
    bl functionFY
    cmp x0,#TRUE
    beq 100f
    sub x12,x12,#1              // variable n
1:
    ands x1,x2,#CONST_G
    ble 2f
    bl functionFG
    bl functionFY
    cmp x0,#TRUE
    beq 100f
    sub x12,x12,#1              // variable n
2:
    ands x1,x2,#CONST_E
    ble 3f
    bl functionFE
    bl functionFY
    cmp x0,#TRUE
    beq 100f
    sub x12,x12,#1              // variable n
3:
    ands x1,x2,#CONST_L
    ble 4f
    bl functionFL
    bl functionFY
    cmp x0,#TRUE
    beq 100f
    sub x12,x12,#1              // variable n
4:
    mov x0,#FALSE
100:
    ldp x2,x3,[sp],16           // restaur des  2 registres
    ldp x1,lr,[sp],16           // restaur des  2 registres
    ret
/******************************************************************/
/*               function FY                                 */ 
/******************************************************************/
functionFY:                    // INFO: functionFY
    stp x1,lr,[sp,-16]!        // save  registres
    ldr x1,qAdrqTabN2
    ldr x0,[x1,x12,lsl #3]
    bl gameOK                  // game OK ?
    cmp x0,#TRUE
    beq 100f
    ldr x1,qAdrqTabN4
    ldr x0,[x1,x12,lsl #3]
    cmp x0,x10
    bgt 1f
    bl functionFN
    b 100f
1:
    mov x0,#FALSE
100:
    ldp x1,lr,[sp],16           // restaur des  2 registres
    ret
/******************************************************************/
/*     the empty box is down                                     */ 
/******************************************************************/
functionFI:                       // INFO: functionFI
    stp x1,lr,[sp,-16]!          // save  registres
    stp x2,x3,[sp,-16]!          // save  registres
    stp x4,x5,[sp,-16]!          // save  registres
    stp x6,x7,[sp,-16]!          // save  registres
    stp x8,x9,[sp,-16]!          // save  registres
    ldr x0,qAdrqTabN0
    ldr x1,[x0,x12,lsl #3]        // empty box
    add x2,x1,#4
    ldr x3,[x9,x12,lsl #3]        // load game current
    ldrb w4,[x3,x2]               // load box down empty box
    add x5,x12,#1                 // n+1
    add x8,x1,#4                  // new position empty case
    str x8,[x0,x5,lsl #3]         // store new position empty case
    ldr x6,qAdrqTabN3
    
    mov x7,#'D'                   // down
    str x7,[x6,x5,lsl #3]         // store move
    ldr x6,qAdrqTabN4
    ldr x7,[x6,x12,lsl #3]
    str x7,[x6,x5,lsl #3]         // N4 (n+1) = n4(n)
    mov x0,x3
    bl createGame                 // create copy game
    ldrb w3,[x0,x1]               // and inversion box
    ldrb w8,[x0,x2]
    strb w8,[x0,x1]
    strb w3,[x0,x2]
    str x0,[x9,x5,lsl #3]         // store new game in table
    lsr x1,x1,#2                  // line position empty case = N°/ 4
    ldr x0,qAdrqTabNr
    ldr x2,[x0,x4,lsl #3]         // load N° line box moved
    cmp x2,x1                     // compare ????
    ble 1f
    add x7,x7,#1                  // and increment ????
    str x7,[x6,x5,lsl #3]
1:
    add x12,x12,#1                // increment N
    ldp x8,x9,[sp],16           // restaur des  2 registres
    ldp x6,x7,[sp],16           // restaur des  2 registres
    ldp x4,x5,[sp],16           // restaur des  2 registres
    ldp x2,x3,[sp],16           // restaur des  2 registres
    ldp x1,lr,[sp],16           // restaur des  2 registres
    ret
qAdrqTabNr:        .quad qTabNr
qAdrqTabNc:        .quad qTabNc
/******************************************************************/
/*     empty case UP   see explain in english in function FI      */ 
/******************************************************************/
functionFG:                      // INFO: functionFG
    stp x1,lr,[sp,-16]!          // save  registres
    stp x2,x3,[sp,-16]!          // save  registres
    stp x4,x5,[sp,-16]!          // save  registres
    stp x6,x7,[sp,-16]!          // save  registres
    stp x8,x9,[sp,-16]!          // save  registres
    ldr x0,qAdrqTabN0
    ldr x1,[x0,x12,lsl #3]       // case vide
    sub x2,x1,#4                 // position case au dessus
    ldr x3,[x9,x12,lsl #3]       // extrait jeu courant
    ldrb w4,[x3,x2]              // extrait le contenu case au dessus
    add x5,x12,#1                // N+1 = N
    sub x8,x1,#4                 // nouvelle position case vide
    str x8,[x0,x5,lsl #3]        // et on la stocke
    ldr x6,qAdrqTabN3
    mov x7,#'U'                  // puis on stocke le code mouvement
    str x7,[x6,x5,lsl #3]
    ldr x6,qAdrqTabN4
    ldr x7,[x6,x12,lsl #3]
    str x7,[x6,x5,lsl #3]        // N4 (N+1) = N4 (N)
    mov x0,x3                    // jeu courant
    bl createGame                // création nouveau jeu
    ldrb w3,[x0,x1]              // et echange les 2 cases
    ldrb w8,[x0,x2]
    strb w8,[x0,x1]
    strb w3,[x0,x2]
    str x0,[x9,x5,lsl #3]        // stocke la nouvelle situation 
    lsr x1,x1,#2                 // ligne case vide = position /4
    ldr x0,qAdrqTabNr
    ldr x2,[x0,x4,lsl #3]        // extrait table à la position case
    cmp x2,x1                    // et comparaison ???
    bge 1f
    add x7,x7,#1                 // puis increment N4 de 1  ???
    str x7,[x6,x5,lsl #3]
1:
    add x12,x12,#1               // increment de N
    ldp x8,x9,[sp],16           // restaur des  2 registres
    ldp x6,x7,[sp],16           // restaur des  2 registres
    ldp x4,x5,[sp],16           // restaur des  2 registres
    ldp x2,x3,[sp],16           // restaur des  2 registres
    ldp x1,lr,[sp],16           // restaur des  2 registres
    ret
/******************************************************************/
/*    empty case go right see explain finction FI ou FG en français */ 
/******************************************************************/
functionFE:                       // INFO: functionFE
    stp x1,lr,[sp,-16]!          // save  registres
    stp x2,x3,[sp,-16]!          // save  registres
    stp x4,x5,[sp,-16]!          // save  registres
    stp x6,x7,[sp,-16]!          // save  registres
    stp x8,x9,[sp,-16]!          // save  registres
    ldr x0,qAdrqTabN0
    ldr x1,[x0,x12,lsl #3]
    add x2,x1,#1
    ldr x3,[x9,x12,lsl #3]
    ldrb w4,[x3,x2]               // extrait le contenu case 
    add x5,x12,#1
    add x8,x1,#1
    str x8,[x0,x5,lsl #3]         // nouvelle case vide
    ldr x6,qAdrqTabN3
    mov x7,#'R'
    str x7,[x6,x5,lsl #3]         // mouvement
    ldr x6,qAdrqTabN4
    ldr x7,[x6,x12,lsl #3]
    str x7,[x6,x5,lsl #3]         // N4 ??
    mov x0,x3
    bl createGame
    ldrb w3,[x0,x1]               // exchange two boxes
    ldrb w8,[x0,x2]
    strb w8,[x0,x1]
    strb w3,[x0,x2]
    str x0,[x9,x5,lsl #3]         // stocke la nouvelle situation 
    lsr x3,x1,#2
    sub x1,x1,x3,lsl #2
    ldr x0,qAdrqTabNc
    ldr x2,[x0,x4,lsl #3]         // extrait table à la position case
    cmp x2,x1
    ble 1f
    add x7,x7,#1
    str x7,[x6,x5,lsl #3]
1:
    add x12,x12,#1
    ldp x8,x9,[sp],16           // restaur des  2 registres
    ldp x6,x7,[sp],16           // restaur des  2 registres
    ldp x4,x5,[sp],16           // restaur des  2 registres
    ldp x2,x3,[sp],16           // restaur des  2 registres
    ldp x1,lr,[sp],16           // restaur des  2 registres
    ret
/******************************************************************/
/*     empty box go left see explain function FI ou FG en français */ 
/******************************************************************/
functionFL:                       // INFO: functionFL
    stp x1,lr,[sp,-16]!          // save  registres
    stp x2,x3,[sp,-16]!          // save  registres
    stp x4,x5,[sp,-16]!          // save  registres
    stp x6,x7,[sp,-16]!          // save  registres
    stp x8,x9,[sp,-16]!          // save  registres
    ldr x0,qAdrqTabN0
    ldr x1,[x0,x12,lsl #3]        // case vide
    sub x2,x1,#1
    ldr x3,[x9,x12,lsl #3]       // extrait jeu courant
    ldrb w4,[x3,x2]              // extrait le contenu case 
    add x5,x12,#1
    sub x8,x1,#1
    str x8,[x0,x5,lsl #3]         // nouvelle case vide
    ldr x6,qAdrqTabN3
    mov x7,#'L'
    str x7,[x6,x5,lsl #3]         // mouvement
    ldr x6,qAdrqTabN4
    ldr x7,[x6,x12,lsl #3]
    str x7,[x6,x5,lsl #3]         // N4 ??
    mov x0,x3
    bl createGame
    ldrb w3,[x0,x1]               // exchange two boxes
    ldrb w8,[x0,x2]
    strb w8,[x0,x1]
    strb w3,[x0,x2]
    str x0,[x9,x5,lsl #3]         // stocke la nouvelle situation 
    lsr x3,x1,#2
    sub x1,x1,x3,lsl #2           // compute remainder
    ldr x0,qAdrqTabNc
    ldr x2,[x0,x4,lsl #3]         // extrait table colonne à la position case
    cmp x2,x1
    bge 1f
    add x7,x7,#1
    str x7,[x6,x5,lsl #3]
1:
    add x12,x12,#1
    ldp x8,x9,[sp],16           // restaur des  2 registres
    ldp x6,x7,[sp],16           // restaur des  2 registres
    ldp x4,x5,[sp],16           // restaur des  2 registres
    ldp x2,x3,[sp],16           // restaur des  2 registres
    ldp x1,lr,[sp],16           // restaur des  2 registres
    ret
/******************************************************************/
/*     create new Game                                            */ 
/******************************************************************/
/* x0 contains box address            */
/* x0 return address new game  */
createGame:                          // INFO: createGame
    stp x1,lr,[sp,-16]!          // save  registres
    stp x2,x3,[sp,-16]!          // save  registres
    stp x4,x5,[sp,-16]!          // save  registres
    stp x6,x7,[sp,-16]!          // save  registres
    stp x8,x9,[sp,-16]!          // save  registres
    mov x4,x0                    // save value
    mov x0,#0                    // allocation place heap
    mov x8,BRK                   // call system 'brk'
    svc #0
    cmp x0,#-1                   // allocation error
    beq 99f
    mov x5,x0                    // save address heap for output string
    add x0,x0,#SIZE * SIZE       // reservation place one element
    mov x8,BRK                   // call system 'brk'
    svc #0
    cmp x0,#-1                   // allocation error
    beq 99f
    mov x2,#0
1:                               // loop copy boxes
    ldrb w3,[x4,x2]
    strb w3,[x5,x2]
    add x2,x2,#1
    cmp x2,#NBBOX
    blt 1b
    add x11,x11,#1
    mov x0,x5
    b 100f
99:                              // error
    ldr x0,qAdrszMessErreur
    bl affichageMess
100:
    ldp x8,x9,[sp],16           // restaur des  2 registres
    ldp x6,x7,[sp],16           // restaur des  2 registres
    ldp x4,x5,[sp],16           // restaur des  2 registres
    ldp x2,x3,[sp],16           // restaur des  2 registres
    ldp x1,lr,[sp],16           // restaur des  2 registres
    ret
/******************************************************************/
/*     read file                                                   */ 
/******************************************************************/
/* x0 contains address stack begin           */
traitFic:                             // INFO: traitFic
    stp x1,lr,[sp,-16]!          // save  registres
    stp x2,x3,[sp,-16]!          // save  registres
    stp x4,x5,[sp,-16]!          // save  registres
    stp x6,x7,[sp,-16]!          // save  registres
    stp x8,fp,[sp,-16]!          // save  registres
    mov fp,x0                         //  fp <- start address
    ldr x4,[fp]                       // number of Command line arguments
    cmp x4,#1
    ble 99f
    add x5,fp,#16                      // second parameter address 
    ldr x5,[x5]
    ldr x0,qAdrqAdrFicName
    str x5,[x0]
    ldr x0,qAdrszMessTitre
    bl affichageMess                  // display string
    mov x0,x5
    bl affichageMess 
    ldr x0,qAdrszCarriageReturn
    bl affichageMess                  // display carriage return

    mov x0,AT_FDCWD
    mov x1,x5                         // file name
    mov x2,#O_RDWR                    // flags    
    mov x3,#0                         // mode 
    mov x8, #OPEN                     // call system OPEN 
    svc 0 
    cmp x0,#0                         // error ?
    ble 99f
    mov x7,x0                         // File Descriptor
    ldr x1,qAdrsBuffer                // buffer address
    mov x2,#TAILLEBUFFER              // buffer size
    mov x8,#READ                      // read file
    svc #0
    cmp x0,#0                         // error ?
    blt 99f
    // extraction datas
    ldr x1,qAdrsBuffer                // buffer address
    add x1,x1,x0
    mov x0,#0                         // store zéro final
    strb w0,[x1] 
    ldr x0,qAdrtbBox                   // game box address
    ldr x1,qAdrsBuffer                // buffer address
    bl extracDatas
                                      // close file
    mov x0,x7
    mov x8, #CLOSE 
    svc 0 
    mov x0,#0
    b 100f
99:                                   // error
    ldr x0,qAdrszMessErreur           // error message
    bl   affichageMess
    mov x0,#-1
100:
    ldp x8,fp,[sp],16           // restaur des  2 registres
    ldp x6,x7,[sp],16           // restaur des  2 registres
    ldp x4,x5,[sp],16           // restaur des  2 registres
    ldp x2,x3,[sp],16           // restaur des  2 registres
    ldp x1,lr,[sp],16           // restaur des  2 registres
    ret
qAdrqAdrFicName:              .quad qAdrFicName
qAdrszMessTitre:              .quad szMessTitre
qAdrsBuffer:                  .quad sBuffer
/******************************************************************/
/*     extrac digit file buffer                                   */ 
/******************************************************************/
/* x0 contains boxs address           */
/* x1 contains buffer address         */
extracDatas:                     // INFO: extracDatas
    stp x1,lr,[sp,-16]!          // save  registres
    stp x2,x3,[sp,-16]!          // save  registres
    stp x4,x5,[sp,-16]!          // save  registres
    stp x6,x7,[sp,-16]!          // save  registres
    stp x8,x9,[sp,-16]!          // save  registres
    mov x7,x0
    mov x6,x1
    mov x2,#0                    // string buffer indice
    mov x4,x1                    // start digit ascii
    mov x5,#0                    // box index
1:
    ldrb w3,[x6,x2]
    cmp x3,#0                    // datas end ?
    beq 4f
    cmp x3,#0xA                  // line end ?
    beq 2f
    cmp x3,#','                  // box end ?
    beq 3f
    add x2,x2,#1
    b 1b
2:
    mov x3,#0
    strb w3,[x6,x2]              // zero final
    add x3,x2,#1                 // next character
    ldrb w3,[x6,x3] 
    cmp x3,#0xD                  // line return 
    bne 21f
    add x2,x2,#2                 // yes
    b 4f
21:
    add x2,x2,#1
    b 4f
3:
    mov x3,#0                    // zero final
    strb w3,[x6,x2]
    add x2,x2,#1
4:  
    mov x0,x4                    // conversion character ascii in integer
    bl conversionAtoD
    strb w0,[x7,x5]              // and store value on 1 byte box
    cmp x0,#0                    // empty box ?
    bne 5f
    ldr x0,qAdrqTabN0
    str x5,[x0]                  // empty box in item zéro
5:
    add x5,x5,#1                 // increment counter boxes
    cmp x5,#NBBOX                // number box = maxi ?
    bge 100f 
    add x4,x6,x2                 // new start address digit ascii
    b 1b
100:
    ldp x8,x9,[sp],16           // restaur des  2 registres
    ldp x6,x7,[sp],16           // restaur des  2 registres
    ldp x4,x5,[sp],16           // restaur des  2 registres
    ldp x2,x3,[sp],16           // restaur des  2 registres
    ldp x1,lr,[sp],16           // restaur des  2 registres
    ret
/******************************************************************/
/*     control of the game solution                                      */ 
/******************************************************************/
/* x0 contains boxs address           */
/* x0 returns 0 if not possible       */
/* x0 returns 1 if possible           */
controlSolution:                 // INFO: controlSolution
    stp x1,lr,[sp,-16]!          // save  registres
    stp x2,x3,[sp,-16]!          // save  registres
    stp x4,x5,[sp,-16]!          // save  registres
    stp x6,x7,[sp,-16]!          // save  registres
    stp x8,x9,[sp,-16]!          // save  registres
    mov x5,x0
    ldr x8,qAdrqTabN0
    ldr x8,[x8]                  // empty box
    //mov x7,#0
    cmp x8,#1
    cset x7,eq
    beq 1f
    cmp x8,#3
    cset x7,eq
    beq 1f
    cmp x8,#4
    cset x7,eq
    beq 1f
    cmp x8,#6
    cset x7,eq
    beq 1f
    cmp x8,#9
    cset x7,eq
    beq 1f
    cmp x8,#11
    cset x7,eq
    beq 1f
    cmp x8,#12
    cset x7,eq
    beq 1f
    cmp x8,#14
    cset x7,eq
1:
    mov x9,NBBOX - 1
    sub x6,x9,x8
    add x7,x7,x6
                                 // count permutations
    mov x1,#-1
    mov x6,#0
2:
    add x1,x1,#1
    cmp x1,#NBBOX
    bge 80f
    cmp x1,x8
    beq 2b
    ldrb w3,[x5,x1]
    mov x2,x1
3:
    add x2,x2,#1
    cmp x2,#NBBOX
    bge 2b
    cmp x2,x8
    beq 3b
    ldrb w4,[x5,x2]
    cmp x4,x3
    cinc x6,x6,lt
    b 3b
80:
    add x6,x6,x7
    tst x6,#1
    cset x0,eq
100:
    ldp x8,x9,[sp],16           // restaur des  2 registres
    ldp x6,x7,[sp],16           // restaur des  2 registres
    ldp x4,x5,[sp],16           // restaur des  2 registres
    ldp x2,x3,[sp],16           // restaur des  2 registres
    ldp x1,lr,[sp],16           // restaur des  2 registres
    ret    
/******************************************************************/
/*     game Ok ?                                      */ 
/******************************************************************/
/* x0 contains boxs address           */
gameOK:                          // INFO: gameOK
    stp x1,lr,[sp,-16]!          // save  registres
    stp x2,x3,[sp,-16]!          // save  registres
    stp x4,x5,[sp,-16]!          // save  registres
    mov x2,#0
    ldrb w3,[x0,x2]
    cmp x3,#0
    bne 0f
    mov x3,#0xF
0:
    add x2,x2,#1
1:
    ldrb w4,[x0,x2]
    cmp x4,#0
    bne 11f
    mov x3,#0xF
11:
    cmp x4,x3
    ble 99f
    mov x3,x4
    add x2,x2,#1
    cmp x2,#NBBOX -2
    ble 1b
    mov x0,#TRUE                  // game Ok
    b 100f
99:
   mov  x0,#FALSE              // game not Ok
100:
    ldp x4,x5,[sp],16           // restaur des  2 registres
    ldp x2,x3,[sp],16           // restaur des  2 registres
    ldp x1,lr,[sp],16           // restaur des  2 registres
    ret
/******************************************************************/
/*     display game                                       */ 
/******************************************************************/
/* x0 contains boxs address           */
displayGame:                            // INFO: displayGame
    stp x1,lr,[sp,-16]!          // save  registres
    stp x2,x3,[sp,-16]!          // save  registres
    stp x4,x5,[sp,-16]!          // save  registres
    mov x4,x0
    ldr x0,qAdrszMessTitre
    bl affichageMess            // display titre
    ldr x0,qAdrqAdrFicName
    ldr x0,[x0]
    bl affichageMess            // display string
    ldr x0,qAdrszCarriageReturn
    bl affichageMess            // display line return
    mov x2,#0
    ldr x1,qAdrsMessValeur
1:
    ldrb w0,[x4,x2]
    cmp x0,#0
    beq 3f
    bl conversion10             // call conversion decimal
    cmp x0,1
    bne 2f
    mov x0,#0x002020
    str w0,[x1,#1]              // zéro final
    b 4f
2:
    mov x0,#0x20
    str w0,[x1,#2]              // zéro final
    b 4f
3:
    ldr x0,iSpaces              // store spaces to empty case
    str w0,[x1]
4:
    ldr x0,qAdrsMessResult
    bl affichageMess            // display message
    add x0,x2,#1
    tst x0,#0b11
    bne 5f
    ldr x0,qAdrszCarriageReturn
    bl affichageMess            // display message
5:
    add x2,x2,#1
    cmp x2,#NBBOX - 1
    ble 1b
    ldr x0,qAdrszCarriageReturn
    bl affichageMess            // display line return

100:
    ldp x4,x5,[sp],16           // restaur des  2 registres
    ldp x2,x3,[sp],16           // restaur des  2 registres
    ldp x1,lr,[sp],16           // restaur des  2 registres
    ret
iSpaces:                       .quad 0x00202020       // spaces
qAdrszCarriageReturn:          .quad szCarriageReturn
qAdrsMessValeur:               .quad sMessValeur
qAdrsMessResult:               .quad sMessResult
/********************************************************/
/*        File Include fonctions                        */
/********************************************************/
/* for this file see task include a file in language AArch64 assembly */
.include "../includeARM64.inc"
File name : casR1.txt
File name : casR1.txt
 15  14  1   6
 9   11  4   12
     10  7   3
 13  8   5   2

File name : casR1.txt
 1   2   3   4
 5   6   7   8
 9   10  11  12
 13  14  15

Solution in 52 moves :
RRRULDDLUUULDRURDDDRULLULURRRDDLDLUURDDLULURRULDRDRD

Ada

Translation of: C++
Decoding actually...
with Ada.Text_IO;

procedure Puzzle_15 is

   type Direction is (Up, Down, Left, Right);
   type Row_Type is range 0 .. 3;
   type Col_Type is range 0 .. 3;
   type Tile_Type is range 0 .. 15;

   To_Col : constant array (Tile_Type) of Col_Type :=
     (3, 0, 1, 2, 3, 0, 1, 2, 3, 0, 1, 2, 3, 0, 1, 2);
   To_Row : constant array (Tile_Type) of Row_Type :=
     (3, 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3);

   type Board_Type is array (Row_Type, Col_Type) of Tile_Type;

   Solved_Board : constant Board_Type :=
     ((1,   2,  3,  4),
      (5,   6,  7,  8),
      (9,  10, 11, 12),
      (13, 14, 15, 0));

   type Try_Type is
      record
         Board : Board_Type;
         Move  : Direction;
         Cost  : Integer;
         Row   : Row_Type;
         Col   : Col_Type;
      end record;

   Stack : array (0 .. 100) of Try_Type;
   Top             : Natural := 0;
   Iteration_Count : Natural := 0;

   procedure Move_Down is
      Board   : Board_Type         := Stack (Top).Board;
      Row     : constant Row_Type  := Stack (Top).Row;
      Col     : constant Col_Type  := Stack (Top).Col;
      Tile    : constant Tile_Type := Board (Row + 1, Col);
      Penalty : constant Integer   :=
        (if To_Row (Tile) <= Row then 0 else 1);
   begin
      Board (Row,     Col) := Tile;
      Board (Row + 1, Col) := 0;
      Stack (Top + 1) := (Board => Board,
                          Move  => Down,
                          Row   => Row + 1,
                          Col   => Col,
                          Cost  => Stack (Top).Cost + Penalty);
   end Move_Down;

   procedure Move_Up is
      Board   : Board_Type         := Stack (Top).Board;
      Row     : constant Row_Type  := Stack (Top).Row;
      Col     : constant Col_Type  := Stack (Top).Col;
      Tile    : constant Tile_Type := Board (Row - 1, Col);
      Penalty : constant Integer   :=
        (if To_Row (Tile) >= Row then 0 else 1);
   begin
      Board (Row,     Col) := Tile;
      Board (Row - 1, Col) := 0;
      Stack (Top + 1) := (Board => Board,
                          Move  => Up,
                          Row   => Row - 1,
                          Col   => Col,
                          Cost  => Stack (Top).Cost + Penalty);
   end Move_Up;

   procedure Move_Left is
      Board   : Board_Type         := Stack (Top).Board;
      Row     : constant Row_Type  := Stack (Top).Row;
      Col     : constant Col_Type  := Stack (Top).Col;
      Tile    : constant Tile_Type := Board (Row, Col - 1);
      Penalty : constant Integer :=
        (if To_Col (Tile) >= Col then 0 else 1);
   begin
      Board (Row, Col)     := Tile;
      Board (Row, Col - 1) := 0;
      Stack (Top + 1) := (Board => Board,
                          Move  => Left,
                          Row   => Row,
                          Col   => Col - 1,
                          Cost  => Stack (Top).Cost + Penalty);
   end Move_Left;

   procedure Move_Right is
      Board   : Board_Type         := Stack (Top).Board;
      Row     : constant Row_Type  := Stack (Top).Row;
      Col     : constant Col_Type  := Stack (Top).Col;
      Tile    : constant Tile_Type := Board (Row, Col + 1);
      Penalty : constant Integer :=
        (if To_Col (Tile) <= Col then 0 else 1);
   begin
      Board (Row, Col)     := Tile;
      Board (Row, Col + 1) := 0;
      Stack (Top + 1) := (Board => Board,
                          Move  => Right,
                          Row   => Row,
                          Col   => Col + 1,
                          Cost  => Stack (Top).Cost + Penalty);
   end Move_Right;

   function Is_Solution return Boolean;

   function Test_Moves return Boolean is
   begin
      if
        Stack (Top).Move /= Down and then
        Stack (Top).Row  /= Row_Type'First
      then
         Move_Up;
         Top := Top + 1;
         if Is_Solution then return True; end if;
         Top := Top - 1;
      end if;

      if
        Stack (Top).Move /= Up and then
        Stack (Top).Row  /= Row_Type'Last
      then
         Move_Down;
         Top := Top + 1;
         if Is_Solution then return True; end if;
         Top := Top - 1;
      end if;

      if
        Stack (Top).Move /= Right and then
        Stack (Top).Col  /= Col_Type'First
      then
         Move_Left;
         Top := Top + 1;
         if Is_Solution then return True; end if;
         Top := Top - 1;
      end if;

      if
        Stack (Top).Move /= Left and then
        Stack (Top).Col  /= Col_Type'Last
      then
         Move_Right;
         Top := Top + 1;
         if Is_Solution then return True; end if;
         Top := Top - 1;
      end if;

      return False;
   end Test_Moves;

   function Is_Solution return Boolean is
      use Ada.Text_IO;
   begin
      if Stack (Top).Board = Solved_Board then
         Put ("Solved in " & Top'Image & " moves: ");
         for R in 1 .. Top loop
            Put (String'(Stack (R).Move'Image) (1));
         end loop;
         New_Line;
         return True;
      end if;
      if Stack (Top).Cost <= Iteration_Count then
         return Test_Moves;
      end if;
      return False;
   end Is_Solution;

   procedure Solve (Row   : in Row_Type;
                    Col   : in Col_Type;
                    Board : in Board_Type) is
   begin
      pragma Assert (Board (Row, Col) = 0);
      Top := 0;
      Iteration_Count := 0;
      Stack (Top) := (Board => Board,
                      Row   => Row,
                      Col   => Col,
                      Move  => Down,
                      Cost  => 0);
      while not Is_Solution loop
         Iteration_Count := Iteration_Count + 1;
      end loop;
   end Solve;

begin
   Solve (Row   => 2,
          Col   => 0,
          Board => ((15, 14, 1, 6),
                    (9, 11, 4, 12),
                    (0, 10, 7, 3),
                    (13, 8, 5, 2)));
end Puzzle_15;
Output:
Solved in  52 moves: RRRULDDLUUULDRURDDDRULLULURRRDDLDLUURDDLULURRULDRDRD

ARM Assembly

Works with: as version Raspberry Pi
/* ARM assembly Raspberry PI  */
/*  program puzzle15solver.s   */
/* my first other program find à solution in 134 moves !!! */
/* this second program is a adaptation algorithme C++ and go rosetta code */
/* thanck for the creators */ 
/* 1 byte by box on game board */

/* create a file with nano  */
/*  15,  2,   3,   4
    5,   6,   7,   1
    9,   10,  8,   11
   13,  14,  12, 0     */
   
/*   Run this programm : puzzle15solver <file name> */
/*   wait several minutes for résult */

/* REMARK 1 : this program use routines in a include file 
   see task Include a file language arm assembly 
   for the routine affichageMess conversion10 
   see at end of this program the instruction include */
/* for constantes see task include a file in arm assembly */
/************************************/
/* Constantes                       */
/************************************/
.include "../constantes.inc"

.equ STDIN,  0     @ Linux input console
.equ STDOUT, 1     @ Linux output console
.equ EXIT,   1     @ Linux syscall
.equ READ,   3     @ Linux syscall
.equ WRITE,  4     @ Linux syscall
.equ OPEN,   5     @ Linux syscall
.equ CLOSE,  6     @ Linux syscall

.equ TRUE, 1
.equ FALSE, 0

.equ O_RDWR,    0x0002        @ open for reading and writing

.equ SIZE,           4
.equ NBBOX,          SIZE * SIZE
.equ TAILLEBUFFER,   100
.equ NBMAXIELEMENTS, 100

.equ CONST_I,    1
.equ CONST_G,    8
.equ CONST_E,    2
.equ CONST_L,    4

/*********************************/
/* Initialized data              */
/*********************************/
.data
szMessTitre:            .asciz "Nom du fichier : "
sMessResult:           .ascii " "
sMessValeur:           .fill 11, 1, ' '             @ size => 11
szCarriageReturn:      .asciz "\n"
szMessCounterSolution: .asciz "Solution in @ moves : \n"

//szMessMoveError:       .asciz "Huh... Impossible move !!!!\n"
szMessErreur:          .asciz "Error detected.\n"
szMessImpossible:      .asciz "!!! Impossible solution !!!\n"
szMessErrBuffer:       .asciz "buffer size too less !!"
szMessSpaces:          .asciz "    "

iTabNr:  .int 3, 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3
iTabNc:  .int 3, 0, 1, 2, 3, 0, 1, 2, 3, 0, 1, 2, 3, 0, 1, 2
/*********************************/
/* UnInitialized data            */
/*********************************/
.bss
.align 4
sZoneConv:      .skip 24
iAdrHeap:       .skip 4
ibox:           .skip SIZE * SIZE           @ game boxes
iAdrFicName:    .skip 4
iTabN0:         .skip 4 * NBMAXIELEMENTS    @ empty box
iTabN3:         .skip 4 * NBMAXIELEMENTS    @ moves
iTabN4:         .skip 4 * NBMAXIELEMENTS    @ ????
iTabN2:         .skip 4 * NBMAXIELEMENTS    @ table game address
sBuffer:        .skip TAILLEBUFFER
/*********************************/
/*  code section                 */
/*********************************/
.text
.global main 
main:                            @ INFO: main
    mov r0,sp                    @ stack address for load parameter
    bl traitFic                  @ read file and store value in array
    cmp r0,#-1
    beq 100f                     @ error ?

    ldr r0,iAdribox
    bl displayGame               @ display array game
    
    ldr r0,iAdribox              @ control if solution exists
    bl controlSolution
    cmp r0,#TRUE
    beq 1f
    ldr r0,iAdrszMessImpossible  @ no solution !!!
    bl affichageMess
    b 100f

1:
    ldr r0,iAdribox
    ldr r9,iAdriTabN2
    str r0,[r9]                  @ N2 address global
    
    mov r10,#0                   @ variable _n global
    mov r12,#0                   @ variable n global 
    bl searchSolution
    cmp r0,#TRUE
    bne 100f                     @ no solution ?
    ldr r3,iAdriTabN2
    ldr r0,[r3,r12,lsl #2]       @ visual solution control
    bl displayGame
    mov r0,r12                   @ move counter
    ldr r1,iAdrsZoneConv
    bl conversion10              @ conversion counter
    mov r2,#0
    strb r2,[r1,r0]              @ and display
    ldr r0,iAdrszMessCounterSolution
    bl strInsertAtCharInc
    ldr r1,iAdrsZoneConv
    bl affichageMess
    ldr r5,iAdriTabN3
    ldr r3,iAdrsBuffer
    mov r2,#1
    mov r4,#0
2:                                @ loop solution display 
    ldrb r1,[r5,r2,lsl #2]
    cmp r2,#TAILLEBUFFER
    bge 99f
    strb r1,[r3,r4]
    add r4,r4,#1
    add r2,r2,#1
    cmp r2,r12
    ble 2b
    mov r1,#0
    str r1,[r3,r4]                 @ zéro final
    mov r0,r3
    bl affichageMess
    ldr r0,iAdrszCarriageReturn
    bl affichageMess
    
    b 100f

99:
    ldr r0,iAdrszMessErrBuffer
    bl affichageMess
100:                                 @ standard end of the program 
    mov r0, #0                       @ return code
    mov r7, #EXIT                    @ request to exit program
    svc #0                           @ perform the system call
 
iAdribox:                  .int ibox
iAdriTabN0:                .int iTabN0
iAdriTabN2:                .int iTabN2
iAdriTabN3:                .int iTabN3
iAdriTabN4:                .int iTabN4
iAdrszMessCounterSolution: .int szMessCounterSolution
iAdrszMessImpossible:      .int szMessImpossible
iAdrszMessErrBuffer:       .int szMessErrBuffer
iAdrsZoneConv:             .int sZoneConv
/******************************************************************/
/*      search    Solution                                        */ 
/******************************************************************/
searchSolution:                      @ INFO: searchSolution
    push {r1-r8,lr}                  @ save  registers
                                     @ address allocation place on the heap
    mov r0,#0                        @ allocation place heap
    mov r7,#0x2D                     @ call system 'brk'
    svc #0
    cmp r0,#-1                       @ allocation error
    beq 99f
    ldr r1,iAdriAdrHeap
    str r0,[r1]                      @ store heap address
    bl functionFN
    ldr r3,iAdriTabN2
    ldr r0,[r3,r12,lsl #2]           @ last current game
    bl gameOK                        @ it is Ok ?
    cmp r0,#TRUE
    beq 100f                         @ yes --> end

    ldr r1,iAdriAdrHeap              @ free up resources
    ldr r0,[r1]                      @ restaur start address heap
    mov r7,#0x2D                     @ call system 'brk'
    svc #0
    cmp r0,#-1                       @ allocation error
    beq 99f
    add r10,r10,#1                   @ _n
    mov r12,#0                       @ n
    bl searchSolution                @ next recursif call
    b 100f
99:
    ldr r0,iAdrszMessErreur
    bl affichageMess
100:
    pop {r1-r8,lr}                   @ restaur registers 
    bx lr                            @return
iAdrszMessErreur:           .int szMessErreur
iAdriAdrHeap:               .int iAdrHeap
/******************************************************************/
/*     Fonction FN                                                */ 
/******************************************************************/
functionFN:                          @ INFO: functionFN
    push {lr}                        @ save  register
    ldr r4,iAdriTabN3
    ldr r3,[r4,r12,lsl #2]
    ldr r5,iAdriTabN0                @ load position empty box
    ldr r6,[r5,r12,lsl #2]
    cmp r6,#15                       @ last box
    bne 2f
    cmp r3,#'R'
    bne 11f
    mov r0,#CONST_G
    bl functionFZ
    b 100f
11:
    cmp r3,#'D'
    bne 12f
    mov r0,#CONST_L
    bl functionFZ
    b 100f
12:
    mov r0,#CONST_G + CONST_L
    bl functionFZ
    b 100f
    
2:
    cmp r6,#12
    bne 3f
    cmp r3,#'L'
    bne 21f
    mov r0,#CONST_G
    bl functionFZ
    b 100f
21:
    cmp r3,#'D'
    bne 22f
    mov r0,#CONST_E
    bl functionFZ
    b 100f
22:
    mov r0,#CONST_E + CONST_G
    bl functionFZ
    b 100f
3:
    cmp r6,#13
    beq 30f
    cmp r6,#14
    bne 4f
30:
    cmp r3,#'L'
    bne 31f
    mov r0,#CONST_G + CONST_L
    bl functionFZ
    b 100f
31:
    cmp r3,#'R'
    bne 32f
    mov r0,#CONST_G + CONST_E
    bl functionFZ
    b 100f
32:
    cmp r3,#'D'
    bne 33f
    mov r0,#CONST_E + CONST_L
    bl functionFZ
    b 100f
33:
    mov r0,#CONST_L + CONST_E + CONST_G
    bl functionFZ
    b 100f
4:
    cmp r6,#3
    bne 5f
    cmp r3,#'R'
    bne 41f
    mov r0,#CONST_I
    bl functionFZ
    b 100f
41:
    cmp r3,#'U'
    bne 42f
    mov r0,#CONST_L
    bl functionFZ
    b 100f
42:
    mov r0,#CONST_I + CONST_L
    bl functionFZ
    b 100f
5:
    cmp r6,#0
    bne 6f
    cmp r3,#'L'
    bne 51f
    mov r0,#CONST_I
    bl functionFZ
    b 100f
51:
    cmp r3,#'U'
    bne 52f
    mov r0,#CONST_E
    bl functionFZ
    b 100f
52:
    mov r0,#CONST_I + CONST_E
    bl functionFZ
    b 100f
6:
    cmp r6,#1
    beq 60f
    cmp r6,#2
    bne 7f
60:
    cmp r3,#'L'
    bne 61f
    mov r0,#CONST_I + CONST_L
    bl functionFZ
    b 100f
61:
    cmp r3,#'R'
    bne 62f
    mov r0,#CONST_E + CONST_I
    bl functionFZ
    b 100f
62:
    cmp r3,#'U'
    bne 63f
    mov r0,#CONST_E + CONST_L
    bl functionFZ
    b 100f
63:
    mov r0,#CONST_I + CONST_E + CONST_L
    bl functionFZ
    b 100f
7:
    cmp r6,#7
    beq 70f
    cmp r6,#11
    bne 8f
70:
    cmp r3,#'R'
    bne 71f
    mov r0,#CONST_I + CONST_G
    bl functionFZ
    b 100f
71:
    cmp r3,#'U'
    bne 72f
    mov r0,#CONST_G + CONST_L
    bl functionFZ
    b 100f
72:
    cmp r3,#'D'
    bne 73f
    mov r0,#CONST_I + CONST_L
    bl functionFZ
    b 100f
73:
    mov r0,#CONST_I + CONST_G + CONST_L
    bl functionFZ
    b 100f
8:
    cmp r6,#4
    beq 80f
    cmp r6,#8
    bne 9f
80:
    cmp r3,#'D'
    bne 81f
    mov r0,#CONST_I + CONST_E
    bl functionFZ
    b 100f
81:
    cmp r3,#'U'
    bne 82f
    mov r0,#CONST_G + CONST_E
    bl functionFZ
    b 100f
82:
    cmp r3,#'L'
    bne 83f
    mov r0,#CONST_I + CONST_G
    bl functionFZ
    b 100f
83:
    mov r0,#CONST_G + CONST_E + CONST_I
    bl functionFZ
    b 100f
9:
    cmp r3,#'D'
    bne 91f
    mov r0,#CONST_I + CONST_E + CONST_L
    bl functionFZ
    b 100f
91:
    cmp r3,#'L'
    bne 92f
    mov r0,#CONST_I + CONST_G + CONST_L
    bl functionFZ
    b 100f
92:
    cmp r3,#'R'
    bne 93f
    mov r0,#CONST_I + CONST_G + CONST_E
    bl functionFZ
    b 100f
93:
    cmp r3,#'U'
    bne 94f
    mov r0,#CONST_G + CONST_E +  CONST_L
    bl functionFZ
    b 100f
94:
    mov r0,#CONST_G + CONST_L + CONST_I + CONST_E
    bl functionFZ
    b 100f

99:                                  @ error
    ldr r0,iAdrszMessErreur
    bl affichageMess
100:
    pop {lr}                         @ restaur registers 
    bx lr                            @return

/******************************************************************/
/*     function FZ                           */ 
/*                                                 */
/***************************************************************/
/* r0 contains variable w           */
functionFZ:                    @ INFO: functionFZ
    push {r1,r2,lr}            @ save  registers 
    mov r2,r0 
    and r1,r2,#CONST_I
    cmp r1,#0
    ble 1f
    bl functionFI
    bl functionFY
    cmp r0,#TRUE
    beq 100f
    sub r12,r12,#1              @ variable n
1:
    ands r1,r2,#CONST_G
    ble 2f
    bl functionFG
    bl functionFY
    cmp r0,#TRUE
    beq 100f
    sub r12,r12,#1              @ variable n
2:
    ands r1,r2,#CONST_E
    ble 3f
    bl functionFE
    bl functionFY
    cmp r0,#TRUE
    beq 100f
    sub r12,r12,#1              @ variable n
3:
    ands r1,r2,#CONST_L
    ble 4f
    bl functionFL
    bl functionFY
    cmp r0,#TRUE
    beq 100f
    sub r12,r12,#1              @ variable n
4:
    mov r0,#FALSE
100:
    pop {r1,r2,lr}              @ restaur registers 
    bx lr                       @return
/******************************************************************/
/*               function FY                                 */ 
/******************************************************************/
functionFY:                    @ INFO: functionFY
    push {lr}                  @ save  registers
    ldr r1,iAdriTabN2
    ldr r0,[r1,r12,lsl #2]
    bl gameOK                  @ game OK ?
    cmp r0,#TRUE
    beq 100f
    ldr r1,iAdriTabN4
    ldr r0,[r1,r12,lsl #2]
    cmp r0,r10
    bgt 1f
    bl functionFN
    b 100f
1:
    mov r0,#FALSE
100:
    pop {lr}                     @ restaur registers 
    bx lr                        @return

/******************************************************************/
/*     the empty box is down                                     */ 
/******************************************************************/
functionFI:                       @ INFO: functionFI
    push {r0-r8,lr}               @ save  registers
    ldr r0,iAdriTabN0
    ldr r1,[r0,r12,lsl #2]        @ empty box
    add r2,r1,#4
    ldr r3,[r9,r12,lsl #2]        @ load game current
    ldrb r4,[r3,r2]               @ load box down empty box
    add r5,r12,#1                 @ n+1
    add r8,r1,#4                  @ new position empty case
    str r8,[r0,r5,lsl #2]         @ store new position empty case
    ldr r6,iAdriTabN3
    
    mov r7,#'D'                   @ down
    str r7,[r6,r5,lsl #2]         @ store move
    ldr r6,iAdriTabN4
    ldr r7,[r6,r12,lsl #2]
    str r7,[r6,r5,lsl #2]         @ N4 (n+1) = n4(n)
    mov r0,r3
    bl createGame                 @ create copy game
    ldrb r3,[r0,r1]               @ and inversion box
    ldrb r8,[r0,r2]
    strb r8,[r0,r1]
    strb r3,[r0,r2]
    str r0,[r9,r5,lsl #2]         @ store new game in table
    lsr r1,r1,#2                  @ line position empty case = N°/ 4
    ldr r0,iAdriTabNr
    ldr r2,[r0,r4,lsl #2]         @ load N° line box moved
    cmp r2,r1                     @ compare ????
    ble 1f
    add r7,r7,#1                  @ and increment ????
    str r7,[r6,r5,lsl #2]
1:
    add r12,r12,#1                @ increment N
    pop {r0-r8,lr} 
    bx lr                         @return
iAdriTabNr:        .int iTabNr
iAdriTabNc:        .int iTabNc
/******************************************************************/
/*     empty case UP   see explain in english in function FI      */ 
/******************************************************************/
functionFG:                      @ INFO: functionFG
    push {r0-r8,lr}              @ save  registers
    ldr r0,iAdriTabN0
    ldr r1,[r0,r12,lsl #2]       @ case vide
    sub r2,r1,#4                 @ position case au dessus
    ldr r3,[r9,r12,lsl #2]       @ extrait jeu courant
    ldrb r4,[r3,r2]              @ extrait le contenu case au dessus
    add r5,r12,#1                @ N+1 = N
    sub r8,r1,#4                 @ nouvelle position case vide
    str r8,[r0,r5,lsl #2]        @ et on la stocke
    ldr r6,iAdriTabN3
    mov r7,#'U'                  @ puis on stocke le code mouvement
    str r7,[r6,r5,lsl #2]
    ldr r6,iAdriTabN4
    ldr r7,[r6,r12,lsl #2]
    str r7,[r6,r5,lsl #2]        @ N4 (N+1) = N4 (N)
    mov r0,r3                    @ jeu courant
    bl createGame                @ création nouveau jeu
    ldrb r3,[r0,r1]              @ et echange les 2 cases
    ldrb r8,[r0,r2]
    strb r8,[r0,r1]
    strb r3,[r0,r2]
    str r0,[r9,r5,lsl #2]        @ stocke la nouvelle situation 
    lsr r1,r1,#2                 @ ligne case vide = position /4
    ldr r0,iAdriTabNr
    ldr r2,[r0,r4,lsl #2]        @ extrait table à la position case
    cmp r2,r1                    @ et comparaison ???
    bge 1f
    add r7,r7,#1                 @ puis increment N4 de 1  ???
    str r7,[r6,r5,lsl #2]
1:
    add r12,r12,#1               @ increment de N
    pop {r0-r8,lr} 
    bx lr                        @return
/******************************************************************/
/*    empty case go right see explain finction FI ou FG en français */ 
/******************************************************************/
functionFE:                       @ INFO: functionFE
    push {r0-r8,lr}               @ save  registers
    ldr r0,iAdriTabN0
    ldr r1,[r0,r12,lsl #2]
    add r2,r1,#1
    ldr r3,[r9,r12,lsl #2]
    ldrb r4,[r3,r2]               @ extrait le contenu case 
    add r5,r12,#1
    add r8,r1,#1
    str r8,[r0,r5,lsl #2]         @ nouvelle case vide
    ldr r6,iAdriTabN3
    mov r7,#'R'
    str r7,[r6,r5,lsl #2]         @ mouvement
    ldr r6,iAdriTabN4
    ldr r7,[r6,r12,lsl #2]
    str r7,[r6,r5,lsl #2]         @ N4 ??
    mov r0,r3
    bl createGame
    ldrb r3,[r0,r1]               @ exchange two boxes
    ldrb r8,[r0,r2]
    strb r8,[r0,r1]
    strb r3,[r0,r2]
    str r0,[r9,r5,lsl #2]         @ stocke la nouvelle situation 
    lsr r3,r1,#2
    sub r1,r1,r3,lsl #2
    ldr r0,iAdriTabNc
    ldr r2,[r0,r4,lsl #2]         @ extrait table à la position case
    cmp r2,r1
    ble 1f
    add r7,r7,#1
    str r7,[r6,r5,lsl #2]
1:
    add r12,r12,#1
    pop {r0-r8,lr} 
    bx lr                        @return
/******************************************************************/
/*     empty box go left see explain function FI ou FG en français */ 
/******************************************************************/
functionFL:                       @ INFO: functionFL
    push {r0-r8,lr}               @ save  registers
    ldr r0,iAdriTabN0
    ldr r1,[r0,r12,lsl #2]        @ case vide
    sub r2,r1,#1
    ldr r3,[r9,r12,lsl #2]       @ extrait jeu courant
    ldrb r4,[r3,r2]              @ extrait le contenu case 
    add r5,r12,#1
    sub r8,r1,#1
    str r8,[r0,r5,lsl #2]         @ nouvelle case vide
    ldr r6,iAdriTabN3
    mov r7,#'L'
    str r7,[r6,r5,lsl #2]         @ mouvement
    ldr r6,iAdriTabN4
    ldr r7,[r6,r12,lsl #2]
    str r7,[r6,r5,lsl #2]         @ N4 ??
    mov r0,r3
    bl createGame
    ldrb r3,[r0,r1]               @ exchange two boxes
    ldrb r8,[r0,r2]
    strb r8,[r0,r1]
    strb r3,[r0,r2]
    str r0,[r9,r5,lsl #2]         @ stocke la nouvelle situation 
    lsr r3,r1,#2
    sub r1,r1,r3,lsl #2           @ compute remainder
    ldr r0,iAdriTabNc
    ldr r2,[r0,r4,lsl #2]         @ extrait table colonne à la position case
    cmp r2,r1
    bge 1f
    add r7,r7,#1
    str r7,[r6,r5,lsl #2]
1:
    add r12,r12,#1
    pop {r0-r8,lr} 
    bx lr                         @return
/******************************************************************/
/*     create new Game                                            */ 
/******************************************************************/
/* r0 contains box address            */
/* r0 return address new game  */
createGame:                          @ INFO: createGame
    push {r1-r8,lr}                  @ save  registers
    mov r4,r0                        @ save value
    mov r0,#0                        @ allocation place heap
    mov r7,#0x2D                     @ call system 'brk'
    svc #0
    cmp r0,#-1                       @ allocation error
    beq 99f
    mov r5,r0                        @ save address heap for output string
    add r0,#SIZE * SIZE              @ reservation place one element
    mov r7,#0x2D                     @ call system 'brk'
    svc #0
    cmp r0,#-1                       @ allocation error
    beq 99f
    mov r2,#0
1:                                   @ loop copy boxes
    ldrb r3,[r4,r2]
    strb r3,[r5,r2]
    add r2,r2,#1
    cmp r2,#NBBOX
    blt 1b
    add r11,r11,#1
    mov r0,r5
    b 100f
99:                                  @ error
    ldr r0,iAdrszMessErreur
    bl affichageMess
100:
    pop {r1-r8,lr}                   @ restaur registers 
    bx lr                            @return
/******************************************************************/
/*     read file                                                   */ 
/******************************************************************/
/* r0 contains address stack begin           */
traitFic:                             @ INFO: traitFic
    push {r1-r8,fp,lr}                @ save  registers
    mov fp,r0                         @  fp <- start address
    ldr r4,[fp]                       @ number of Command line arguments
    cmp r4,#1
    movle r0,#-1
    ble 99f
    add r5,fp,#8                      @ second parameter address 
    ldr r5,[r5]
    ldr r0,iAdriAdrFicName
    str r5,[r0]
    ldr r0,iAdrszMessTitre
    bl affichageMess                  @ display string
    mov r0,r5
    bl affichageMess 
    ldr r0,iAdrszCarriageReturn
    bl affichageMess                  @ display carriage return

    mov r0,r5                         @ file name
    mov r1,#O_RDWR                    @ flags    
    mov r2,#0                         @ mode 
    mov r7, #OPEN                     @ call system OPEN 
    svc 0 
    cmp r0,#0                         @ error ?
    ble 99f
    mov r8,r0                         @ File Descriptor
    ldr r1,iAdrsBuffer                @ buffer address
    mov r2,#TAILLEBUFFER              @ buffer size
    mov r7,#READ                      @ read file
    svc #0
    cmp r0,#0                         @ error ?
    blt 99f
    @ extraction datas
    ldr r1,iAdrsBuffer                @ buffer address
    add r1,r0
    mov r0,#0                         @ store zéro final
    strb r0,[r1] 
    ldr r0,iAdribox                   @ game box address
    ldr r1,iAdrsBuffer                @ buffer address
    bl extracDatas
                                      @ close file
    mov r0,r8
    mov r7, #CLOSE 
    svc 0 
    mov r0,#0
    b 100f
99:                                   @ error
    ldr r1,iAdrszMessErreur           @ error message
    bl   displayError
    mov r0,#-1
100:
    pop {r1-r8,fp,lr}                 @ restaur registers 
    bx lr                             @return
iAdriAdrFicName:              .int iAdrFicName
iAdrszMessTitre:              .int szMessTitre
iAdrsBuffer:                  .int sBuffer
/******************************************************************/
/*     extrac digit file buffer                                   */ 
/******************************************************************/
/* r0 contains boxs address           */
/* r1 contains buffer address         */
extracDatas:                     @ INFO: extracDatas
    push {r1-r8,lr}              @ save  registers
    mov r7,r0
    mov r6,r1
    mov r2,#0                    @ string buffer indice
    mov r4,r1                    @ start digit ascii
    mov r5,#0                    @ box index
1:
    ldrb r3,[r6,r2]
    cmp r3,#0
    beq 4f                       @ end
    cmp r3,#0xA
    beq 2f
    cmp r3,#','
    beq 3f
    add r2,#1
    b 1b
2:
    mov r3,#0
    strb r3,[r6,r2]
    ldrb r3,[r6,r2]
    cmp r3,#0xD
    addeq r2,#2
    addne r2,#1
    b 4f
 
3:
    mov r3,#0
    strb r3,[r6,r2]
    add r2,#1
4:  
    mov r0,r4
    bl conversionAtoD
    strb r0,[r7,r5]
    cmp r0,#0
    ldreq r0,iAdriTabN0
    streq r5,[r0]                @ empty box in item zéro
    add r5,#1
    cmp r5,#NBBOX                @ number box = maxi ?
    bge 100f 
    add r4,r6,r2                 @ new start address digit ascii
    b 1b
100:
    pop {r1-r8,lr}               @ restaur registers 
    bx lr                        @return
/******************************************************************/
/*     control of the game solution                                      */ 
/******************************************************************/
/* r0 contains boxs address           */
/* r0 returns 0 if not possible       */
/* r0 returns 1 if possible           */
controlSolution:                 @ INFO: controlSolution
    push {r1-r8,lr}              @ save  registers
    mov r5,r0
    ldr r8,iAdriTabN0
    ldr r8,[r8]                  @ empty box
    @ empty box
    mov r7,#0
    cmp r8,#1
    moveq r7,#1
    beq 1f
    cmp r8,#3
    moveq r7,#1
    beq 1f
    cmp r8,#4
    moveq r7,#1
    beq 1f
    cmp r8,#6
    moveq r7,#1
    beq 1f
    cmp r8,#9
    moveq r7,#1
    beq 1f
    cmp r8,#11
    moveq r7,#1
    beq 1f
    cmp r8,#12
    moveq r7,#1
    beq 1f
    cmp r8,#14
    moveq r7,#1
1:
    rsb r6,r8,#NBBOX - 1
    add r7,r6
                                 @ count permutations
    mov r1,#-1
    mov r6,#0
2:
    add r1,#1
    cmp r1,#NBBOX
    bge 80f
    cmp r1,r8
    beq 2b
    ldrb r3,[r5,r1]
    mov r2,r1
3:
    add r2,#1
    cmp r2,#NBBOX
    bge 2b
    cmp r2,r8
    beq 3b
    ldrb r4,[r5,r2]
    cmp r4,r3
    addlt r6,#1
    b 3b
80:
    add r6,r7
    tst r6,#1
    movne r0,#0                  @ impossible
    moveq r0,#1                  @ OK

100:
    pop {r1-r8,lr}               @ restaur registers 
    bx lr                        @return
/******************************************************************/
/*     game Ok ?                                      */ 
/******************************************************************/
/* r0 contains boxs address           */
gameOK:                          @ INFO: gameOK
    push {r1-r4,lr}              @ save  registers
    mov r2,#0
    ldrb r3,[r0,r2]
    cmp r3,#0
    moveq r3,#0xF
    add r2,#1
1:
    ldrb r4,[r0,r2]
    cmp r4,#0
    moveq r3,#0xF
    cmp r4,r3
    movle  r0,#FALSE              @ game not Ok
    ble 100f
    mov r3,r4
    add r2,#1
    cmp r2,#NBBOX -2
    ble 1b
    mov r0,#TRUE                  @ game Ok

100:
    pop {r1-r4,lr}                @ restaur registers 
    bx lr                         @return
/******************************************************************/
/*     display game                                       */ 
/******************************************************************/
/* r0 contains boxs address           */
displayGame:                            @ INFO: displayGame
    push {r0-r5,lr}                     @ save  registers
    mov r4,r0
    ldr r0,iAdrszMessTitre
    bl affichageMess                    @ display string
    ldr r0,iAdriAdrFicName
    ldr r0,[r0]
    bl affichageMess                    @ display string
    ldr r0,iAdrszCarriageReturn
    bl affichageMess                    @ display line return
    mov r2,#0
    ldr r1,iAdrsMessValeur
1:
    ldrb r0,[r4,r2]
    cmp r0,#0
    ldreq r0,iSpaces                    @ store spaces
    streq r0,[r1]
    beq 2f
    bl conversion10                     @ call conversion decimal
    mov r0,#0
    strb r0,[r1,#3]                     @ zéro final
2:

    ldr r0,iAdrsMessResult
    bl affichageMess                    @ display message
    add r0,r2,#1
    tst r0,#0b11
    bne 3f
    ldr r0,iAdrszCarriageReturn
    bl affichageMess                    @ display message
3:
    add r2,#1
    cmp r2,#NBBOX - 1
    ble 1b
    ldr r0,iAdrszCarriageReturn
    bl affichageMess                    @ display line return

100:
    pop {r0-r5,lr}                      @ restaur registers 
    bx lr                               @return
iSpaces:                       .int 0x00202020       @ spaces
//iAdrszMessMoveError:           .int szMessMoveError
iAdrszCarriageReturn:          .int szCarriageReturn
iAdrsMessValeur:               .int sMessValeur
iAdrsMessResult:               .int sMessResult
/***************************************************/
/*      ROUTINES INCLUDE                           */
/***************************************************/
.include "../affichage.inc"
Nom du fichier : casR1.txt
Nom du fichier : casR1.txt
 15  14  1   6
 9   11  4   12
     10  7   3
 13  8   5   2

Nom du fichier : casR1.txt
 1   2   3   4
 5   6   7   8
 9   10  11  12
 13  14  15

Solution in 52 moves :
RRRULDDLUUULDRURDDDRULLULURRRDDLDLUURDDLULURRULDRDRD

C

IDA*

/**@file  HybridIDA.c
 * @brief solve 4x4 sliding puzzle with IDA* algorithm
 * by RMM 2021-feb-22
 
 * The Interative Deepening A* is relatively easy to code in 'C' since
 * it does not need Queues and Lists to manage memory. Instead the
 * search space state is held on the LIFO stack frame of recursive
 * search function calls. Millions of nodes may be created but they
 * are automatically deleted during backtracking.

 * Run-time is a disadvantage with complex puzzles. Also it struggles
 * to solve puzzles with depth g>50. I provided a test puzzle of g=52
 * that works with ordinary search but the Rosetta challenge puzzle
 * cycles forever. The HybridIDA solves it in 18 seconds.
 
 * The HybridIDA solution has two phases.
 * 1. It stops searching when a permutation begins with 1234.
 * 2. Phase2 begins a regular search with the output of phase 1.

 * (But an regular one time search can be done with phase 2
 * only). Phase 1 is optional.)
 
 * Pros: Hybrid IDA* is faster and solves more puzzles.
 * Cons: May not find shortest path.
 */ 
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <string.h>

typedef unsigned char u8t;
typedef unsigned short u16t;
enum { NR=4, NC=4, NCELLS = NR*NC };
enum { UP, DOWN, LEFT, RIGHT, NDIRS };
enum { OK = 1<<8, XX = 1<<9, FOUND = 1<<10, zz=0x80 };
enum { MAX_INT=0x7E, MAX_NODES=(16*65536)*90};
enum { BIT_HDR=1<<0, BIT_GRID=1<<1, BIT_OTHER=1<<2 };
enum { PHASE1,PHASE2 };  // solution phase

typedef struct { u16t dn; u16t hn; }HSORT_T;

typedef struct {
   u8t data[NCELLS]; unsigned id; unsigned src;
   u8t h; u8t g; u8t udlr;
}NODE_T;  // contains puzzle data and metadata

NODE_T goal44={
   {1,2,3,4, 5,6,7,8, 9,10,11,12, 13,14,15,0},0,0,0,0,0};
NODE_T work; // copy of puzzle with run-time changes

NODE_T G34={ //g=34; n=248,055; (1phase)
 {13,9,5,4, 15,6,1,8, 0,10,2,11, 14,3,7,12},0,0,0,0,0};

NODE_T G52={ // g=52; n=34,296,567; (1phase)
   {15,13,9,5, 14,6,1,4,  10,12,0,8, 3,7,11,2},0,0,0,0,0};

NODE_T G99={ // formidable Rosetta challenge (2phases)
   {15,14,1,6, 9,11,4,12, 0,10,7,3, 13,8,5,2},0,0,0,0,0};

struct {
   unsigned nodes;
   unsigned gfound;
   unsigned root_visits;
   unsigned verbose;
   unsigned locks;
   unsigned phase;
}my;

u16t  HybridIDA_star(NODE_T *pNode);
u16t  make_node(NODE_T *pNode, NODE_T *pNew, u8t udlr );
u16t  search(NODE_T *pNode, u16t bound);
u16t  taxi_dist( NODE_T *pNode);
u16t  tile_home( NODE_T *p44);
void  print_node( NODE_T *pN, const char *pMsg, short force );
u16t  goal_found(NODE_T *pNode);
char  udlr_to_char( char udlr );
void  idx_to_rc( u16t idx, u16t *row, u16t *col );
void  sort_nodes(HSORT_T *p);

int main( )
{
   my.verbose = 0;		// minimal print node
   // my.verbose |= BIT_HDR;	// node header
   // my.verbose |= BIT_GRID;	// node 4x4 data
   
   memcpy(&work,  &G99, sizeof(NODE_T));  // select puzzle here
   if(1){   // phase1 can skipped for easy puzzles
      printf("Phase1: IDA* search for 1234 permutation..\n");
      my.phase = PHASE1;
      (void) HybridIDA_star(&work);
   }
   printf("Phase2: IDA* search phase1 seed..\n");
   my.phase = PHASE2;
   (void)HybridIDA_star(&work);
   return 0;
}

/// \brief driver for Iterative Deepining A*
u16t HybridIDA_star(NODE_T *pN){
   my.nodes = 1;
   my.gfound = 0;
   my.root_visits = 0;
   pN->udlr = NDIRS;
   pN->g = 0;
   pN->h = taxi_dist(pN);
   pN->id = my.nodes;
   pN->src = 0;
   const char *pr = {"Start"}; // for g++
   print_node( pN,pr,1 );
   u16t depth = pN->h;
   while(1){
      depth = search(pN,depth);
      if( depth & FOUND){
         return FOUND;  // goodbye
      }
      if( depth & 0xFF00 ){
	 printf("..error %x\n",depth);
	 return XX;
      }
      my.root_visits++;
      printf("[root visits: %u, depth %u]\n",my.root_visits,depth);
   }
   return 0;
}

/// \brief search is recursive. nodes are instance variables
u16t search(NODE_T *pN, u16t bound){ 
   if(bound & 0xff00){ return bound; }
   u16t f = pN->g + pN->h;
   if( f > bound){ return f; }
   if(goal_found(pN)){
      my.gfound = pN->g;
      memcpy(&work,pN,sizeof(NODE_T));
      printf("total nodes=%d, g=%u \n", my.nodes, my.gfound);
      const char *pr = {"Found.."}; // for g++
      print_node( &work,pr,1 );
      return FOUND;
   }
   NODE_T news;
   // Sort successor nodes so that the lowest heuristic is visited
   // before the less promising at the same level. This reduces the
   // number of searches and finds more solutions
   HSORT_T hlist[NDIRS];
   for( short i=0; i<NDIRS; i++ ){
      u16t rv = make_node(pN,&news, i );
      hlist[i].dn = i;
      if( rv & OK ){
	 hlist[i].hn = news.h;
	 continue;
      }
      hlist[i].hn = XX;
   }
   sort_nodes(&hlist[0]);
   
   u16t temp, min = MAX_INT; 
   for( short i=0; i<NDIRS; i++ ){
      if( hlist[i].hn > 0xff ) continue;
      temp = make_node(pN,&news, hlist[i].dn );
      if( temp & XX ) return XX;
      if( temp & OK ){
	 news.id = my.nodes++;
	 print_node(&news," succ",0 );
	 temp = search(&news, bound);
	 if(temp & 0xff00){  return temp;}
	 if(temp < min){ min = temp; }
      }
   }
   return min;
}

/// \brief sort nodes to prioitize heuristic low
void sort_nodes(HSORT_T *p){
   for( short s=0; s<NDIRS-1; s++ ){
      HSORT_T tmp = p[0];
      if( p[1].hn < p[0].hn ){tmp=p[0]; p[0]=p[1]; p[1]=tmp; }
      if( p[2].hn < p[1].hn ){tmp=p[1]; p[1]=p[2]; p[2]=tmp; }
      if( p[3].hn < p[2].hn ){tmp=p[2]; p[2]=p[3]; p[3]=tmp; }
   }
}

/// \brief return index of blank tile
u16t tile_home(NODE_T *pN ){
   for( short i=0; i<NCELLS; i++ ){
      if( pN->data[i] == 0 ) return i;
   }
   return XX;
}

/// \brief print node (or not) depending upon flags
void print_node( NODE_T *pN, const char *pMsg, short force ){
   const int tp1 = 0;
   if( my.verbose & BIT_HDR || force || tp1){
      char ch = udlr_to_char(pN->udlr);
      printf("id:%u src:%u; h=%d, g=%u, udlr=%c, %s\n",
	     pN->id, pN->src, pN->h, pN->g, ch, pMsg);
   }
   if(my.verbose & BIT_GRID || force || tp1){
      for(u16t i=0; i<NR; i++ ){
	 for( u16t j=0; j<NC; j++ ){
	    printf("%3d",pN->data[i*NR+j]);
	 }
	 printf("\n");
      }
      printf("\n");
   }
   //putchar('>');  getchar();
}

/// \brief return true if selected tiles are settled
u16t goal_found(NODE_T *pN) {
   if(my.phase==PHASE1){
      short tags = 0;
      for( short i=0; i<(NC); i++ ){
	 if( pN->data[i] == i+1 ) tags++;
      }
      if( tags==4 ) return 1;  // Permutation starts with 1234
   }
   
   for( short i=0; i<(NR*NC); i++ ){
      if( pN->data[i] != goal44.data[i] ) return 0;
   }
   return 1;
}

/// \brief convert UDLR index to printable char
char udlr_to_char( char udlr ){
   char ch = '?';
   switch(udlr){
   case UP:    ch = 'U'; break;
   case DOWN:  ch = 'D'; break;
   case LEFT:  ch = 'L'; break;
   case RIGHT: ch = 'R'; break;
   default: break;
   }
   return ch;
}

/// \brief convert 1-D array index to 2-D row-column
void idx_to_rc( u16t idx, u16t *row, u16t *col ){
   *row = idx/NR; *col = abs( idx - (*row * NR));
}

/// \brief make successor node with blank tile moved UDRL
/// \return success or error
u16t make_node(NODE_T *pSrc, NODE_T *pNew, u8t udlr ){
   u16t row,col,home_idx,idx2;
   if(udlr>=NDIRS||udlr<0 ){ printf("invalid udlr %u\n",udlr); return XX; }
   if(my.nodes > MAX_NODES ){ printf("excessive nodes %u\n",my.nodes);
      return XX; }
   memcpy(pNew,pSrc,sizeof(NODE_T));
   home_idx = tile_home(pNew);
   idx_to_rc(home_idx, &row, &col );

   if( udlr == LEFT)  { if( col < 1 ) return 0; col--; }
   if( udlr == RIGHT ){ if( col >= (NC-1) ) return 0; col++; }
   if( udlr == DOWN ) { if(row >= (NR-1)) return 0; row++; }
   if( udlr == UP ){	 if(row < 1) return 0; row--; }
   idx2 = row * NR + col;
   if( idx2 < NCELLS ){
      u8t *p = &pNew->data[0];
      p[home_idx] = p[idx2];
      p[idx2]     = 0; // swap
      pNew->src   = pSrc->id;
      pNew->g     = pSrc->g + 1;
      pNew->h     = taxi_dist(pNew);
      pNew->udlr  = udlr; // latest move;
      return OK;
   }
   return 0;
}

/// \brief sum of 'manhattan taxi' distance between tile locations
u16t taxi_dist( NODE_T *pN){
   u16t tile,sum = 0, r1,c1,r2,c2;
   u8t *p44 = &pN->data[0];
   for( short i=0; i<(NR*NC); i++ ){
      tile = p44[i];
      if( tile==0 ) continue;
      idx_to_rc(i, &r2, &c2 );
      idx_to_rc(tile-1, &r1, &c1 );
      sum += abs(r1-r2) + abs(c1-c2);
   }
   }
   return sum;
}
Output:
Phase1: IDA* search for 1234 permutation..
id:1 src:0; h=36, g=0, udlr=?, Start
 15 14  1  6
  9 11  4 12
  0 10  7  3
 13  8  5  2

total nodes=838133, g=32 
id:838132 src:838131; h=12, g=32, udlr=D, Found..
  1  2  3  4
 15  0  7  6
  9 10  5 12
 13 14 11  8

Phase2: IDA* search phase1 seed..
id:1 src:0; h=12, g=0, udlr=?, Start
  1  2  3  4
 15  0  7  6
  9 10  5 12
 13 14 11  8

total nodes=8598744, g=26 
id:8598743 src:8598742; h=0, g=26, udlr=D, Found..
  1  2  3  4
  5  6  7  8
  9 10 11 12
 13 14 15  0

Literate Programming

I think I used the same algorithm as Nigel Galloway, but I'm still not sure I understood his C++ code. For anyone who also had trouble understanding, I thoroughly explained how everything works in this literate program.

The program takes about 12 seconds to solve the puzzle on my machine. I sacrificed efficiency for readability and extensibility.

C#

This example is incorrect. Please fix the code and remove this message.
Details: The task presents a board which a solution must solve in 52 moves, there are no starting positions requiring 164 moves to solve. The required output is specified: "The output must show the moves' directions, like so: left, left, left, down, right... and so on.

There are two solutions, of fifty-two moves: rrrulddluuuldrurdddrullulurrrddldluurddlulurruldrdrd

rrruldluuldrurdddluulurrrdlddruldluurddlulurruldrrdd
Works with: C sharp version 3+
using System;
using System.IO;
using System.IO.Compression;
using System.Runtime.InteropServices;
using System.Text;

public static class Program
{
    public static void Main(string[] args)
    {
        byte[] t = new byte[]    
        {                   
            15, 14, 1, 6,
            9, 11, 4, 12,
            0, 10, 7, 3,
            13, 8, 5, 2,
        };

        Ultimate.SolvePuzzle15(t);
    }
}

public static class NativeLibraryHelper
{
    [DllImport("kernel32.dll")]
    public static extern IntPtr LoadLibrary(string DllToLoad);

    [DllImport("kernel32.dll")]
    public static extern IntPtr GetProcAddress(IntPtr DllHandle, string ProcedureName);

    [DllImport("kernel32.dll")]
    public static extern bool FreeLibrary(IntPtr DllHandle);

    public static T GetMethod<T>(IntPtr DllHandle, string MethodName) where T : class
    {
        T Method = null;

        IntPtr MethodHandle = NativeLibraryHelper.GetProcAddress(DllHandle, MethodName);
        if (MethodHandle != IntPtr.Zero)
        {
            Method = (T)((Object)Marshal.GetDelegateForFunctionPointer(MethodHandle, typeof(T)));
        }

        return Method;
    }
}

public static class Ultimate
{
    [UnmanagedFunctionPointer(CallingConvention.StdCall)]
    [return: MarshalAs(UnmanagedType.LPWStr)]
    private delegate string GetSolverName();

    [UnmanagedFunctionPointer(CallingConvention.StdCall)]
    [return: MarshalAs(UnmanagedType.LPWStr)]
    private delegate string CallSolver([In] [Out] byte[] Array, int Len);

    public static void SolvePuzzle15(byte[] t)
    {
        string LibPath = "Solver.dll";       

        byte[] ZipedData = Convert.FromBase64String(Data.ToString());
        byte[] UnzipedData = DecompressGZipArhive(new MemoryStream(ZipedData));
        File.WriteAllBytes(LibPath, UnzipedData);

        IntPtr DllHandle = NativeLibraryHelper.LoadLibrary(LibPath);

        GetSolverName GetSolverName = NativeLibraryHelper.GetMethod<GetSolverName>(DllHandle, "GetSolverName");
        Console.WriteLine("Hi! My name is {0}.", GetSolverName());
        Console.WriteLine();

        Console.WriteLine("And I try solve puzzle15 with board:");
        for (int i = 0; i < t.Length; i += 4) Console.WriteLine("{0} {1} {2} {3}", t[i], t[i + 1], t[i + 2], t[i + 3]);
        Console.WriteLine();

        CallSolver SolveThis = NativeLibraryHelper.GetMethod<CallSolver>(DllHandle, "SolvePuzzle15");

        DateTime StartTime = DateTime.Now;
        string Path = SolveThis(t, t.Length);

        if (!String.IsNullOrEmpty(Path))
        {
            Path = Path.Trim();
            Console.WriteLine("Ready! Solution path is : {0}", Path);
            Console.WriteLine("Moves {0} and Time: {1}", Path.Length, (DateTime.Now - StartTime).ToString());
            Console.WriteLine();
            Console.WriteLine("Bye bye :)");
        }
        else
        {
            Console.WriteLine("Sorry! I do know a solution :(");
        }

        NativeLibraryHelper.FreeLibrary(DllHandle);
        File.Delete(LibPath);

        Console.ReadKey();
    }

    private static byte[] DecompressGZipArhive(Stream FStream)
    {
        if (FStream == null) return new byte[0];

        FStream.Seek(0, SeekOrigin.Begin);
        MemoryStream OutStream = new MemoryStream();

        using (GZipStream DecompressedStream = new GZipStream(FStream, CompressionMode.Decompress))
        {
            byte[] Buffer = new byte[1024];
            int CountReadingBytes = 0;

            while ((CountReadingBytes = DecompressedStream.Read(Buffer, 0, Buffer.Length)) > 0)
            {
                OutStream.Write(Buffer, 0, CountReadingBytes);
            }
        }
        return OutStream.ToArray();
    }

    // no way make normal long string literal
    private delegate StringBuilder AppendDelegate(string v);
    private static StringBuilder Data = new StringBuilder(100000);
    static Ultimate()
    {
        AppendDelegate a = Data.Append;
        a("H4sICB6/w2MCAFBhc2NhbFNvbHZlci54ODYtNjQuZGxsAOxaZ3QbRRC+kyVHFg7nBAdCaDYYiKg21ab6ggR7cKL3HgJHDcWRqK");
        a("5RDDof4on26I/OAx79QTDVkmwcOTTboTgxxYQm+yimOcYU8c2eZORLaP/xi253dmdnZmdnZmd3EzgxJhQIguDEL5MRhHbB+qsV");
        a("/vmvGb/1t3hxfeHZojfL20X1zfKjzzl3UdnFdRedXTd/YdmC+RdeeFGw7IyzyupCF5ade2GZ77CjyhZedOZZO02f7qnI0jjcLw");
        a("hnXl00he6osNOW6zkKNozf7hC6thGE448RhJINBXzww1cUsnWHJTfBztzgIWcO4PPi3Tn8/AqKKS1POYVzqLzHKRw+RZgC4avd");
        a("8uC4Uzh9tvCf/8653ynMLvjr/p2CZ10eRIk5WwLdSZObilMmCKfvdOb84HzUoRfIDJy5JLNjCl4tyOxUR4jg6xGhE/QXo0ythV");
        a("e708UcD3O05irsgfIdlHa8MxYtQpWvhVDpXLc9EN8Djjya6m76xIBHLD0o7fKda/HdfTrXOQwJZfE68M7KzoOvUZbeHJR2+erO");
        a("uuCiBQLWhq8RcFBuuhbePOH/vyl/ma3PuNE5BT7RBh9pgw+2wfNs8F42eBcbvJ0N3tIGz7HBM22wxwY7bPDEDVPh723wiA1ebY");
        a("NX2eB+G7zcBidt8Is2+Bkb/KgNvs8G326Db7DBbTZ4sQ2+0gbX2eDzbPAZNvhEG3ykDT7YBs+zwXvZ4F1s8HY2eEsbPMcGz7TB");
        a("HhvssMET19vW3waP2ODVNniVDe63wcttcNIGv2iDn7HBj9rg+2zw7Tb4BhvcZoMX2+ArbXCdDT7PBp9hg0+0wUfa4INt8DwbvJ");
        a("cN3sUGb2eDt7TBc2zwTBvsscEOGzwRs62/DR6xwatt8Cob3G+Dl/8DnLTBL/4D/IwNftQG32eDb7fBN9jgNhu82AZfOQVuc213");
        a("h0OIuHZD3tEJmEXPrBhgeqrb1YIMRGS6a9XGTkGfVbqTU1B112Yo0qlMJsOi0/tucwhp9S1s8zRoLkbz8a6Nl4NUjv4xTH+Tuo");
        a("dYdFYZOlg0MJ5+/qrsoLJTcoh5+J9TT5zpp6SZHhpl+us+vZst84/zjEKNulb2ZMnEThOEqvjZ0WMzrf31e8ntGfzJ4bioGoFx");
        a("ZUHKLy0tLQosSCmJTxwv7IPBqrdHWbE6oK9Q9ZWsKIFZ+mqSTW/GGEA9KbdT2pVe7ytRQMtweh7N0oAQBoSIniVA1KnzgQSbVw");
        a("okymh6llVJp+9keVOzzWdI1ntzE0E5mi3TKIjlck7yl9+J7THjnOyFmkMAMMpJlx8sAPCnWWs8NA16TuzgFJjeyYU9i4TVByDv");
        a("0J+yCrn1TD/Ukr9K+X8s/NXcTH+MhbusrrPYve5sF8FgMDRlCDOcFfkNqh5n3j5pyfWoLypiYkpavBCZphIed0vXLkDjWHJacM");
        a("+xpBgsbnPOUDKpiIOFk+JY0hGUNAMt4bRD06nNMZZ0BqdbTU7e4gzoCSUx7ApVW5WC4KwYAwLjvW4l0xMaVcJD0wKt8cZSaYtm");
        a("kgdMwCGTBKfQD51Ky8+UNdcVMVRKIOGuGFjGDN+MIdU4egYMbN6Qqh+ZBilqHKXGcTSOonE8tALEtzDD7aAgSFsswRflbOcMab");
        a("ZvRi1rSRJDsPmKtYwQ8fZawKBfqxrqjDiRSqlS58FxfI5MUVsvtQ1QWy+1DWSoNSsJtQ5RaxptWUGobRQfiPKw0pImHkrLOBVS");
        a("+GwsuPTE6PBKmIsqJknf9fNJ13uQrtdjmeU5dXNVs/CII0/dXNVoc06qO6fqfXOqLkW3m7QN3UDhf2p6jqZpOW1osTxGdn2PZj");
        a("J1u4EK1/dsmlEJVDsbqi3J6tutGqUz0OZW9Zmhd0nbww2YF1d4dj2h2+Z8tVv6Tubpe4Tru5roV5LGqkljldQ2l9rKqG0utZVx");
        a("fWclodbZ1FqCNksQanLjMzP0qF3b52W1/R603WnznwkYep1AyEMZKl4Qs38B6cnEWGJasGosgRXxRUQWTuA3Io4lHMFizadTg4");
        a("NWZizhBAKHnbQqDHEivHp/lkgXhra3gGkACoLTYyrHcjPo4UfW2t/I6WYSWIbQT51sX9L+or3zVgjItaoux/FL4deL3wAGoxjC");
        a("L43fKH7joffNW6QtfBY+dIAB+KToQ7ZKYzgwRJ80fcgwMe5tUssJmUm1aLJ0YUrRU5p0eg8LfzIKGakanAfCJZp0cY+gSRdhsD");
        a("ehSUH83o+vGNqarh5WpM1Z0hY0/iKnm5Bz/Yh2Hlaz/LLPWTiV6ZRejrOoTxxlNclLv+nM5zc/x49zCR5BNHK85LG4GDpak0JT");
        a("OfYNm+U5XXGTq7X4M1IxsNfNvy9NEoyTBF91alLTck265Ds3Pv34mIBs82Jiv7+q39yE9Gubn5wYKSHcviFfUa+sJ/vSvvLerY");
        a("/BGdvi2bf2nFnfeL4FKmsGgptghqo3yenPk5Y6xXl7OR0hxMd4aLQq3ommYgfzpviw8L7n3oQjdNA1mikyHZki6aZ4Pjn0K7y/");
        a("cJQBoYDZMNDvnewXqV+0+YOeepH2YXbDqJRwZDhTe7+IMtsft/dfM0PvzW9j1/QB0aODUBaewfIxpCfSBOTBozbYUpc9vxhd2T");
        a("CuGf7xrenSy9oyp/ZLT5wyjn6rK1/+jZl+LgLW/BJVPxfB7JIyVb9iLtMvqWX6FQzrVOFmulxJ+yTK6h+kpksOx+eK03/wSU3y");
        a("xfTVKaJR5fpYtnLbPdnKvU9lK4/Es5Wne7OVF4b4dG4bRbHyhR/xxdoK4Qkx3C8gRBgbMwNyGZDLgFwG5DIglwG5DMhlzK/8Qb");
        a("qIRLmIi3IRRMGXi0IViGJVIIpVgShWBaJYFYhiVbKiXE+iDK5e+QgJk5lfTWo67thcBsmi++5cQRlK9wseGsceZMbhs5k+61y0");
        a("olqC6oVWtQzVRRw3kTbfzKUqp54GgkfxZEtPhcc3qDsknN7i0s3DaTFYFU4fHqwIp8uD24bT2wS3DKc3Cm5tbo/4IT2fcIUpDu");
        a("md5haAtZfOEswNtZj2kksw16eyQDALUeqvI5dwPb4LuLbGgx5j+rxyp5DpNSmz2h9VTZ9t5XEnTbEfanqYRdWKMn9V/MVKyhh/");
        a("/jmT4S3pb7/NZAxfRS0feHznZP7VvG/F9eQ0c9INv2Uymu7SAKavR113nY8q5ExvINhSNJu9aoarEqia7h83XHugpvt/Hfz0jp");
        a("ULx6UnjvnVMtMcP9naitKnQbJ8mvb+wyZy/fZ88Rd7R+4P8VW67k4eLGnv20K67lIAsvR8XNYRzj4pkYsS/vIeFeufGCpTixCb");
        a("EwwAkgeGwOlNsr4hZImmlN3fJ7fBmtRlPyh6v9K3WgmvHleiTo/pAQ9VL6JgTN0/wvExOkdKB6nVjPDWA94UOnlCHxWo6kdyBD");
        a("mfAAQKNFx+wQ1A8SaU8CfTlL7hgDel9H2iiD1mtU96fmPp+TmKdzCgrwq0DgY3D7T2NxSzZXGKasNdGNcu4jP8Yk4HSQfxVWp6");
        a("LusM6J1K36dK+NPxQLR4vUkILMyAPmBuS/MhP80jv57cTscxs8Si5abJEq33AWcJiK8rNa/XFSl9Q6DqNvdVliVgORlzN9qvIK");
        a("+I35yAdyCgJwMw5VkkcFFWYHNDLq0p5cv5elV80qiPyvnr66qeqG2rqpWWdrOa3saC2sh7VFejtSLTV6VLf8xkaiO7Zj0Tw3Pj");
        a("jztWPlo+hnspJ9Kt6m/7pKXb4LezsqBLWfAmlpsozgKE0icdtho1WIKsv4v8UQfv79Ee2tYfSZ8tI36KLHqYmDXRrenDoqUF6R");
        a("t/gPqbAPEREzRi77wRh6w9Yj5G+CM4Dx2F/kNz/emD0WzWrKOjCh1/L4MEjLMPiDp3iBRYsUE+RT751NP+1EYuXklLN5aWOryd");
        a("odlQgxhYEIf6efqXVsfh9J1aTB+wRZd8/2OtPcFSxBeGrZT/NaUfGIPUjqq43SuxzlTQiBKKawznX61lHxkZxI+d1vrQiF64Vi");
        a("B6oqjqg0BtmNldsCMLd4rkmWsG4KJmgaIjzf+E6W9pPI9QcRxtf/MN64+tWc0SIwVMf5+Fu0V2/vse8m9vL5hVRgoV/WfWmgm9");
        a("yfRRZBkwFtW7iom9qjeO08ImDJMXEzFzpkW3rTBSwDmHvxFZzXd136reQTkyh2SsJE1m5X1K0fsC0SNEHNaZ/inTP+KM1y0Tyc");
        a("Py5GH58gxYYmzGxYDezQ1ycsiRAmVSkB/qvgl4V/gjGxPzw0kQu39gYitg3QGqrKKNyvEdHbm/Qw+l5gyRBPcNBTL3N2XFkFqU");
        a("jJQW5PmLPZ7rgXFNn36y4aAtdGEu0QA5nDrI4hrdcjuVWswXESbH/ylNn6onIQ1cvqEwUBW34rHYWEgnDFhKPyyvSKnpa3Ch05");
        a("zNIW9foCbRUJytYzCnrUYPhrkfnjN3ueUTKlSxJ73F6FrbgLQ0EU7PD4/PWeTm1TLTEUlxD7FQfJFJOuFxsb7Sp8fJImPYtcE2");
        a("jmVFzJXU6KzwtQ7hoLZiz4GRYhEL6QR20yotlmO0zv3q7sxa8nSLJe0zUK5Ff6t10e+c1F+kpGW15ddMlPmBM50Yt1PP+c8DFU");
        a("OIuDkboAiHmKXo42QGd/yKo+HSQlYz0HAQSjIObzJH+9qKMhGUr4HcanQJBxT9uxep08oYXplAD+JNGbekifRCwLEYcSxDwjBp");
        a("h/9Bnpf+hTxrfv8reXa0yfPhz/9GHpgzMxxwgzJjl4BRoiRGymSjmukVFbLe6YOM0pODBOk/+Mv7FenJVQSUI2OYAJDkwFh5hk");
        a("lPrmGJb8rKx2TaXJtV8T1ZX8W/SUX8nsiXx1VxgCXMsvJBJg4QHxBUxV6mi1LHkW5Z6qh1i73heK2my27/kp7gpswQaeDWRDC0");
        a("PkEi4uO5blHQ+FqX2OaUiz9Mf0/qcO3XhuSsY9ZFcNKzfVFniSbXpOpdWtWgeYgqdfQoUkccWQRrr7nu02r8W19ZM4zLk/1Vcb");
        a("mGwCV1JNn58ZkBYKlinGEAspOgS2vpBPeWX6qERpf2UpUQQ08nu41nyWzNz+TZ1NB+tmb9KWvSLDHhUBJpEE7yIPytQ0OE2xnJ");
        a("rnp+fDauVlt1h3Cw4XQwvfBgw+fAEeRAN1VKUCkZvkjkuQocmw475/dhxPSjMMK6TMNq88sYqs2lWgnVKsFBuk4XBIj6rYi8eh");
        a("mzWHm4TkBa6ji6olLs1cBBa6mvQC4c3OtPtE9BX41CoujR2CefFsm+qtNvkt0iRa6GoFTWkqDDz4ILH9mLka5HuGTFkGJ9oJS4");
        a("OcpjQIGeltH+hi1L05fxk1uW2UP6X8u055+UmyyZSqbKVLMumcytWr60GLxNDPyWtjwWiofLhBP9853oOwF9bU7HEpwimPQqtp");
        a("lC6Dpm30jy4z/cqfd3cuC7yjJUvDKXF89VolD1B6t58XgtL55jvHjlcBSarL+t6e9rkrfXp7/ODH8l01vPQQeq1aheTDgtmQGh");
        a("0al1DAia3JIRm5umaXIHkoeNNX9rpqFQ81dlzOnUlYH9UZegr4JuCq7B4cIoRmKCKY1dDcBnnLkYl4a7f01ADfKCw76zSECMN8");
        a("1tZawGrg5RvOPtRdNroPmbQ2gAzVdodM2w5pPUYaYvbP5emtF6OWRra23Ct6JZutkfl55vbQSQSDu3zkwT6FyD8xjHymU9ksDK");
        a("4dn+CH6IL+jH2UdraRiHsbTew++2GkZRvZm2Af+tAgy23qctg+mKAhVziWp9Ra3mw7eaRf23Zm39ZlKZry2AO1quMrpAfxYngi");
        a("VxqXUrp8AHVws5Gnw4w/dMTuqCisp/IHWbC8eDwQKMhKyZaZasXHObQ7V3tOLoNiB1FC+mr2+xQ0yF407ob1lMWurHTRC/HvEm");
        a("EUD4/EJVXIC5WKJmDJ3CV+p4PY/vdwXguwAfxKfJMSf//ZhXaMzOWVnhLJswA7p+Mff/YlhRLylfe2k9wdwW0x7FamKZ4Imv9I");
        a("ZcfN9o3x8fBgtAVtviHxcFBg5sxVmjiGGuDCiH47tpXiycgd++nZn6zbTWeNNs4gPSQwzNKNPaMv8AzimHSR3HjMvtnVnx3nLQ");
        a("GiZY9JgUomf6NOxOEBSIoZl2AmapGvWngEmPJUP+yp70fOxd3Zg5SRRIszUr8S40pKwYBs6tSK6J/jfrccvrgvBoAek0bCvqxg");
        a("pA3HRunPySldPhLGciIwWdU3B52qOWr0RGau7N80lEbYZ3JiYux3lSc3E1pDBW0VcoK0zp+UCXL3Ly9FsxjJX3YRh01QUz3ffT");
        a("TMNGQH2LDgcI+6d0qTUrG104ZYcep2Y1eoJIYnThCNi0AZqlmxNWfibrKfN0LebXl+NVydzfpyfAXhZTct9IHntZf92vd/v0lf");
        a("KKb3yR73BSPKVrXkTd7Faf3ucrT4CIJYidV6PLj3vPR7UYiuC2aO3mgRerQ2e4plloCMcdpDrq6/a5bkOOp+0d6mrYnFQu+jg9");
        a("9ELXpGh/EVRK+Tc56WF5bqkZpH042qRTgccAbIcsCq68bhcrdFneGuL5sebbe2FXw2Z8rf1Sx5v5jN9wc8axtb34bzgjmaSe6n");
        a("Uzf0GkZzArdBtW6Das0G1YoduwQrdhhW6Dh24rvbfyWdhrcQUEjUsd/bK3V0bte2k7/E5wNYQdwvdM2iGuQfGNG0odvejBlfLO");
        a("iSsdwWlYWNrHgU9k3J1554l78L4Ik25AShjCJf9ZA6q+EEYaSjOcM3iamMS031KNhZUI7GdVa7K0tAEhcyxE4Xzc9FA0j4uox0");
        a("meF8jEvAkI+RmBsrcLAf0TCuifQLLPsL/4xaEACaKvTLMfaGt6VRR419ZZjxwFh24WLS5QsENkU+xTvwamX/yC7GAs6MOn0UNo");
        a("3c6C7U13trYjNpJuys/MrbQYuHdLHe+LtCb8mIFLiffntZVuh8MVT0xgUE2rmIHJG5i8gckbmLyByRuYPNf6uvZfrBDtv5fzoq");
        a("EZWotAazFoDT4aukfVGx62NHdMXIUDRd4GOdLcwmpSXy0+AQYLOhwu0K4ZpxyPqffI0p3LaHdcs1KL5YyxB4bYS9HGwNvux24e");
        a("bp7Savzt9dNAw99uuqh4CtPsgvqtf5osxqGDX0WcAVHGSRe0CjzeepOIrwR5E7jD1GpWaZK6SmsdpL1cHIhhTYhXHAeC9FPfQd");
        a("tYFurdNrsqKX7BYxwTx8o44LsrsivTZQJX/ChWuyQTXD+H0+107GgW50FlGq0M5vp1YylWqxDnbfS+Rb0Rp2cKaiVHzU5jBjre");
        a("gwToKNnJ6sA2Z1ZoMen5JAg5C8+OskofRhPSvEixQEj4JIlEzbuNhfi+09RPaoLT8N2GZuX961nxq9lvRvis4DN5kvk5bT9NNE");
        a("8sTDQP2p7jIB63Sx3+p7yc2dw8ZoRHzErymLVYzLQYYuhT4WFRa+2pr9ReymTono4n/cgXpaU+V+/Z0cPzJluanWzNyqaV2Fwt");
        a("mzRgkwZs0oBNGrBJAzZpwCZzBm235zLLng+38snjrXzydCufPMfKJy+28snLeQHylnm3B/Quf+RNnkzCNGHLtZrRkLPte7r9cS");
        a("RXDwuYJjRxD6XW3XDbBjc2bORI5kEI+tUNMrZOQI2zVCBVw/6eIrNPf/wxTV7gvpDdEaqpgzvDLgU8MoO4CBxSsNuiEloPOeoY");
        a("MlNYyJba3qdUN5bCR0LVlgjVSI1k/X3Tje9HlMxiZMN0OkLxY4E/YhZCtAjfwSNwtHsa3KjdQz2I79UUTyi5OiYG6B7Qi5FD+S");
        a("O87VaY4mvAeE9MUnZLk01Cpnsws42wqBFCF1Oo3SqmyE/FJKgks7ES8qQoakvXliJrwXCpY7k3BUOF6Q4g3KoDNMcCTVzFMEjR");
        a("yU3bVX11uuibP910B5tBt5NB88npyaynzklnbRom/HVwg6wxtpMJa1bcRP7X5n8Y55FTzLk5k6ObsUQON1JckvWvTu6i33CfHm");
        a("vqN6uAD7ZPwSUduSEkB4Y41jnkbXwzjTuuS2wFOoDYWe9Y8CXE9om/Q26+HlDUki/RkRsBx7QiAxQegcK3+wtNHBPJEpxJBAtC");
        a("ES1mTVYztwPtfBej0MSnW+rOk71lYYwzwYNXU59ZnX2HmDpvGkk8EdT+YuTbKG+FmNv+lZi3ZsU8/wvuA/Ajsc2PNbdc1LBc1L");
        a("Bc1LBc1LBc1LBcFBHA7u15+/1Q7v9HkRu3nVKpIX31GyWaX3fjMaantm03z5LBkFLb/LszdIACU2Th4QJsDispv9yFGprrPb+7");
        a("67aVUQ0PY3GHeajtCa6HJHHlvOZ95hQFR6gh5KxdWhQDIZe0+F7Kt6x662LUlwxKS66kNj/dWINqj2aoHuTon4g4mJ2OnspBc4");
        a("b1rt4yIgoQbXnbph5CpGyTxECy2eBcMhj8COLuYh5g7qRl38lJIJpKs9Mj8nIfj2dRiYyh4QTlKW5zQ+iVODf1dzvQskzADNpF");
        a("So/s/29rbf3FoL8Udv5e6HAAO/8Qdv60mtUnLn+QqPvaQpX0/1iW3773Io+/qsdf9bX+tqkpdAu98yHnLlz86tBMZc1qVexnfa");
        a("twxbgZPV+dmyR85k3obystE3ysm8bq75o7qHrfnyPZmk9wWMiOlJGN66lz44Qve5PA5jETmziStk5KFXnIvFsgxSBeLxkLzqRk");
        a("K1QMu+12FqL1ZSv9km6OR3gq20kHc35pRcNBya8PcyInciJi1yTmIO2qhFmQxZT1nzlmZRaT+qFRZFdQlwF1GVCXAXXZNPyX9s");
        a("kwPdgolipE9w/9Zo0svZIIufj9dnmcHy3Ojffh6I27umLMMJl9lp+17+UOwbd38eI6XGJQMhDzw1Qk1CeKhAa0vVgEe4TRrzDV");
        a("nN2QnZE57uDTl/PXgjWr8U6gr9AHzo970OZN0rUdft8zcTRSKsr6SlPKNvXj3aLbyTXZ3/SsDC3aLSm335LfYooRmFAM07wVJn");
        a("QPTOhhmNBT2F3brWm/H9BfU/UAaawSx9LPrJjw4gYUGI5cDQp0QZPJhFwdG/MrWISScLVQL/Ga4aZDqr83+4ZHVatdwE7H9ASd");
        a("WGMydIenmz/fRRTYFebb7qEh0pO9uOhXojLu4ZMR36xP1/2E0kfnzed8OrGLG6F4oPXrYGG4m/ZlVBv2D3e7WDjjbijscPNX6g");
        a("XdzDuOGwuq1IxL6hraA0r035Ff+bwTei/FQ/NMJDe8cpLh7xW7zVl8fUCVhREL0YlT46eVKMPjrvo0fwVVvWlQ9elpGmf4Uzh7");
        a("boIiPO5sXI8jULsWA4EUyebz/u6nS8pb0VM1eLY/epkD5Hw1ow1FkAQWXSfGlAXdzfWuT3av28aqra5ZtL6y4D16JGmGPkRzOt");
        a("ohyepKfsiPhX8RQ4dkUStDsrlBdn/w6aNcXhkTQB3dn9YER1CTlqI5HEfj96TgrADu3DwnVYSKWjPUuAHdA3kI8A7J4rKYT/8i");
        a("pgLHgJEgF/jtczwPfgelS9GX8ywi5MSV8zTQPjt6/C+tg/WlZ0eP+D17qW/4ByhhNees1bYjW0BJkd5J5W0yNwllDQgmKftMEB");
        a("Zln0hV1QV/ed+8NrZDWtqX5eDHWJzTSrXYn22VaJPDy0RMtH4Yj6nHiT4ILtf01h8k6+/JeG6j/UDmClqYEvkyh0r+HL8TpwmW");
        a("PdBu29GuT7Oc3Wj314w2vRaDPeE1J7Tln2N2tOQoyZejDG3YG75hBrzUgJca8FIDXmrASw14qQEvXWt/kOl0L3Q7BLlb8DTX4K");
        a("IJT530buix4ouCzQeyzQjULK/fyA9uB+LVb4YcnigKfoZvWfCj4Ae8uQ2tzRPb120uN49sHyzF1xGaqcWwUxEJcxrslyogpdYs");
        a("l64lG5YxDsOaJyqkxRHAzSMVwS2aR8TgdvSpah7xBKubR+YG12seKZNaNb4XQ8wSojN8HEDQBOzgDA7mfW5e30+LWXwad1PwqG");
        a("oZCckdnjg+OC08cXnQEZoDTAdRw1zXnzrf7FgPyde8z4zK4AjpZK33f7j961bIC1R9VjWGZ1j+kkSX7RXpB3Dfreh+cJ2m7I3i");
        a("2rtFLi9o+oXmfabhnlMx/ALDwkrXhdHHcgd+dFZKrRejSVnmB0yXnHwsaKkgb/1Hh6p+tua75vqKMk9oI2kpOLIXcm/n2GaHX+");
        a("NP5UmCh18gPwffgNBWLC3pkZY8R1yGK6XFJ/FK0aUbNg+7645oHnZdWtY8vEndQVTbzqylMWBaLIWHK+VIytwVMky2Ib/akxo3");
        a("12L5bcdRmwf6PArtT5XE8NzGxUZofguP7b69oKFFdJZOUfbgRTr0fZ1LrUkt8kCJwzy3ilEvE99Swn4BayEpe4N6dG9MJacQRf");
        a("8RU68Qgi5lzQdajNdDMtdGcC+GkFa/G8PVZXB9/i3F1xGcY5Yp0qtdit5lbkS2KHXw+npUX0BVDZchQzH+MJ/7bw78PRzWTx7g");
        a("wVNyQFL7AlKgt2rQ+q+tSdE8JPf/sMXatl1qq7uDOwF3QA6b8+XwL3MWOXco47790WRLYW3zN2gDaqgUvHOEGJ7mlZpVTc/j01");
        a("jCDaHlC/oy76hZGPCmVD2OZ/W8fCP3Jq7SSdaYqSK5EhPpk/vwgGm5ed77efSIja33zq9PIkuDooSgnxk+Mie8IAIvB5QIOUAz");
        a("ZmXSBYKml1jgslqHiLGozuYN3dwmeTXvFfkoS7hwjRCEVCKLHp9my8CA2F7lFuo3oTHRQ/7g7DrAm6qi8EvTphMTaauIiqhVW2");
        a("dxEmtHsMX3NEHcuP0cce/E2aVFbXxG87n93Ip7T9xSCi24KM4qqJWqvBpHnBQpjf859+UtUxx+n80b995z7rnnnHvWfRRoHR+n");
        a("0/BiiimgNvdIHstWIeLM/8PCEfS6KCU1F6GpnNKOxRgGbGd9D4xauWaXyFbYPxF7K5U7WlNSawkKYFLaNuiHsQh0kO0as87Hft");
        a("7hvU31Yw4LxUVKe23T7Ocd7nHa1wIDEYZcYjkFoVlPQyR4zFMmSRI0dQqmKAObhXu4VhqhEIoNaC9tRu9xDRrkhlRlGEnuRsry");
        a("4Vm6/Lw/3eIARwBTcZqtFk6w6a/AoYHD9DqZJdBhCPnBqHwXqGrhWthnhai+y1nphiySPw+zpCy8g0uKoq5QOx2nYfCQZDysbv");
        a("r5twAOA3Y5VaB40H8wWQxFqJ1UJknUNUZBgMh47l2m7WTre6/e9wusj9n3Z60qa99fe619T6K+aEE5OMYXRki4ydv4bbjJ/1uU");
        a("omcLIihWXd3knfuOEktRav/AUSqocoHohGspXlLM8B10HzpV6FfgIZ6j9fP0/AD9eWwYXSK5stoA0iM0kUJLcavEFio19dHTZf");
        a("9owHtjz77xsu0wACxV7SCkWshv2TqI4PtkRfWCg+kYy7g/V7tR6Yaen055X9thBGCWkUqLN7k4KI9XIAIAcON30Vi0dFPLZ6hl");
        a("zR7RwyyAvisyetvgXG+Fs3ANsflRYnhRL0RtzqI2UxajwRw0SB5iGfYGDJulx15Gj/PQYwysNzSwDqERBoQ/+2FT9TxtdLwk6S");
        a("r22MAxzmoy53mjlDZjoi5/TRNNscuaXz5H+CcJiOCtMge0T3oY/gl8kyiCTi2wzEMQyUP6grCowerv/P08EgwzOmk0HlI1AMyf");
        a("A+ZD+7hFvgX5BYrsgtOKIRnaIKnFpn7KLBfr+p8sQOWET2T/IYuaizEWarP26pMimnJCr1zTtKi1VB/2KBo2lcN8hdhjfxjGGw");
        a("lYKbN5eUIrXmjl85ZB5vOj+xvmumgolYKJJfO1PNQA6aJZzj1pT9OWLLD2DQzq8gWjdH+XGl3kf897zYmcSns30PGDC/YO4b0Q");
        a("Ay/EwITxfClShzsDzF2+DBhDDk+3wfh8BWBA+H6NeBqq00lPotH/Y9tjwAVxde8V10lCa2Lez2MBoZsa/Wuag/Bczk4bdHPA35");
        a("rvQoBf1sVLvy9cRexxGpqkmCkewVCAk2zrBhydrHlUx6OGUoaKROsB6MimfhlP0+Wh393S0MUQZ30ZC7Cm0X70z2jmxtgoVm2A");
        a("nA/v7JPxANQ20KAXNb5oPi0y+ujzaBxrHqCc6FIQnUB4yCrqKCi+gARtILYqWYAxCP+3+Xc0WqxPogHvRfsccKKhb6a8r4YW+X");
        a("9u3h4e3llWeCHAA6fNUHPnhah9CO3hwaASoW2AhiQxfsfD7hHufcF4xUS1BainmxUd9QY73rvOJ8VWYVVwgB+M7+2Cr0aqeRQK");
        a("XHEtZ7x/MugP6f6YpHuyV+J779xD+gNPuzQf7nkbXbkqneYtatIIb1EQNkgph/ghpSqkVIWUqpBSx3blPH9Yt4FdDZj779MQ/j");
        a("4Ifz+EfwDCr0H4UzA4sblB3BcY5ykxC1D6YyDk1OpXLPNe24MmaP4NuGOAokuWfe/8Lq5HWZgsBL+rO3z9pZtafk+b3UXj0Auv");
        a("bHK8t73989ze/32kQN+ydgQm2MdpjSqR3SXq9nnoF6Hyfhr1+l6iFe2Lx2nJauxJTIG9QAEKwVoZhIvhPsVWt0BYBMf9olsEb4");
        a("4yubGyoIsaHQC5NZBbD+7Y7aX3UA8NLorm9XiQqBIWlwnBjyB+EGUzh7qAnbaqJ53unGTWejrtC4sJBvR7w5RU3wDz1nRabjOP");
        a("dEiv0CG1t30BdKmEGvYY+IwosgGCHyHkVjeV1VIqWSYr6HIARQvv3E+I154vyRwfsO8hgJ+pzW7oNKGX6dDLtMfetMLelGBjVA");
        a("DlOhTtDjfR1505cxoCKRs6q024e2XgTiaojnpwk93AWHnt9SKOUS7zjqq7Y9ppS2ge3ebyVxFI5EKCVs4z9O2aN2x8dMbnOt+p");
        a("G+h8ctpCrsvNbLG3F1tsafsO65QXdG8B0lEN8pKCvAyLCSBBRsbg1U3VsM+rMrPYSoFf6xJT0R58j+cAVysdLVHiR1W4knn8Y8");
        a("7qoBwxqxmELRmWlEW1yNOWYl4LxLy2unG5myUPAMTUtOsXZOb1Ec2ru0j3N4C2CrRVoK0CbdUoJc6iL85Cpp73jJNwYSqOsc9f");
        a("f+A4r2yeU74fI2BqPNotYthhKLlXhtaCa7YGcvTGeFCOB0GIqaGXafdC1JEk6sBhPkTcBwISm5WjTauP36uAs/FiJi3L707GKW");
        a("eR2Xfym3By/F9EC6ekh+aL86kwmfPIDltP7u1l9/9R8Ry+Tl+BCKfb9NTtr9nWoWSZm3XRnIIsOu3s12y8OPAZ67RvonthvQBY");
        a("zNS6e8D9kauohJMCrhU00c2grAiXXRfR1HoRe9EfnD/KD6CVx2Jdp34R0tYIKlJQdEOOodQhDkBCkcfF6h7yTBx+xGOv2ua7Ce");
        a("ZArgdp2tgsbegNwMA1uZmu6FF8pU1gE5eKjX8M4i0AxveZQBXksv+D5s0gBJO9116ZOanRJaMglo6ALB2WEQM8V/jd5SHdq0bs");
        a("H8N5EslZhOYxKUITjwmQejA53Q1aODZNw72p4RFtZR28JujdvzfonYEg/XrcWjuri2VEuxLj8PAYmrq6hnvENGIXDScLqMWJaP");
        a("EP+pLUnnrOMLXu+UkshlocjE93kT+Uy5AuHEYjgeM+Gq3aOJ4A+rgtStH4z4wHqBtRSIDPs7uYtpEi7bI0czp40zwckoW/g7He");
        a("UOyd0JQfUAbswlK35CIHnrBxmX+guRCuyzY4MNo63voGYXtEdmgn2SUY+0r7CZlneGxE2S2Rt6wLxr6jnaUS29F+OOUCmoViJ6");
        a("Fyb7oGDmniCW4PSgRj243Fk058EQZkURDgAUhwRoguWsSBLIhCBm3kzk/hh5/oD9uGbd2rekPx49Iz4mfkpJN7ZuKTC1yBq5uk");
        a("Pd3RCqvUYSqE7udQmqRUcGDzWzpzhvYLXUH/ypZeXIMyznngTKVC5xZmKJwUEbYzIl3NtWg/Ix7MSZtKyz+/eSsFUUqBB5gysG");
        a("dhNB+6rFuP7aJF20pc0zlNZ/0+pFscl5qSttJAruWjHzi+5ZEgwNhl8JIkhmt/tGNW0YKluH6Zptf6nUVJzOF6fppHFn/waVFv");
        a("LOC+oPFPH6kjFbljFFfIvW6h5lEQJ44DxCXmR8BGXgzVhKg6nq/VfsBGhxy/id7SvrbxKn6im7w75JKcCTAqgxGKekx7CNwcVL");
        a("cER0byQipK4xT/b95r6Dg2eNZ77aW4YG1GB+xZZ782Qvw3gCgg0iot48Sl0Ai9xvPm3ehUj35bpSsMDkWX1UGGMxGB9iFyy/sV");
        a("CPgLSagDMuqr/hTWj3b/n9kUhPP7H9+b2+i/zi8vUWIfBi2fNLEZ3lQWcY6Lp9+W/4aL9yoFZ4lt9q3/1xbgfz75XjRR6wCY4g");
        a("RCjaYwAP/J3hcX1YpaUqF95DK3M2GDIF+g3f2Cvp/V8n7mw3fgkjuTXn5QNO9x+Ndni/Y9Yu/YbeADtll+JxOFcCCG2CSH59K6");
        a("pRyLVEwm2Bnq5yCYGkR+S0RPdv3O6YFgvSxxlg8RfcNCUQe09nDrflpe7KF7iEvugvZCcQTjJXtYwd2wUoCjIFfvEMc7oQLpSy");
        a("ta/3e6O7YZ+HXdiW7TPyBzGvKjc0t0mHqWGsoBXEYFXtqZ0D+mX5Cd/yn8oosfRsPmQgNWYEDcZazYj4RERHwAosR4T7r8FRA/");
        a("4WRR8lI8YTWgJfMRmZnxvlviVHp3dv/xmCGTf8f+/k449rHBrE2wgScBCcQSengIFZYFUZGE8zoMl/TSVTlYj7bG2DkaU/aKbB");
        a("/Tce4XAlRjJw551CDqcAPBy+yv/h7v7MulLPZa8lmb/XLUUje1+T0cS6FMzWJfdz9rs9V2RTuqdSwmgy2SB0M16QahMb1oKWAK");
        a("e6dQn0KyWGg47QswiH3LMPIP2gp8f4i9Ve3ld0hX59QD/b/HmzOZsvbadnIJIpvEPpuyWHvhBd3woc1qH+3BNK1tso4YeymWaq");
        a("ZHmE11exvpik2FfbN0MDYhk4/IpZe4zE3maSdiBDMXUW2eZ8xQmmgc2SbRXrv6QEKjCByr1QOLZIncsV0u3U0BMMGXZibC5H8x");
        a("gB8NmeVUZUReOqRtyntGj/AY8bQBTzU6Q6xgGmgK4XtrLjVxniTOzp9vfPvP/KmLUZfBoj1uSbjrGfhCR/h74LL3ZNTB7EHSJ1");
        a("1g47yqJegQPwSBgru/whXV9ws1kfpJ1wsnZ+deJ/+aHr7Mwd52YI/UHMltQRM2txFl6fdke9WDD9eGY8MoXTb5eMunbfx543ts");
        a("h/8EFysU+4qyG5D5dPnaAfjn63IInPpFYPIt65ZVwES3qlCvcQgpkXfXCOmx5zRuecomUwUClxXYKy1B7WkriIKrkUyGfkmXnw");
        a("7MoFOd+si0p01sZunKrAbtMaAlFrDjK2zPCDWozcSuQ5iWCqUHFiKUr6ZNJ+E0r8fKd3lnNwt/D+bE7FMpv1tbMJMYvlIRdnF+");
        a("k9qAhSHjltY8SGoCMqhoW4ywDDbgKbXzqAeCl9iopG/kjsHhpYSh9jzEqIBaXjtiUf8O/MJMDhFlCYsRw7FqvbVTXv1vRfMQdN");
        a("g1oQjONrEm29FAwz8vAtMRp2cIAU16kx0KizthrofwFtm2GpR0T9FHW/2RnBnxwE0mWi2fAVpF95A7dszF7sy6aJYH7pb2Wx06");
        a("+bsvKMVjXUXR1GtZ6XV4codOQ38ar5jH6xYOnFUiA45HZKT5h1vzsNLJ8r+/Q8Es4DIPfKZd+RytDQHcnoefpGPhRu0v413BeJ");
        a("fRMEtXxi4aSQivbxRGnZ1zeDW0K+eN9b2LdnhpHq6/6mYzvmO1O0LZwN/ecku8xRa/bdtizf3g/Wft+8EvU9e5HxiqfqJQ9QfV");
        a("Gqp+mu7KzvI5UtQiK33xW/asdI41K830GGM/+E/56cG5Zn56w9r/l59+c66DzOvWnw1fGfpTOAekPOtM5WnEMFY+atOdsxZn1Z");
        a("2HL8+iO7Pryw1cY+jLO//Ipi9PftSmL/sXZdOX4z+36csPlmXRlw79ZZqTIPo06BKhs4SHa+qbi7B1ZNtOs41n1WM+7O0OLWgO");
        a("uqVt0LHHGy8KCYq5kOAL1s009py1jl3emW+328BEJJ+u9m+jKF2O0P3RUl0vz6aHP4gaJxtPKLG1aAeSIPAJfR89QPavotTzfv");
        a("Gy7QmR7XJE3nkTeHf0wRh3KeyQUIxmqtU/hUGp8sp4F0ICF29IcW1BL9kBramJ7pEZbrkrS2sYS789yQ4Awif6NjWIB8l6wU69");
        a("MBszA9yOAWwNX0HDbPDvw3NBPjMz7LSPdgQ7/uf8T8iS/oGPDKGyJn1aAxQZeuFXIVF2Pm97yCZnqYVuiXw9dEC+hZyrdPmJ/W");
        a("6RjzG1twgQuuiLBHly/FhXAnLBc/yFA7yvPAHKoUpQrvqMaBAvOYb8nzlP67ZXcXpdORvH/riTvj8KCbEGVhxb4zdznVujPf7X");
        a("l9EGqzgO8Qfrwxp9i5T/pM7uIVLrhq9wEK5ssdH398BbqmA5ePxERD7P2wC1bZO9V96CTiAKnngvV43vLuRNtcVD3Ymhs/X9c9");
        a("lqIyTqtjryrQHrA7F7plrzENNKlto8URRTgRDgUeygILsIlDY9DvISO46sNiKlbnnpt7J7l3CvR0xLxErRph9txLbxD/YmIjns");
        a("FUwLJzJ0iexuCaN6eS69iKHqYkPi/spjIpJKkM7iBjVKbASMaVeN2et1UkGQpgDhRIrv51ZQi6GhPDA23dFah6YsC035Q+nt1Z");
        a("1j7+yX89BmhCsOGmmiR1UUTL+6YuPGzsH2wfZoTaB9BCWEdDxyeXhabJNzFRSq0o0S+zJZnvUxfUVm6DEM2Ng5QN9aKac8More");
        a("lg5eNjgKqJ39FBzs7Kbhtf3im4AwScQG374gX3wsJqHEL0qjvtp1Xh0+VqXnsxUsG9piKSMVBVdvMqmh84uG9m/bcfSiPZofvm");
        a("ywQYoUhGJ9nDsP+j+5cAl+A6/T1hLkPJTLe7kLKBGoyC78109/vbPbMLi4ynPpV5d/Iq5yvFcuxNXQIpeEseqJlq/jMsE4hKep");
        a("m0wCYDp+uqxr0COfsEyJfZ55ddQkF97VSzJeBguTeKN0rG3zXtViDAX8Mt+hsI3nFeOVZB3PhIVBba9znOCQRM6XpAu2xMUoqH");
        a("D+RFxI/YB3+WpAp9df4uaq76UMQuHEEJ78DZ98AXP8OvApyYbPutF1/x1dSXLhQ6mEWNonXVAgkErur1z2A72R40ekg/5fL9gh");
        a("GEvKXcki+bJumo3Be/T4skE6X09vlksSBk2OD7m6wYGUy83MEaJxgTcU+yk8PbY9d8R80Sro/+WCwpD/U+9Va8isQB5bjpVVDD");
        a("2Zgz66XBzMcjGxsXNF+4r26DYsF+N0tv9QwQm8YssNy8Cxpgys732xb+mKy1Yw//cx/8+nYTRsg24qvkdBsYX/ZfD/6jH4/2Di");
        a("/4mdn6F7+5/t0aJw7LPwZSvs/L/Uxv+oWf8TXN2FwQEK/M9//fhr8D9d5Yn3aHmLuAL/X0H832nyf4vg/4N5GUsm4mAT8f/irq");
        a("+wyIuV2LuZV8GJLrzjFV4cLBzCG8H/tTb+30nwv208rxivJOt4JiwManud4wQ3Nv+/bOX/x638f7f0d3zyBczx68CnJBs+60bX");
        a("7UB3TP73ElMlC238mw/+pcfgW2bZ49BF4TwcYtsq/qfv+GQtoHPaS+v9+i/jSYaBJAw4z+/pNEcQovRMRATE9srD9iIHYi9CQT");
        a("JUYdNfZJCElY3kUDyvGIFHsnsygag1fW7ky7Hv0Z6+ETqmeciISKtwfLrzN90W2skZh7LUP7fX9tXRpjb7QbZK8j5AgQ3hM04d");
        a("1w8HMd0HXzQJgOXv4w547HJ2joTAY/NJ3PoBs/WjeuuuJKE3B3faVSdyo3az0RW2IdvEkJPFkFViyAPN1ofZhjyAhtwVQ2pP3W");
        a("+4yN9JuF9SZXORTf98g7TTcbS//2N07Pd5n3JMMm/Fu266e0zcnfEe3639ie/eE++8Kb5bJO7WF3f7oqU5KhmXebIaGO7WDx7Y");
        a("ATr5qSUFnhoWPCWi2udlZM4txy9C5qoeuRTtwfvssYKeSuJ9etlaR0Zg+GF2vndGwRFO34Dz47vmwO8qkyIbyuo+BfyRRnU6wh");
        a("0+8kWmbiZMx3N2MAK4gmuAjRrVy0Mc+PYB32Ga3M6AGz88BWQJsXEOxHaoJBmll62FZLD98BARXkVXsx7Dnqsmu+8W0El37paP");
        a("sl9oD8KMXb96K0h4D0j4MFyYp+HCoHY1Oi/In7kIDXChdhOyayENB1KoVLFlgnbkvQbGiwnjS7dhesPDAOXghbiQ8szhs3Qqvj");
        a("uuTpU7LgEZC3FXFlQrk25KrHN1pQ9HNFAfGz/e11T9R6YWMnosvaQr6AJQlnKGXAIzZzuu9UvpBVvfgLPpFF0l9sf8juFxEfzx");
        a("4ZSN+O5NXPbRMEFVCoIkhVRCH3jRNZSA/GaGF3W3RLFHqfK6kGBlaiE7NLgh4hsbpHXQHLvZ15gsBsngU6bjQ4VcPqLDQcCIW6");
        a("szCxhIja9lffGgoUBkSHuS+WLq1fAiMPsmKskRCM8khAG8gCqRCu4h/dtYkYJL1zqJGqC/D57bBKqJQsaVC55W3EexACJCjRw/");
        a("Jg3XsnlnubeHZIbWA/ocepRpbBkhB3l7bREKWpHjbFuGdhkqzIGZwvajgTJUrD4P7+xDXWQxjPia8VH7lj6pddOg/6S+1g21GX");
        a("fbmeGsrUiuDKJdMSn7gK0zCO2pVj4QfQSOJhnFGlbND8anuQjzNzbG+IInqL51W1gQTwGulcxYRIDI8cZvxotMSw9a8qagBhgP");
        a("ATIeMMAF0VtHB+Bgq4gmROdL7hWspjhYzU97BzE52I1ahzDgNB/OxDjQV6r6qIqBOLFtoq6bBHOBnRKgZ7+r9XDCWrCeS4QJmq");
        a("cba0n2mlhPFM0C0MfW9WQgcZmA9UNNpQnMBKwl1pfqfTk0s5PRgRg8lsO3dJpbsLANdqCA1g96QYVeUKEXVOgFFXpBhV5ANe86");
        a("ziMv0urvZHbwvjhu66k5ErhCO3pLY8dxxC/hP0+g7QsWaBH6aM/xJ4ciuUgU5lQbn2G160/xRUbqWgE9TL1auFe0XFtwh8GJy4");
        a("gTv96C66MM1Zk9P1eOYdh/ltKQQKHKwwkjZOGItwJiFzcFjXQE/x6vK6KQpOpBowSDWM8KIpywxuwc+xlWIgaOgm0dLaclohI9");
        a("QEwfeAoxC8t0S0qKzAJsHCIg3qBfrKL+oh4/wxKJFUZCfwKZ6JSoykW8KaWVtr6deVxCUy+g/Mh6WCZ66gygO+f3R+t6bDW504");
        a("mEUf/nxXPMWzyfeU7CmKCDfl7MhvMxOuxsFkXyAP37uOrhM5uQlorvjU8FYu4dF02GzkaIJaTKPsW1FPEn+CckP/MqAxBXOlCO");
        a("U3IX+7DXBNWAL6RKtNdgL+vifA5ezZSij3Tb49FqqQgPTd8qkwwQWXByLCfjLeSf9tcLma8nIWI/U4qM0yOmB2IBxPz3l9X9Gg");
        a("jKdO3d8aJgD4ztV90nI/CAU7o7BV6j1dLPPyxwoeQWH++GIdvoLoEMe/T79MHu3Eb/yrblVDfFMSNrhMbJv9r8XK5n0L6rMPPH");
        a("2eO/Qyea/95HUD2/MoTlqg+oHtT3n5J+M83zwEmD2DBhoahBTy7N77xeUdo1TjxHMNhTktwq4F974QfMi5f6eFvf24fVpC8nC7");
        a("0VvQvnKhFcskdW/zE+PXQqo9hg4LdPBj9Bu4SFbkpNs6ckOgl3Aq9G4Kt/ZFWLz0H4sRL4oergwrfXhefNqDt3IpnlvBoXaMEB");
        a("D7TD+7ryerTgAF9Q3WKomXyVkIpPndH2cIQSjxYhWtskQY/GuqHB5Tct+hv0hWpGEL9I26hHEBYIecRg8V19yVrSDWQJFFFE0f");
        a("tiQxEZof41OI+tgDMxiSIFPJGZhSgz9F7RzDG4P4KxVPZzTiiakNotIoYdcCrdZvvesdowQVBxn/sNc1+8V5UJ2vELGW3vjbZv");
        a("ZJPKq0pukNBFD8MbdBaSl0ex46/wBx27q81KvezxxnlMbzl2cEWlmsOfSdXJvfNQm/BX1dbJIeQj1lPibQUBNZ/aTE3mAxxdoO");
        a("3UkxHZAfXr3mC+oW3zhB5iFLyrrinLj26u3wfVM/JhoEJuCEQlguzBmJbuS/oT2DoXKR2tNB9FnW5SvAeM6b3iEgzLPVhE9S0h");
        a("0Z6p4miwroB9fjC757F5Lj4TLMdurKjEr70E8e17afgbuQVN+x7hz2Des2jeGwbgtETW55nPrqhGIyJEsggE0O9V8YMvGRAZ9m");
        a("cyhC10wAmjfKH1noYFCT6stBNkjkBOFcgZZGkIE11eGpsuRwBOppcMJGiUDHX+ou1a4KKssvgMMDoIOOODQtMaFQ1MDCoNQmpG");
        a("wQaFwtXssWvauo6WbRrMoG2K4mAxflJTmWuvrX202267ZZkJPQVKqLby0cOsLSWrD2czMsO3s/9z7jffnRlG2Mdv/RVzz73nnv");
        a("s6332ce+45G6h32CwcIsM7KFIf+OJAjlOpsjtJbwPn3Y6+ROE46zulEAFRtlOWjT01nfvflscdxZgf5xmHrV0C9owJisGBY9Yo");
        a("gGZUhez/o7OVRAdeSvXDypGAGOAgR/xGIzGnvJ+Q56sWiH3E08QMFlHsZK6HYR7a8SqTbZjqx5LPjFL8sdTUIJKgC+jPDRzl06");
        a("LS2n8txpIbYMRX17t9NXeMgNEmiipnPuc4LE/G7RwNTqOeUEqgPYrvIVDmVGYiPT4sXU8EkUA25e+abRDT7YJOdQJbwWJN9DVE");
        a("bP1ovnZJ2TEWTjbWnuaTM7r3PMcLxsAMTQlpJPP1m8U+TVGY7RM4at8Aal+HUmSDyZFyHPPPC7xfVLPffS6i0I+0s47HztriVP");
        a("Bfb7HVQt0g8+yqktNd/ZBHq8es6HoM6a4eFxV7lxuYu4sMIQ4jGymR1UsOr153dZPyk5lnkq/EXq9XRqzXZcVKrxKshHjPsmyG");
        a("tqYYxHq9cGtGad0vesEKxYR0rjeEdbLe50VEmxH9FvXJJydpyDw5fqzlK7Z0s0a6k3jHY1m9BGXGXtZj17/mjPW/Lnb9aYVL01");
        a("a4tLAWDItMkG34ETsypIs2bPp32lBxxjZEjlfejgiHTFgaI9+bSe1yPosoZlYWV1+APimHNIXVX+BZja4zjjKi/L/wELgTIMk3");
        a("1cfZDSt15uB0clKiXrRe8kys/ThrjXz7IOzZbMMkfh7AZPU0QDLd0i+k+cQ78jnoH19HLA2Gnvf3LZPMhNYyKZl+hLRWhZS3h3");
        a("1/1/0FzqjqF+tkk6LSvUvNGGYhBFuQKl31iP6afJhKxA0zqeZwvSLSIQ0thNBTczT055fjQ46G/ogg2EGWqeMPlPh3SPxKxgcz");
        a("FezKJCW2kYSC3YfTVqjYh4hDvTmYugWJDZ/wfnMAqgr0wgyge5JF5O6BOMfAcljKFYhVbxkgWyP7WzHd/5ZegyMv6TU4jKBTSU");
        a("P3ghrrro1G/sC13jvTDB4Tlkiwdidyz5C5G2TuFzn3dMzwVrA9VJuScTSxqS1w2q3Zn5gwiGSWpvvOjxPdH8kXsn8Ot+r0r5f0");
        a("ZxL9FkYEznaJM1LiDJc4stkx5Ml0h//599EMwekkVEujtp++N2Y6OzwqmJPBirV0RE69BuHI82RXfHsY/rh/A98Whp/aLb6j/m");
        a("QQ/1Jfb4g3xOLvYOrm6JSo9Loe0mf1kL68h/R5XdJjv3feJe9vusqv6e5GXtwQ/jN7dOX0YKqxQXsG/JS4RpGqvDH615LR+IPl");
        a("hlbu3CfqZeW0dHoNQ4n4AcIKiRBqb33s9sTOf2WX/Ju39DAePaQv7yF9dQ/pv+whfSTSD1lukEetqHRTD/l/fLH79He7pEfLFz");
        a("CeI+Ngoz9eShei0velc7pRpjP9abgHcBjFxrQ5Ap5rN5P8RZ6ra8m7Cs7hfJJaY5iQaFmVjDlvTJ9K2xijOwP/Z+P/GfjfOibO");
        a("nTvG5DaPibesVlEOW8pr/5rXgtfTKLxH2zeDrlpJHAipwrawuHIt7nmO42MlBLaoHHnnGQjZ8WUG9yDIQYWp8hedxpC+UByScX");
        a("iaaEZuvs2wPNhr5eWQFC0g/VXSBJlNAaiA9Kc/Q+lPBv25OFDsp3sPyMIpT4D1sRleauaI0aiHgK0MD9bhNIb7ABavaukRU5e9");
        a("eWz/YM66KWjURDRqAizDzR/i9LbYHN6TxmXpMLDpJ/sLdKVFtwm8ibViCQhuB5IV1gJWRG1mo8bTntwcud+LKtHYCNEbaQu5C0");
        a("nX9SEYw0OfkUwAVz/0tBAii2WZxUKwrb0v5HuKo/BTs42qhF0616c07/CK/XKjFlt/cwaYJo6Y5mUDM835Y3Da+hPCYBiE3Vfg");
        a("/wFgnEvBOEnEOA+I85cm6OVtagn8oWgHehufDwC0L5R6K+CRyWgc9DcgpIEWN/NIL41HyiSPkLAGtGaF05qDVY6uDfkMQUzgTk");
        a("U0xQStPLbAST4WqQYq9ZVl8/ibSHRfguaUURMPA0/7Lm4luBUwmjlnjEkLx+Os+bFoA+uXvYA2tUh4wS7AWyTsfBfwnwSMNtwR");
        a("3obk00hTRNp//M2Al4WIn3d3yaBHUSw/Rzn9wsu5BLdwgVGiT65TER7MYU5Xe7chIlHo0x0J6cp1ww+9RYddzDyADvpOdNh0/L");
        a("+Q4F2iw2ZTh70sOuz89p3avJCkHt2ENjeH9dffAW8SMKUv3Qn497H76zO6R7v7v+afrHBatpOSfy7lScKdrTEQ3yUEhov6bthH");
        a("jghFuOQbhDXeMhyRvBXjPC9lHCGPVOKxxPVQvxTixRW9V7KC/FjtKTTe47a8KeRJ/M7Xp+K7fYOnEpzJbqKnozDTsj/KqJ3812");
        a("P5cVT+J8f+u/Ib//fy47n9/2X5t5/6N8r3pTy8Md6QQ07boNhxD8LOua2FSuGak+RhpP3cwpzGm9+a5+t9KzmKMLbiBZUvYU3B");
        a("InTMsr+3mN6Gl7DFCEui9aHzNJlC3Q6pTD5IKiX3JfiawE5jARQpCX7f2942uHXaU932xUbzQ3D3RgsWuasKrnAkwqWJWpjThH");
        a("ogx8lnoXN88braS3qhro2ICFCEb2BtQZsBddhQTz/h5b2DdL281wD8Z+Wt1cpzY/C4vDtD5d0WR+UpqbdQASk2bC592717yTnV");
        a("SCJavY9pthHNxB072nNaLzYt2gwk04/Y7bWYHj9PnHBi7m+UoTVI9pqW469RMS3ZEG/IN+UMjTN4zNUnbkfG8hFKyk2IVUsw/x");
        a("1JnYmgYirbQOT7AO1lGgR/9Qn80PuIJUDK+bTFdAGRE/0DB2p1pmefQbaBG3zbG9WknO2+psa23tVtK2eOeSynybejcZ+F2rDt");
        a("u5wdvtZGtX9Oq9xHxTqfs7093zb1MvpphDMcm7NeN8E5rNGZ+D2dyVPo7PKQ88gJCPRJWAIwYgrAusyPUXqXKMZiBfoVEk4sVU");
        a("hMCraOA2pM/RXF9NlL+pnv9j/rZ77bEHRV5+K4dJ5li9W3i1/FqNkrQneymIymm7hfIi9kpT6LenEtkJWhP3sa9JV8Vte9IKfT");
        a("L/QYOlhw7Z0Qz+cxhoLQc/RVmPEaYQnrMXgLDIPYcXYw9WhanLSZE2v/u810GCjGbaZ/0I+6VErnNP5ARXb9BQ+QMlH+QjMqsJ");
        a("vP/o1tqMQyKwRSVhmFeztrmp9f8yzZETWE8jz9hwa93449pPdbJ4LVS83PY7H2jARWVYOu07ULSVKn611A6uIJfOy+XpI6d71O");
        a("6pz1ktQoYF0sSR19MJzUIUDqSJDyAytF0lr1hE5rxRORtL6p12n9DEmS1kxA6if5glZjvU5r2+91Ws0IhtN6RNJ6AkmS1sOA1D");
        a("rQijhnT2dzDrvxgQUKcD/I7ZflHPyjXk4AQWY7jN2fzoojIcej+KF/GCva4RbcS7Dn1ZUF488mThlO7Ze16a/VpjmQ6awbmgwo");
        a("YANGYIte2pzH9dJmIQhUEJ1CRC01r3A5n2MCO3BlHEKHoKi3Es+46AFsLlBETJWdd5wf6yizCqjCqb2AIe5k1q7VHURSekGBMN");
        a("U1bwKTB+qnqRpqMHU7coXh9tNw84DLr2xNzwIXWRaAy9Xfx3HR6yYjip9Etwk0tQ9+8fZkOlYv9TRmYbSpE32JNi2mCw7THgDa");
        a("ZdIUvkwK2RkH5feQhtsjYUkvAxGvigg25jctldv2Fy2qJaHJIOZG3CPhrWHKfUhQxwAJQ1WNMP3De/PB2lVRBhnbOIM8/Wuq7L");
        a("RpwIx5PvalPHg/aG+X33Us+e07oscHK+hxCqEz0EGT0EHiJq76ri4iXDlfhbYO6K3rUomb+rLZgEMkCHEiwuUb6hhIXT1uNP8U");
        a("fElDrJge3ayz0/eSeYmP+YBgBRUxmhfP4L6CFPHYAAwfPbboMqfp5wVt91si3G0hRE3LUC1spZDiFMRhrC8Pyt1zjPkdi8yyO+");
        a("Sczf5g5YQdiQ/funOoYlM5A3uuO6p5hzuqrudXLxRpugBIQuHJtBf9gvZkUrYZpyPtBse2p8IedDqghKj6QX93S9F+g67uZVat");
        a("Y6H8XvRB+1KOY3d1u1EFlRX0BlAVvMtV8LFIp/i6D0A2mPrrAfThrAbA0iv09od5hIKan8wNqdMU+b1F+42ob3H/OINyzX7n3C");
        a("YnvPv2Wrn8A4Mno/o4rfQrbFxuftHuyrPUYVhHEOyoTGq/DaWo/cjmSXQbEYrtbwSMdB6qhTuMYOrZA4TP+u+W6EpNu4gjLdGd");
        a("Fq0PiqedK3HheQ3N/5v0mS33N+HKxBcBAp/PncTKxJ+hvcA2Sew+SJezcgIgteVSejkBtM+f19G+fCyc6D8AQXURBBVB8AWJ2Y");
        a("A0SXATILXyUka6VyKtiyB3DyCaJydyHXMFyZsktisCew4X3gHMvgLzUok5IQJzvKB7t6C7V9QiUWL3jcA2E/bcFmDOvIwxv3hO");
        a("x9z/aDjm54BA9wpBt1bQfU5iv4h02QUbAakPjGektRLpvgiSiiC5384kxwmSN0rsuREkZxFJO0g2fEmM0hn9DrurfE8dvohl8D");
        a("RqO9WRAOTeJSb+odvC8E094mP/GUwdz3NffAhTyh+zkNCt/LOH9K8GdJ/+YQ/pr/aQvryH9HlnSr82tF9/34n191l9sl/3N32y");
        a("vw9BZ93GreL6xamQ7q89je3WzbQ1mBD9g/NJbdM+m8jH7l+cRyT5cZL8RUTeJKhDF3sKqP8E1OEU02lTytJ805OVMptvulWxW3");
        a("3xLZMSiKp+yzdQ/KSJnyHixyZ+0uknetCh92ODt4FbRZIjlwSK1W17YYmVDhjevXuLja3QgC1RijPYhWc77DYmv+XyC5FQv2IY");
        a("AVWmmfGGw1rqm2LleKseb0W8GfHmAI4nTtgYS4QYsjhva0VjbPvrtPtZGKR5v4qIhPeub2JaIAmSE4S8wrdtVZqOkgYUK1C4/C");
        a("qbYVmi9+l1QDICJm1O6Iq0wdbVA9sRpx4ItzkRXj401/hV5l6jO8V7l5/yO70jTgcmKAzkm/amxBkqrAIH1npNhr5xhsoU7Mao");
        a("3hfTw9l+LBuaZIayoUZBltTlPrUVfcVtIJv8E9MwzFBUK7NxH9r0eBsNP+LTAhY+sHBnrHqKWmLvuj8Cz0zLoDENeoaL8fRRHi");
        a("dEV/sSPBbQFuG9sDgY+DLiPQLuwp97QrM8MsyvtepvYjRwd4l2HQkkIX9lWqDN5c8ha1QxpigSFkPLkzin1NdIznW8xzLKF5TU");
        a("DQRPNBd7v0zwmK+qGwILrUl+LaI08Z1S3w3I1b9EYxf07znsWhe0SEHbRM0tbpnIjMxK9lPSeLuVCKDEN81GOuVggO3NsC7X3f");
        a("04lGfE+IqL6tc7WfaiRaNPSPlgJsJtCaRxOtWaAHOTxg66kWUOQhnMdv0l29nAdjbQIL7T+dGms6wF9MygHxozyQ4R80H2p+g3");
        a("qkOC51wttJcFtMpECsd5zMxfm4z+WvkgO1q+8Bzv9iZRBrNnBNiUDhyZ21C6oGHVxrSdt2Y/SDuKVJ/Y+nzMF6VQECOr//D4dj");
        a("k53RBzhdKbexJy8fNPwwSEGeI23G/7Lg+UFcOTrDlQ7GIcmkHg4ykvsCSnNO9g+ZhS34WEmch3GL5eAQ4U0yNl7zP8wdDDnPlk");
        a("XD4F/HGSKJOZA7/ObVEKkrH1UXGmAifgAVEuGxdy4HRnhTmS4pY3xJvnbxWv+KRT+sQZys/m4wMsDmjKakfRQai0n9wMrT4iHk");
        a("n9gnf49RsB4d0PQvUcSvHWEyGolr7PeClj/oSF/E/9OJyNPOojZg6nU/i8RBy8Cp5CkHVROSEJUFj56p2icDZ0t4h9l8zKgFoT");
        a("zYeQV0bOh9BzQsi0ACSAkiZQouZDxfsEUwGm92UKGtxJWmSJb4EVLlKsBg4/TVF+1Ohmqmo6msDtv8IoX97nOk3sdwza1ro5rk");
        a("43nIwqpCCozjkiq07aniiG4hmx/TbdaUi+aYs5zrAEHJryDAJqpp7NXYJV8WHEcRZ/oCCUYT1lGI4MdZThaKee4RxkuCOUIZAi");
        a("bgp3i3RU3zNGxGzTc3gGryzY8msMgLs3bOA+hsY1vEN1f+EQMNDVof7oqd+ov3gAvbxSs8jfiLHRluz8PpXgbIIxBtwKUDKw/i");
        a("EtQijFZmDyncWNX5sYoSSzA4ZJKzUdmnTUx1Gfi+DUugkZ4H7ha0XqZUGFz/u2kdTj6cFp4tsoqZSmqhlE6RBMa5b3u6ouObvE");
        a("58Tn5UB9WwOwP5D8A0WUku0viqU5YMIPXi8vltUv008tczNbmKx+GcOHfwm86omxNOKH9Yo4j4HDgeupPUH8K65bnou6iu7wHs");
        a("ilWRPVxM0RPc/QFqjrzDQRVTfRfIhWcUfwIxJnGl9ImRlw2FC3OBCTt4g93wdgrGmeajxoAl98/6S+2f5Ikw210A6rYDugEiX9");
        a("CFn2GMnKdfCqV+r7qjjzTXxGeKdwdbG31GD0PssTEtp3BU1K/cUDl/6Co2qEMdr25awjJ2Q4hHXrCb58QLlsBfum0rpb01vh9s");
        a("JmSTOQxWtzMZDRAQlHnHWikxIsaXGsGxUaYTT47nCmShNMlebMn2KuBA9SBOtVwuGRjWSbPd6XhOZGLNrVJ+jbrUxbWfDcOpqG");
        a("Vp+DT0Cw3HNszk0xNfxB77ff/E702zbqt3EbANECsgF7TiwETuUuGkL2IxUYH3rnlH+ddclw6lmqqbU4f0a6taI/eoQS3IP8DX");
        a("2EDIkmMTgg4xfBq99a0kqLwjk4mwh7+6OOh3pR85+JN/BQJNS0CS1QAgxffimaFl8RLRff/oiI2jOB88h8y0yacrwl6Vbc263n");
        a("vjZOTGMrIE50s9NcSSsvETFLBhSdK+3f0wNl02tG/lBOQ0JnWDIYd4XnO0fS5hldRj114ynmkgAu8U+3ViQh/VJe2jkV98ucGr");
        a("4MR+1X6lI+IwnttUnEDTMzWGh9P61BxAEiZhWDOJcT4m1AlLQizhtA8RNKElA04Tdo1QtaaQSCFoFAnE+Iw5O6nhai9ovx9OHo");
        a("u4wMkvtXf8PNz2uFwRfvbcSeR4FkYrcYLZPE1OqbbhbmV7Drc/qGNDozG3YjnrdQK87nn+VJgieHwiE62zGbcRhMlqTZ/v4YHd");
        a("d1+xnbvp9Shd3PN9iu32h29yVKB3iBPRn4KZ8NvDdZT/GrQpOYjhzadISKTdP3mACm2GiuNKtrO/kUrW3SkmGzJtnTWx2F2iE9");
        a("82VqR0zrLLHrV1dB5s8xNZC+oBnfVOAa/jZM436rf4ODHw+fuwYCwvxA5VA2pRdqQfqiddNVmn5OcePeYCvpBJcK+Mz1kfwWGC");
        a("bk85uIoTCc4AcvB0Wfx4MSVkKxAfpEZzKZX08027M7A6PQDnDSUBI/bTALSbUfEwXiEinuDcTZXzTaa6X+4pnqw0wpTn71sj4D");
        a("RH3qo+oj+V1Lw1kGZT5IUiurVo8Noh7VFDfBHEs/CuzJ9pIS/erU07EtIcnzDoawCQsdzKHytw0bR81iEkDQHPv8r24WVv9oiA");
        a("dRlTZTlXRruS0uPpd0KbPLfheWIoqy9wt196Wox1Hek1+KgFaD6uM0M1nuTjMCqJtyPh/98hotd5OKBRT+24OkSHOc52TCtiK6");
        a("Igk0278Wcfzt1GEurLsOluu/ROYTSwbAXBUCuyv6EIVXNb0LU/VvdHGG61FdnAE5F82GVMeSusVqcX6l2bLax3lgmqwEH+SxQ6");
        a("xMnVKsHcPA6qCX90UF7nHuEnKQ4yWZB6JXQmdoJcQ+EbWq6Ie5ncj9ik358xsCkb0k8zuncWlE3kQ9L1pQWBtDpTy2fJb44lxp");
        a("fZcUNi6AOCGkkLRCmsFCRXawsaZ4fhqnvTm8nF9beG6gjE2nuJ5wcWuLQ6cPDBmKdNqced9XmEEhEE8zVN77TsuVx1QvU9tGHW");
        a("wRjyNw1aA+dhPmR4Dsj4vfpiqRlvUFv7mqCxofpjpWjQDj1yKoxpswBNWmvyBspC+1giLHmrSx3PyoPv88jAS+V6PhHLoOkOb/");
        a("RKUJeggMJwL/w5NGRHs3REqYZPktJgeXD9QbJOkrIkjnRZFunMukfyVJ03qIO3wd43FgyOKkfAXN3fkQFbeiDCW3IMhN9COgvh");
        a("PPtfjgESnffihCvg0IPjzULw/yVVpF0Chk+gsR0O0RNnfhD1i67HccjpEL3gFvjDAaEPo7QoMQov6cJ4ub8lB4owsBobojuLpV");
        a("Q1GBA6S2MC9eTFWzsBGi36/w6yft8YzQPkupModm/xRtStwIa+sB3HCPOu30naZ3VXs0xmzBaZf/UVH1GyjOck8V91aqgsMQZf");
        a("7hW52/Lib9+g0UPTkNXWBGkN9Bm4KMu9iKfhBHIUCmA4iEb5zqrNnAM1IJM0UJ65rRmiHUmo/B4OSXGnAywfMBg4Zk1Jjybfh8");
        a("VwtO8Sj8zccntmDqkwj41bN3IOlQFg9k0sP6rPNErT7rPFYrbpO5abl2+m2m+4+HdOQHJLK/lpqVpmbfiMIY70WJt1DiLSC8C9");
        a("KQvl6m58v0XEEH7wvQSKZzm8QbUanj2RD0pXWZ2iX/osXNoMV89zICdJf0gOiJT09q/LgLAYr/6Rf8lSTIkho88j0BguLYhaxL");
        a("USvWr1i8IVJfG65V8d+OFpxiRBXo2SZdRb6ENZpuL3qHPuvcZv3+EF5ES5RrclnUQs89g6m3gxHk7VakvN3z6x70qXtIn9dD+q");
        a("we0ot6SM/qIT21h/RT62OmR9gT3mTEIExar41rPgK1zX58FunY1o0E1DBCXB0AK5WxkoKpKYyFca7dLb+WGOU/ub4Hffge0j09");
        a("pM/qIb2oh/SsHtJTe0g/9WD36V/1kP7ugz3py7P/dV7Slel4InQJMsDHbjIv5wdi2IdWht5xF7TD8qHotGxwTif0KUj0W9gMka");
        a("WZ7dPnHVlxoIsdbpkfJY4Vdo4UM+q3jndJgCBmM5MNyLmTzNoevuH6YFCvyvsRBCPq0776jPW568z1kfMtP4EpWUfyS96NDt1I");
        a("4aePyjVWvKdTUrYgwWc2pj6L39j2xdXDZcEgT4FTjxoN1blE25PU0AYM9Y4vhJwhBymo20tloYXHM15J/RlINtgJreknenwfkd");
        a("EuprpjRygfbacWAQV9iCn+wbKY1nil/tYbR/RqDFHPJtJK6kbE+eKwKF3L9qdPS38RXfW/avT8bitAD9dB/Xwa8vD6DrjFQMmS");
        a("QJS+ibrvatL/A3KuyNwIGCNNDRymvnGa2zZUJP1RJDWQ/EltpxHAfvI5RJJaYkK+HES9fvs79fqNof1YJ9O5EVlYtkSPtaqnaf");
        a("IdToOUJXtaLP0awFuFdyftnTEYkRYDBKwUyKb17359tZm6SF9tnAiSaaYkEpFlqznMORic2jZNKZriM9Q/z9SKtaP+PfuDkU6d");
        a("3i1U5mHR8eSqo9BbLaZXOozc5Q1p7E+CSvHlsp+bReLbudbaUnRSjGLRSW5EoQ8dh2v5VGSDsWmq3WXMf804gCexPdl9zFNK0U");
        a("mHr5dvdw/uTri/eHQnobWk344e72J/BkvoQJRyxvdnw95SE3pIP3x1t+niEesXV/PxordaWRoM0htamSOa3iVXdUMPp5x42GQB");
        a("2mOy2Mj867qvj1rTXbo6uTQqtau/QU13qKVot8GqqwftheYQlIlaiv4prDPXle6GPuYPRoOjvhciwNOPlqDlvdRrWqTuzhnkoW");
        a("zrufo0Twkp6v6rmAtw8g2kOnwd2TsbDAlgqn+IaJwSxd1QMPXpe+MN9Na8DyD4d4v2MRPln+O3PdpTjDKniPuUXHXJVcKWPOuj");
        a("8X0dvVHNRskG92BEMu2f/05oPwHjfNbqE4/37uxiA7Gb94JVq/6r+n1cyvU7B1XBWYwpzfitXps8rb4r76H69hGNqEFkT/WT/P");
        a("FibP6Q7697SF//P6a7Snt47x/mM7SwlvzNNbARuap/8D1ThNVw8pEKH04BC6LIwuXIzCpytL3Pbg46YLEByvUkg3Ec2W03d1oe");
        a("bKpuI/DIHmKvFSUsFaJLPZ816mVXV32SJHX2FHFaPPsf4ZNQ7PexWB2yS2QrY6XbZHr0+q49nk+CCa9fTeWn/MOz6FzqHpC1in");
        a("7SskgS6R4UOB98+VocG7Hy53QGbCwwhx2L1/ipxntOTJqwEJUoYODGfM+M7gOf5ezEPHrrVFmnsPEq7n48Z07tPn1U9+kvsfOV");
        a("+6ZIpKj8lV2SosYH7xmUeIJZr1kE2b4mu7EyrUcMoGePMeQX0D4BrRHQDwKqEZDpOENVAvq1SFspoEEi7RYBXSCgWwVUICCXgK");
        a("4W0BwB3SSgGwQ0/QRDMwT0lYCuEtC1JxlyCuhmAU0U0J0CmiCgOgGNF9DjAsoW0HM/MjRaQLs6GUoX0GaRdq6Azj7CUJqA7hBU");
        a("+gvohICSBTT0FEO9BHShgAwCcgjo+BqGfi+gwwLaLKCDAmoRkCqgTwTUJqCAgD4T0CkBfcRQyuuQTqh9DoFXir7Xtr+3+vSd0j");
        a("CXvlM6F0EIHGajlci2HQ8f1IrJfLFAuJCvpv6d4r7gOFMuaIQ+Z2wnSXmowL2GDwwNvcSZkRTNJD+GbOe+fgfqs2ay5M0u/vke");
        a("6eG9+NuR78UPAj/cOO+NVEDmZOmB9jHdA602uWv12UnoqRcSep+I+nC68PmhdhTJlCh9caSYt2K2lMmx37+vi9ke6CM/Ifw7qv");
        a("yzuYN/3tMbpxYVspd6IiKUxw//msS62O1Suu70USkSjvd4Kqruz88DWgstr8PnegNtU9RSVBRyZniEFO/rQaUEmZgm1JdJs0S9");
        a("HpWE9jKFdbtnD16rLY9jeHkU9VS4njHs/aLnBwaM0C8+1Z2/mO1ifaKFofM6cdQIiXgcvpPwGW1ZW8nWroP4J94VD4Brd5eSMN");
        a("EFN5sVM0ZS1Sy/N2V8YzRUTLbXdHrGCSN7nix2SLGjTWjgt35IDbpS81kgnnwmTJV+EzS6LQkTf2agR8oPYJ4/0521Mz+l/Wsj");
        a("VKwQ6KBAIgJHEJAmns6g39oU5M2veHeqzpjEtrGS1YUT6bmV+gGOfGd+n9UKAoW+bXr/lJD6PbaBbJ9vIlPqpw4TlNaAUvs1QM");
        a("p3p2dY7pmKEO2+2XnJ5w5eCy/L/lSdiwoUbMQesjw96zn8VA7KunA0oLNgSqoyJav3+wj3QXiJyb/J6CI5UIIPBEnbX4japP3F");
        a("1sh6qQtEjQaq9zqoRt5jv3Sb1R+m8R5Q5g9bH5E9XWRHwNEQJ7bKDmoPCBfwFvgs1SEqn6LmMln1t6AYbscw0j/jU9qT3Vz0W2");
        a("Ht+74OKAUZoU8wHUg4MhZsxIOhZcOy6Mc9Ee1clupV41aw/WUnvQYa6/KL9/Om4hp9ivy8XJ8iPy1nCa7L0H7MSFt9BU2y1x4n");
        a("jstVzwLjk37ufiOpJ2NvAPH779ogfB5pheAdulIz1qTeD7h2e3qHOxnm8G2QSx8H45qFSf3AJchwO2fIRoZsTuBdyjlIuI4T7E");
        a("iwGzx9ChVTC8rhvYl3b/yyAuojtgdGf0YEssjGznNo5ktsReBcF8HPa7A90JfhTRq8lfrJSrJsUZypXtyKoFGu6jzSx1gIWJlq");
        a("rj5GJC1rTwKsbqewm/QlLGsDiECgajD+uEe2f8bZUzZ/ST2R1P4uT9USflWDnxSwqBm1JfT+GaxhTxNTHE6gYs5z0kCHz320sy");
        a("NOIfazc2VVt50vt6q/NhjpjlXztTIIulqDUZSbx3klRkkr0a8hJALhGLrXM0DdeTlzWf7VjER8UpKPMXaniAJ2QcYfmieuE7NE");
        a("l/1u8ASzwVNtzAbd67MXsmWp6dmU70+0Z5iLI86kDKemHh3KLOmz/Z1CZLOFsm0/IbLlT7K5R6Bcf7W0n7NY2s9ZzIw7DySjaE");
        a("feb9NHQuO6bEwW/QiesuIjcQ+g/TF9TbBl+JrRit8c/Brxm0nfyyBZ7DNS+PI0guJ7WS2KnL4UMxLmsKLst9TJaDBwXuclZ1IZ");
        a("KsZjXH0nM64nhfZfIs0eKAbQqQEo7emVeml2WVoBgvxg1DOYembycT4Q9kXKyyKrE2Mnd/IxxoMu8UDaoO65kgfxy708iBorsK");
        a("6IkfXrluFDAcFsWf9cxfQjsEPf4UC21xcY53e1eJcC2diH+qlOoTBEPQ9b4wziYXg2OBzfo46X0sVHm/Q/DdbVHZ6pqCG5FuOl");
        a("Wix5mx+kDr1zqbMOJgIa7GITBrqVoKu9f7MhyFzbzfn7dPm/ba8ndNB+Z5n0J4tPR+wa/hhlsSf2e4O9VfpQen6pD2U5gjyUun");
        a("0N5kyefW5ERBZ9v1W9wZien7j8YBmCPb1JGDLdzt91vmn8F0bMTwgUUMCCQCEFejFSmRxTfA78m0G/fDak+V9W6/Nb5fyPIPh5");
        a("Pu4WY38/vo/0ZWa4WGYm0TJzFi0z/YT9hGDEOrNvuV5OpSynAkHx3bDvCjxJjq6nS/Eui8cPu3fWfeo9kB8MIopsNbiUcaYbsE");
        a("V3+TYRIjc6FwUulgWaZIFxCIq31r1Fx8jvJGq8smX+Nxfq+ZsQFJ/eWcK+hVjSXsKwQF51Wm5zuo7/Mjn+kl75Qn3854nx18bY");
        a("PT00P6Qj8+9l5lyZedxC7r2bDQGSOq+QOAMlTj8EeSQDsBsi2EZWMbb/figX+iBuXUC6zjaoZQbyhcpaNsFwUTClLnkUfNSpv8");
        a("EqBPWOUuM/S4wLbYK2wLQyZibe3n1YnHdq2dau+Ul94kZaxYx32rrKVqLl98xwTGSpRrolcAX1v6TL5tznfuyoJ+5RH4BIIvO0");
        a("c24LXhXErJnv47wjqFd0/o+0/NOLabHpdBorbFLcH27fbKnTwHL9YJmca5sjxrvjV/pwrL5ZH45VN+vjfW/0934DIsRqdCG+Jc");
        a("9VLj+8dGOypHIfuCNOXKcXs+bVnSj+J9wuZ2CK0P+RxZ0lixuAoOAQ4qJZEufQAh3nuwWxvvXY73Wx/kkaz0gaTy/Qm7WODbuG");
        a("2vRzvU2XUptmuvyWF892zoWX+Kbl/eASwLcns4nUdfqjWdze3ny4QQz50dHa9uQdepmXyTIvRVC2rVrinCVxBsi2yXE803pw8N");
        a("bu/BVi3JdjaD0q7kc6urgqJP2MpfK92nz5Xm2+3jOjjaTfo1wZ6pvOUN9YalqElVvL6jYEXBGOD33NcKjf13cocw9dkBxgv78w");
        a("oHsP7T1Jm/apVTwVRvpCrBgfPj9+NYPmx/wtND+6L8ep+W6EdPcO891iTQvJCXqDZ+jYXAqK7KmQyt2J3Rd0qTgXqsaIny8AIo");
        a("YSyZuQDF7WxuvHJXpPrJFil7td4eO1Q+K4JM5clxwvWlp/Knze4ytF3yvoe2WeXGblfgFYYSxaKUknSdJmV2jmHu3X3v0fczTw");
        a("pkLz7/wMC3wPcSK+tODJsBfsgm9il9dLlvfIPL28DfN6Ku+9yPLujVme/P4Ka6mwhyr1wsbKwi6Yp3PZBtpaVmlrCAIbQ2vIbJ");
        a("nz8C/0nN8jKNaHnwMnX+JslzjvIihG7nKXH977qizz66YH6SP96XEe99DDq/wy67LeiA/OtPs5Tsyt9toJcJAx2Wbs5j6eOxMz");
        a("GvhqrAnSt9tZIpmPHQEqUPOD2DpXnxZfzgJq40GeVszeg3QOvN5P8k6BZmfTn8FU6h+Xj7cPgSJKfyyUHsfpfWU68XDwoLhXAa");
        a("JbIDppv9rgArLaf2ssv96+1pFB5BYStMa2PonbpbNl+pcjJSY9wXK+gW5PzlbB5NHp/284JN/8xW4jyzeTuso3Wd8Eyd3qE50p");
        a("XTEWmHAMt6wKAsjiYM0HPPWlWWqaObDIUrNGmwxrHgoFFmuBVaF58q67Qkm/DAXcIuCeT38u46ibQ2krOTDfne9VCyw1t1Dhvz");
        a("KS97klIXo0yDy/+etb8bfZ5a/PxjdKv29p8Nva78Va/EXab472+7GWnqHBuzX4E+13j/Zr1NKDvP5LZmF+8u4zYmcaxzQhXNoX");
        a("R9BFISiBoOwQZCbo4hBk1TG9e206jZY4Lu6M7wuhevYRyZB8b5XmBKtzJ5VN3kvPA+1QOEgLDMA94tEdJGJwBVM7EICgeVhnib");
        a("FT6UVuGlpg06vU8Yr008D+OeAFLKF47hsldWOaHNXLDZTqHuiAAvKUuoHZtIPp/bi4X0VcwlV1E7Jx/0SOO0jPqiT2O8Mu+te6");
        a("WZucTtzorLopnlOlCt0MCNrqE4wGticwNhhEtbduK4QHVge9v3di1bwfidiGCN9kpkJQwEMDOl2mqy3jMDfHjWXhf/52owEm+t");
        a("v7AJ3Wtz7S7iIcGlEvwG1ueYeDLVp+x2u3PTtY6jsM9XX4mXWs6TXBWJ4zxlaZCIt5/fjMZa8JurPgkJJkVY6VVcbh7kRE2S3r");
        a("WgLJRDfQy+VHsp/CRNfzEr1bA79T5X2HqPIfAuBivoYeTe0cthBTooxpap+h+QER5eZp5Q4KXKiVW4AER22RgeyvBUZ1KX8gl4");
        a("85T6KF1eMvjm1F+D4EiIXmBK2C447N5uLZL1RjWzyE40cQo07cyXKFAKdikBI6UDVpmuYMD8oi7JuyQQ6WgOYvti37GYeU62zz");
        a("62aavW1HGcSDbf51zkVAlH4Hlf4GxFd6AoYZG35OVlJuQDJF1CU3kq7/p9gniXOwQ9QUCR3OuezyWa3aEe5ZOEo+QPcLOIRuM3");
        a("3zrpGsoN2H/D6TsguaSEWHCRf2qBBFb6jatJkplf4s5gnpEZpuBvN+UN1yAfrR9P1tfIk1ZTGqP+4yFjGP9gdTnwcNEAziX/sB");
        a("1t+CjxatBPXbJ1HB9l1EvmDvFHwEML4+ki+wd/uKDre/ZGD638DFoNBlGvoBa7Fe8z4KFnVAj6g3xmMtnIg4lz9kexFIdkJy9w");
        a("VCCyPkAPaT/tcO1m1aNVroNl2vtoJoALIJb0EWmmBwTxNvT6oXoSG35nFDbKC3/b0QvSVMrxGwX/3pZu5/ddMefHN98afFtP+X");
        a("2tdsumyn0DBSig5ryiRS/tD+pNh1TycrUyPd6DuYWC0grZGqgfgoBiCmniBnJj+gw066AphTs5F2pIIvD/cuokxNkCypH+bQQX");
        a("Tc50hA+scifSvStyVYDU5o7yOGiHkuXFmQ9ncjy93qUh8HQlHOWxByvXrKSDPNa2Npj0D2wQmf65eJUkmUabl3WjBCR66rPW7K");
        a("UzUKdK/U6c4TdF2gC6r5hCHGB1CWgCLXWZACgY9YUFdmR/AdEXQi6A9ysAx5vy8XeaW8Fen1J3CndeKfLKp8IShEfTRdqwelUE");
        a("PXj+ZO6NfQCTz12gyeKjciDkn1nNS/4UdKmiCSnkScH2l/1dIOU5pNpD1EaXgP+Tbx5GrwMtu/WSjfB8yMeB8ASJ2M7GT/RiIl");
        a("TA9HOv0TIA0TSLdIpNenhiM1AEKrm46j1aZ/Mu7lEnfE1eG4QwEBdz3hfhJg3P4S9+Mrw3F3AALuLYT7N+C2/yiatO8WPcO2q8");
        a("IzbAVUgvuQd4yGl2hHq35zmEvYKDOYImoenMIZypBBiJ1eFBlWyAyFEVW6HBAyXKhnWC0yXCUzNE8Lz/AqIGQ4+HYowwyRYajM");
        a("MPya8AxDACHDu3qGEcggOTOW/s5WIW9yFfpIgYcuPnIJI+LWlyNCq3qRb59ly/vCgI47E5KnuSpOGjB5TPP4AwvZzWDV+K6ECn");
        a("23pmeErMaGE7kQRBDv8i1VhUKkXJKi9xv6NaULxxSusMvX4SoYbSgvcmWNhmzDlRX3Rnke/hoqL3BlxcP3AYXd5/HfQa4sz+Dy");
        a("c1xZiTBY4soad165hQ7qSxL9sCkdSGLf0MIvQ+xGv4f6FsJ/420Z1OoOnFXw/lztWEFN7qw6v9Dnpgb25gZGZChABiS4Wuzqxg");
        a("RxLykX3qj722VCeentlfiN8uU/LMJjv5IU0j/4/DqyX4he1OVGkl65Ru9X/wG9e4jeQ+H0uun/6tPtpzwm6sBeove677uLzg/v");
        a("u3VLRN+dd+a+MyKDP7rPZPsuqtTb1Uuxn3TuUKllI0TLEPUTRB0Ia9vz18ZjPsiQbYvq/zbPf0bv50Tvl7Hpwb5Af7qfHER/bP");
        a("jjGUFr7NWjWMsiQPKh6Vq4D8KXpnPYsqVZkoqWv2L+guBcySX99vE5n3qb2X4hokiAekkTaGr6+33J5Io6kFrj3cYmmz0H8r5Y");
        a("8Z7LT/o6J4S+zgxeYM87zdB2Af1WQG8L6EsBvSGgN0W+8dcwNF/TYxJQ3imGRgvoTgGlC+hbQWUUIHXaSGqlaY+DRBmmffjZmI");
        a("CAikD7KRfsbyKA9AP4kad8VsUrwzKYGveW0YCgHcGPibhid4Ly37EKdNHvXVnwQTPtdUYrpmXI5T0aVzEAI7YY8idhIvrrbLIy");
        a("3WI6DrEK/XPR+tfM69/L2vr3C7n+RaxEmwCpf/xKrH8SKaEkYv2bCqRqgXSLRLrQEY6UCUj9mUAqkkijneFI6YDUSwWSTSJ5Cs");
        a("ORFgFS+wPJ5cf+2g7gSVvU7N9V3wOvlPUPVX0T+KBGgrAMvCN1CHXO1xFL2+dXxJ4nZ6fjeWP7b7m/fNvXlCRkuOCoYqTF4Kjd");
        a("Wtua3uGobazBfnSMzd1vjNWdMibbPWyM353TTrIirDJbXSN7Q6egl8FhWdfUfi3T1OKTEZ/A8Xxms7y4dfXWY24c7uIwdg7L/U");
        a("2BC2iOXnnU4hnknAsHrYUJmZor2zR4tDLhJXVgMD27Ti3K3umH/8Gimp3uRO9xi2XtdOxC+JW11CmJtR7iVDTnOCtFscUfGMV+");
        a("hd3QNdyjPUqiq3+6s90NWB01GueOwvTJRsx+OZ3FLTjXauc+9CCpCKxNz+BLA+FOTr1reOipg7tEro91wtMdz3wE2DQgm4A0Dc");
        a("goUWqYGKnrK6AnztSFoAizCkhrJUQFuALN6RVRMCL1CkdAGaQwP+uDRqNh+VTMAfmPpm9Hcnki3FlZ7ilF7xTmHVwxEFMF7l7o");
        a("iEYS7fXPsY1wZWA6FJf01viSmxx0Hm0/m3L5VD8rlkGGpC9o0v4/68dCvWHtt5qSjZH1zJWplwhV/XXY0+qqNdld7p/LMN9Id2");
        a("185XzRMIp6Id3PfkMfTd+A3xJ01FMMHxdIWadZRYE64wlOX8v4jnr8Ed9+AwlYVM9QTdK7MSHylprirkPh1AJ9PQPpbM24SiN0");
        a("cUux3fnAJghw8VH6sPX4Vt1zdDHuJ9N0Me5HCGKshLUJfhBipd8MHJKHUCAbKTYK5LawVgo7IpJmYoShpC6NeWqI9rE7m2P4/8");
        a("Ry+kGp703oKJUolTayU0GmeAqVqmyEc5n5qi8nOpaahcI0Dxe5DY3nCwrImM9Hr7Kx4Fmv07R6NqQgnxzZbSnGIB8KphYjkkj6");
        a("2fuch2QZlpoHQQLlWcFNLF3Hbu8+rBoOiKw+Xd5LLcpClRuod9Upz7L6JD/i94H7Ksya3twkW6HvziGaMT906bDZepfeVaZ3qR");
        a("dBZn9SbzGdulHH+bnEmU04vjRqknEN/sRyNdpVPifVJrFd/RhdWOp7T7kywbvX5ZlTfQWJTj2DipW6o9SIJ4UCSGIR7IN5ybY6");
        a("OwI5hqajnOeM6jzQwft+O3/RCGRwv+MKFgcVB/BLfMdKyb7yuTiuFADmcmyWmpw4bvoo2Szlar1ZtQhqX34ZxoXdtKz+s5FzHJ");
        a("6l57i5SM/hQpDQ7UASNVSnniSNzSN7LMVNviZy6ZnLBB4VONBD+NaoGVy+/UvawNUwgogJYskBSquOchVQnApXh5lcaKstvYjQ");
        a("MOsJpMwvxcZ0GmT2oYK6ZILc5RWjISzXD20i1zltsgjv0iF4yH0Hs8c6jlEmDiG56LbVncvmsXV5aq2TO0f8GruWSPIpDUkZ2K");
        a("SVfklk6R6t9Mf2YdoSyHnbVvzF5Q/rqWOBUDdcqWEvArZL4R6jnU0HvlllLUPqQKisBGbgkxr9CkC3PVQd8tUWCgufbbJjZBgv");
        a("Jn942aj5cHuLZFAi+BqCWD9BErlh84SXtSiXpd36r91Y7HtfsyBnB7uzZgV7pi2huyS6ujdDkufwfVikuJwO8TVYVo8DWvu5LE");
        a("sDb+dCEpkQ8hFdNyGuMH+RuTwdf5MrzlWwYhvdJqzqlf3VjfcJL2obasD15PUcOAmWVTtEyGzx0uRHfj1rXkPAoX1zOLvwElvL");
        a("H96YI/zhJWsOdNtzjJQlrjKP1HDtJWDwW47pDF6o9C7G5hixDhR7uxqyQeJJJbF5YDbLl8keX/xkM1JAB1uSRkFJ+5jVR/hLF4");
        a("5qgN/1oy6Fr056/6dZG3x6MMvXqADqm8J8m2UV2e9hQV+JuLXTutW3i+8/eB8fux8Tyq+knqmA+1x829SNnvH5sxPK++XPNlck");
        a("i/GwebJR3nwHamt0+CY7Q931hIGmlWRqW3C7P6w+5Q8AI5fnxLIIRpHnmX/RdiXwURVnfDcX4dxFCARFCQoaBDF4JgZ1Q7K6kQ");
        a("2NAooWlTY1YuuBklWsIQmGaJY1uPWqtrXiVW3VitaD4pUETKJVDIgaATUq6ksXNaJCuLL9f9/Me/P2vc2GXvx+ZOfN+c3MN9/M");
        a("fPMdwOl3R/FoIfQGhco809nlq2dB7JXFV9vl7vqggd7g1PdXlu/d8lvV3yjYoyujugWCky406FfX2Qb9+hJBslRw/eF6viEqX5");
        a("PK9yqCwlLBjnlG+m9U+kpRD9nToPP1rVCHrDl91YvMG0MPv8pg/dhFxZQrifVbiyWpTZ2JEGzib/cRs3kYPrCGr8dV0AFZaxde");
        a("YrRB8EZE7/+3I/zHG/EnjxQiinbgz2tf4s9pzBlPna0A084yAPsCQeF8YgRgGN+IBrdcIvRRqvkmtQOQm0ccNb3hY9jWmfULMx");
        a("Pr+5zSZ7rir+MRlPFZG5OprHbL8qMS119ArlLQh+hoWzbpP9GaYNV36Cf9/X7SW/pJ/1PidODFHJBX7ZrPVDa7/arQ4e8QGoXw");
        a("5rEoWrfL5i892KN7yYdXahhpW58VGR4uxvZCusfLoTFFUSqT24+l2JnXW73erE8p4L0lmmi+lGNuJSnPjl3v7kas9uHNxFGuEz");
        a("of3h4IzHSn8FmypBscw8XvOHGWlHbPutDpLRc6HWwnG/yPr8mvilf4kfonfQLSuenaBT8YGHEQ7V+YsP0Rpva/0tD+Rf23/8H3");
        a("RvvKH6fS9o3nvzhhOgBnIULccNnddybCMrOejqufNcmsH++MhnscKiWu/JxqIpnrofd9Fl9Mskazg2q2BKQibebe4srLBnqk1O");
        a("6yXiTWVA5EgBzsVE3GUD++WQ31L3Ig4eG6gIeaBIlYwlfTloJWhblxbrmbZxIVh1BxzHwvEvpEj/PPK6uFWtEa/PiDjzbyz1Nt");
        a("/PN8O/+80iFO0jg6lHuD66FAtIs1KEdDybkncliYxZ9qfy18IM9QPpC9iF3aA+lwei/6DuDme3sCm0TciewAcLUYnn2+BtQQSv");
        a("KLCJzYaXNFV/LaA4Nkc8AjUuA9wut6cX1x2Xs4/qIGMb509r2bpIoiF5pw2AUc1giH765mHEaYnUUAhzWJwxoG9p1/qIE96kvg");
        a("8D3n6wP7tBzYCVCIC9V1sjCVGLWQGLWQGLWQGLWQGLWQGLUQj5oY+INf711VidZbmQnWG74ArP+c2+96u7xbrXeTfjSQeinrSE");
        a("Mer8dw4GBDvSlo9bhNqtUucOS1u+ZYUS+pm8+uwR5oBNPuobDQzZICWAtoMYQWQ2gxhBYtwt6J5QVhT0HBiGuapNOQkSou24xX");
        a("SDTsndSpN5lOh+bg617nDghGVKSCV1L9HkLcF6dpBF8hitU229qXU76lvljF5mA7QQFtk38MlXRqK29MoI9e0k1rhSwBFJCynQ");
        a("HDQCwLmBzXloWpO95OXktc3Zl/5xiNBfnu4Oc4fCgQzPLkbRgZ40mAeBPSG/g6JlGEQJq4DO8sous52zPEd3z77rBjrl23gDiv");
        a("UdgRbFTyuab0mxOkA5qyTWxm0mEhrH3Ru9BMtojyNiqFEtxxmKYtbWqaNkwDyo0/zzpNt38tZA8UqnUoghcfvxT8Oy+1i1gp+F");
        a("ZhvjoxX5ofS7C8CJMHmgXx2xGYgk5MENKaouct5PlCSqdDiE+Fw1iuHXSOoIX9gpM1LGndenlGcXC4CbkoD/WMC1acyW5iKnIp");
        a("A9XOg1bPKuciZQTa6zCnli7A+wP6rk2twwxSrESKWHnc1dolBX3hY42tfwCK7V/228+7nKqfsnlY/XMT1mpXocFyYHj5REf0+s");
        a("NcL87tLsI5lD3bfAY8EBXOSI9kUBJH/9QUzVXBMUPUVdfBxt7gBycauIKJpNPQX/U2ou01gwmjP17oBOXnriYjiPTVSG9Hettg");
        a("QTLX0DvveoVJUz7Fgn+xVMekByUmeSPYOS7vu6GAauh3l/fZ0ABTQ09QQ7NsDW37p1R5mY3F97/Ag5mAXDu61oIHB+1fnxaedu");
        a("rFABPO+4bR7jpQOdSPo//EhKYUV3CPe53uL0Gl83dmfPus2MGhwkZvcrPRaCZM7Fadg7e4Si+E5yiFIyeBcGxA0lKXKQqi2WF8");
        a("pvMnfPyWtWiXuaJRkplKLswE5zAT7xnd8aBFD72s81KwTHPC45r8HRQZS+Zx6R2PDeE6yVVDZmQA0RLnuZmqD2Z+uIWm8hgSM5");
        a("/dXABWENRPMKcG81Nxxs3/+tl/6blvKSxpp7GCkYelc4LthEJNe4AfnybD74WvqScJ/ASMThqp8lXBB2FrVTJcF1BEatTFBu67");
        a("idSnYdQgUpwWSPOHytIjZ/saQGVDo7Qb7jOMfJ9ILVYRvT22WSHvER8BeZ/xW+ntOI3pLWTr+9jL+8c3tmeIEH5BDI+A/hgh3n");
        a("BCvCEG4tl07+znfd3YuE8y2gEo6ptl7Gjx90eeOS2b90GyCV+YiRF0Vw1iZGM8SuCvVvm4IpwaAKmkTMfSAfR+Xgh9g7Kv/Mll");
        a("mf78MnfVaPzNqhoK5ju1thYPqkgPlpH6Zjq36WyGWydiLv1xSDSKlMyC4AG8x9i1eGLpuaatzO/LPkCNOM+vEef5RnGebxPn+X");
        a("Zxnu8Q53nd+ZwuB1C04qocOg+zKJR0IgHQvh9A2YA+eECs24Q07/KvWYhSiQcY4vh+cbKWUvn8noG7jzx7M+DvFSEqByZQ+Our");
        a("c8VRRj/eDlbH2wmNCgu7O4GFg2fqWLhcYuF94FpHCk1QLOVK0lQln7xmosNUScs5eiWvy0rmoBJeXehqtuhqNnf13X+rq68cZu");
        a("vqPwvNXd1f2mdX80xQphKUWedYu/rX7f119YdXVSWNn6CSrcXWrl6BSsJ6X/1p9IjTAlKarZWm6UabSyDCd+ReuTMFWTldoFJI");
        a("oFJIoFJIoFJIoFKIUUms/MTnJ79xfBppP1aU6+eK22LOT/K4NP8qfqnNIn2abujTkAWi1MdOTsZ3SY5xUN6137qZzhBbZr5tM3");
        a("WNS7yf7vsc0/r+Tf/FfpqtnQrPjxzoTUGAKd1JVwlK5yJKN8igdIreqP0zK8R7q/1+oWlVd6r1b6cPHdrVpybSv2KZWxomSqDu");
        a("gWz7cgDciyhGhzGK4oGiHHxPJumB93X3hhBaPX0RclJkE6PcXA3FurXdAaeDDogYMOGp03Ub6dDAXaLy1wlfhuQDMeSF34WUZr");
        a("xFkLJUyrTGSDIu/TW5rsDF1Lo/RE1pobnINbKdK7+oSnINphspKe1grU/GuuNO3wglSJ5J2SFc32jUP8USOIzObl2fYlDyL97u");
        a("uv0Gk52xh3WDXmuFglS7mOnY+YXiPC2a33KF6obBQjnHEfc3mwYlG87o2JkL6jbfefXi81DcNM8J9UfUbrWR9kejZMx5pFO8zx");
        a("fBvA2em7R5qfygZ7tvgcUcgx/t2iwI5vOYeUWgR5uOgMIYtZ+E+9pPfKy4Byxqp+w6NiktPvJXliaGSRqI/xniiLnD90/B+ZmF");
        a("YUJUBw+VJvALvaaLROrOgUl86RWl2rVfohSS2xkfG4hy7IFsr+C6aV/ngkCOh9AUKDDRJu1JrDMZ5oyN2kVnyIn+rrcPuqbWuB");
        a("r/2iXZjsAwDKmdpRj3PpmlbUpWOazpbJ8YlcEgEYF0zNlGVp4fLK5WDz2m980Padf5B1g2VaPEcqQb1CFESO8QtEfWQ0SQw8IP");
        a("20BeRSkYLh6FENYAJwJzcH/CB5KBarVFuuPRTDFHwylB5HTIM5kaKFv/yPMImBi0FbFvFF4B9vFQ+GjtAknSKETsix8U6DTGIU");
        a("je35/SnHQA1yaW86seqzmZ4eYTephH4FlOAsrfh40Oj4MRJzwK58IUI99ia707nJxtcpTNtobm7uDMYxmRmnW07iS7ORfvqG7n");
        a("sTw+Sn6+aBfioo2QvBA8I4Aph4pQCVOCkL3/PVTyIPvPtJs2B/ZPODGsyLXqJMA93CGn+a6oevSJby94lJQ3nJmlHcMEpEl7p5");
        a("w9sHEb1V9YZBnt66MqrXZhNmz/8FoZRBW8ut9sg9a+X8JJOTb6XyC+9qZsCDuQvjYdpocXsC0a/nty5Gx6l8WAB7cY7HXth0KH");
        a("g2whmOKTKX4bxY+yx69HvKK5cd4Lgu1ouDKtKN/jrjwEVLyEGJQZX+ANUVuYpDBXwS9MaZCuFt1Fx+/CNmS/c8bkz1/srkxnJu");
        a("qAKpw1BuPiiiYeoiZSqYn8xSMryAaCFNyi25m2LQTkXDzSqNhcX90uo7qRRnWzn0N1L/DbPdqrOhWVVg0S9vQXISvFHeUrK2Tp");
        a("MNbhSSLwQzPd0NNpI2I0aBDNGa5D9s4c5H1uiXGfm/WLg7zPxa8ft7D8wpGBwVTpbKEvxsDOxuXspHak1ha6nX34B2Lcqhgr4G");
        a("jBKInQlRSS7kUtCG25HxrXQ7KXV/srt9PPo9ZSPKmxuKkzmS5vrKXlb06XCNAwslvb1tsbDRWOPN0BcKuOaNKS6D45auOvRvrK");
        a("Zo80ZjVthVoXgDw+fTOZyaLtr0FdGtzyvF8cbNfP+i9vJvtpp5tZ/yk4Isfy/a/pUETANt5isJemwf9R5ALYRRmJ1PSqAaTy5I");
        a("zk8neNo2oQOtekJcMp8EREgeWuRyUh6pDam0ZSETebglpLTOZk9Bpj+CMIASr22dgy0xqL4Gs1klzb6CzIa8IlGd5XC4KNM/NH");
        a("NgciiKlOW8P0GNnj6jdjDaQSTqv1dAItgOfpNCfRJ50AGClwf+nkPmZxDmYRrK9gN+mxwR5XL+ZHTmtwejfhWSL/xbU3uh3sH8");
        a("0F9tHlDaVRbbN0iBPs27+lM0r2s2t7oxVifbJ9KcsAmfnr++xvxnZ66gY9nSepqIvp53j+eyL0ta001JMcn4ZmJ8enoW78jXM/");
        a("SVf3k3jvG7V7nYEMfXc3rnndrnF8vAC4lfo9D9wk4/hRcWk0quP2om2Y0TJEcA3gC1NepMnzyZnifJKr9nkzn7RH3Os0lSrudU");
        a("e9j9NhL06HfDSSq6J/+ibIylDz6Z/isAcfLsYKYn/iYVj7FH+x2t1adp7Twct+HAKCDJ18qSCMbiKMgw3CaKEJdnuPHfxj5tk0");
        a("eHv4ZJyxqRejdPhCNhpofvMcpr95PvjzRG+ehz2lGAeXvouqHsqzvnnmQC+R+xrI0HO+t4Xeyy6mGpk9K7j+p+6luRLAWrgCB9");
        a("+fJOpPyeV992fjzxL1Z+aTqj+3b0JVm3Kt/VnwruxPxXgD9Pn7hdNZOvIEBvH0XXEq62nau2P3XyUuQ3gPh3F34fT5THJXiwWC");
        a("NYjjxTScM9c5Sa4cx01faJY7uEX7C7sLhUvaLi8z2+DzqRhHS3fd1uot5WGrJydF/8zPZC1krL2VcGlErKhBO+GnWdRAnfcO5f");
        a("Me819ZZBQG0ukDV2TeZPnM96kiXnb6K5pH69ouloaXX2t7CLWjFRnoi0FrDGAUJP/dfvfW25jS6CmJ97vqjQn2Oz9zvdJJW/Ux");
        a("iBTS4B1iGzxF7/5z+nZXPPp210Xi2iXZ1ly+wZep7b9P0b15HejjE3v/t3RvMvkaSrtCp3uJ3q+7tVfNoC27T7EG/sP7KKqcad");
        a("xG4UFdu+oiY0SORJCvKjdilEQPR4l+uGJvnmEz0LMlsBdCP6IpUzsAX3imPVed77kH4+6zbKEqvWbJFJiVafFMPbLFc/yUFk/O");
        a("6aGMBb9xOqD02+KZ4kRx0vj96x7MyH0TUIusRre37SRfqtAVl9F9789ZuE/THQvyl/yyPZg/yuEShX27ay9zNFmaW5wVGcdIKe");
        a("+aLMNAHPiIsOhmQc4E7xuZjHYzqQNf96ADa3IEf2oYTj8+arT1KNxbS0KFHoqk08Lh9GdK5DSGifSfqdiDyMX2FkTcQxTXQHEj");
        a("jLgNFHcj4sJ9ne93FxQRkYbp43X6d7rlO0l9W/HxYyyWvvmd4Ice2b+8BT9t6Pfj47BeuOY5VHPsSsJwR46kXMTX1Mqg3G9dT2");
        a("GS3uZ6vBXsTHcjI7AP482x394qb9/n22/f6v0Uqk2ptb6ipHW1C4oOqPT4+JMp8CdTu3OkwB/+OPoCHX8qRxr4kxnJioc/mZGB");
        a("2tFuJwfVLMW33zV1/EHb7xIQmvl+Whh6ZmYBnGBr7bdOWDg5UbdwUiTsm2DfE/y/Hs5KFdBeTFUcB2YzmTLpYPtlL8vhRG/7sm");
        a("Gl5Pd8zgTye6sl27K2GgM6FT7bM1ngKuPtD2h9XEiHDiKfF2RSu5kusbrXRTL+5owMN48nWN8PrKufPyGL7NvzQYQV7iom5LBp");
        a("Y6k/yaLsFOuRKgh7SE/+R5IbGGDaZkotlwsL/qr9sUjuiw77vviHVnTgq+MT74uXv2mgoxqvdMt42fnRdvwYNg6dAwBaKgLife");
        a("uIWIwx+8NZA3xBPwJtwJd2yG91+MG/i8UbvVfGKgVWsNXqCbj9sI/mpd04XFA/qnvMuAXd9eLar53QWK+cXtzaQvhl2JXxsuFr");
        a("WsUm/PJqhga139npb5iQW5L3Y3U7vwcL5Z+U4kn7CY/xbkt8kZunENKdEr9R120PoFajYY/eLhoVYKp2e4x2xZN7hRunDiIXeG");
        a("vV7v+mNxr2O79UrQhROdBjf1539RACL7iHz8QT2T1qj7NyLMYlXIx1RDvujBVzjozWY+NsQYHOag2dct1+jemdI188b0CnDXMR");
        a("wlyEMBchzAWYlAn9y+g37lg5PvRBRYQRAU8gUIPiOM0Fi/IWP712e0nQzmwpIS2VDlyIpEo7YkLHwCheVao/78ulI/zsRHs8lA");
        a("Rgo6crJB6mv2D5DBjUQUe/qTxkBpRIJ0drewaREulQKJF+UZx/RGC0nw0aFK+Jin/V2q/ApI14Ea2di2dp/MLuZsmkznMa5hwV");
        a("xTuCNhPKTVThsBk1lZOjQwNp1EJ5mKJcHDUoMICi0HZJ8HjPi3D5m8DCkk1/j/Vx3sXZ00/6h35ilOcCoauGx/Q/r2cpyDZ1eg");
        a("q9nefs6nqK0Cv0Ex+cMfOb/Bbgo+FuKz+mMFgpFJ9/RMVVxcEf/Zjzy/EqjV/Mi7mnP5tG5iB9NEpoC6d10yh5pyJtKhG/ySiL");
        a("X0vZiShbHDrbp6wQOFsNR/nBpIIX4IW2266WZ32/HDYw0X7eqf310AT7T4lG+3lRPV/9OplahLnW+nSwquj8eAh90lI3NvSw8S");
        a("rMsXyo+/BbkMxHYJyAhnIO34ukCJosrbZ6/kgnN024p3byhXHyAV6N70RO7LucuQBD+KUotIlXZJf+8uhQp4SE5zkfudngFVaw");
        a("2qn90yy1Gf/8Z+SHHIK2Nia/zP2mcEmj3T9FbQIHx+/lI+0YPsU6hvCRZIivAR8nZrONeT6ycHqm1jlYT8fHCE6X7JBR8CSQgE");
        a("/8392/wvHuXyOhKa2Ng9doebfK5BqkWq64mBWNlcEWz3GO2KuXR1xM8mxXr3DCu9fT69Doip/a7159+WeOPT3FfJGZJT2iDRFW");
        a("fQJ7fSA6eoFSrg81BPcq0bU4dVjX6yV4GEokb9A7qq/1ukr4wwu0W9atkD3gyjemINTnulUx+q7YYXrzUjulc11MVoeMzltXNd");
        a("1WR3CdrYrLG0Ymx+TLEe8Otoz6i2E5STUoPSHIM4D+Br3bg80xRdxCP6QI6fJK0SHTjANvh6ldTE00PA9lIKdQ/YS+d1+jU4pb");
        a("EBCe/DCgdn8qtMxrLaJvlvXTSWb0j0eExML48utkfiyx/EgnzSXxa4rqgetUZlCyPo0jYoVt+E1KRak546VS24xL9li3nm6dvB");
        a("MTFnQ2q2Q1Z/DKhgk6udW7jSaoXMqdbHMyGS7zboNISWy5Uh7wbcR342H+NkYURI0vkd2jrL5q4r/ndvsbZqBjGVc+43Sw/YSf");
        a("ybth4SLLUok7vkv5jRvnZQ0LWA41C7MLrswSWAj8KpnUvjdfwyKc2Kaljv/s7b1RlMPnp8gLMDwExhersdfVCyfrqAwJWP+k0D");
        a("5BR638qOxzaCk/XON8qMn+2+/jszBgCe7b6l0+8KZ4l284S7zLb1BC1RrXtNkR7/4NIClCW+GZUj9jivqaWj9jqvo6vn7G8eor");
        a("p35Gjv4V8niCMzxiTZX6JCfLp31wSDSqshQFZxTJLJkMSz2EZWVEFkecCuMWtrs/6WHL/Tvm1f/NiWpRmd4ja286CfbdMNrPOp");
        a("neegSO1hae5FTcKvN7+kn8nn4Szp4IC9mTHCF74lE3NUXf+T6LkR78Bmb4aC/fYFxSdpNss9SvA4zi83GIW9avq+/ox343+jJ1");
        a("uBQLmjjchq12+t4IfGUaD7zrwP0OeBvQjPtdd4xcEBxTWjc3davO6wn8HGMjuAVzC16JsYf6Ou5VmhQh4hJUoR9LAtHimtdtum");
        a("5BKJL444eL090Q2EX9tvol1A0PmDx5Q8j0ST3JLzcC45mgAuNxGwL0oYDC/Pjyd7N6E8vffePi8WvXvnDZx89+nqVtMai2Rfy2");
        a("q+3xWSI4QDJjl1DbircT+4rcU1QijwCw3FYAcjmWenj3Rx2cl6SsYuoAmWi3Fshycnyntf5sEd9hjc/SV8tLOqHZRIEE48v+xo");
        a("b140+kzcRwaiKMMISzzG7dbh2cDH/IexEp3brdY3XrZj8v8V1XuzgiWEFy5Wxo430H1USGy6h/tOpRqFCtqPj9+XiovT92+X8r");
        a("L21aNJIM/WO1XmiFVE2Ghls8tpk6FzILjNC+ObZoII01m6U9eIRQkZLfN42I3b/eQcLPxKgIQ6J3AUZisswr3LkOMcOmbQ1HBs");
        a("p9eZ0zhgJ0UP861DI3969H5lT9iy0aSHtJ9S+f+tdxkP07Y4jkZ52MgOrowe7Pdj4Wy8Pc+xW0laFORHwhTJqVJ+R6YSZLLstn");
        a("41+CVhG/pyC4Hj7tKqeWlL2lUhm4DlzJxda90zLTFZk2PKlu4MnmsXDxXCfY3a33D0At58eOfyPU3YGpUF53YEt8PDNfK1T9bb");
        a("TC3qEurDPfYyTzSF1KVFn7fK0cdLD86fg4mWHDyWTgpL/scyNe8d+K89ZXjjXHIw50QI6PuAz8Gzi7gWfkOMbO+Lzs2PP8iXZ1");
        a("dPt5/YgzE6xPde+ik1fB37McSuf0QIvQOR0KP9Os21e0gi/UYpsboBPvlYpDEbf9P57Rb/v+YLz2l8r2G1pk+4TfNl5Ktw7HLT");
        a("Y4bPef2f2PF/Rn8HKdiP80NT3BfdbOf+JrT2ACdY+qh10xdAEzOghH34Lobwj8rt1C54Cb/+RHrp4VGwrRlL7xY993Svtz2Hqb");
        a("OgcFvW/5yua+5UvGDo/sxKn5HvZEGMbfjZbi7nyDmuOOYh9/S9095TUq6N3sK0O809uxbGk7hu/MpSkbL2uHiP7mfO9bFfdSIt");
        a("cyqRlBUqDWG1qBhmwzQTaPZAbAfnmGJDB/FLLZ2m5PkdtBPuCoUA74O2zLkLP8Xm379vno/SHBfGRkTU7iRrXLz5Vi6yvTEumz");
        a("SnxT8xPLb/gZNwaNTopC9U3HJrHiJjUhafElkIbXT2FQ7aQ4457fxvOy3leG/8ksjo6SGZX2SsagEqnm0o5xxfSsjzM9b3FFTm");
        a("/7sqVtPD2pGy9rKw9jLvO96yvCnIoKMD9iHvUGB9sbfPwkMQt+yF2L4zDG7PseaqWRAcFGka6zioFXjTDpTZZhikIljX6ky4Nk");
        a("t+b63uEwT+chtUsbHa4Hmnn4ZnwudOJRP32OgFaFaTArJtnAGn6SQFQbIjFefH9A4tAghSA2/Ph4Z+LzdlWKSk98vlZocNHO/+");
        a("xcrZa46ehrrSeMOD+G33Qk1vb0yJ5mRWXgVEXOLPeLPCT0e7+G+YCoIqUnNAlSOqOJSakWtt1c3brs/+1oNyH/Z0PuQdPzWCCe");
        a("ahRAvNKYiJ5nJh08PS9U9DyB//8keX4LJP1X91X7Oc4LxvVhfsjCq0MXg9/1kqTn8p2yErqxCR4pO4trdzhLyFVx7X/yXvg6hd");
        a("246Orv4x+9jH4meiyc/xG/NLgLgsyNojJ/pjIleT9Uh3S+4otYcv3egO30ODwxycHD/gGcI+O7Bt88+q38Dfz9xiE3qS8cieeD");
        a("34dXYz7WYD4aMR9tmA/MS6DDUE3t64nY2Hwx5AGM002Z1O/nuN8VRTyg0i5MllMNqKJ/UEA/QnfOlV5Cw4xYfXyXvsRwz9Wozl");
        a("Xb+Nytibneo+V87XDQcY0ciw0sgUPfHnqF7euB+B7zhJvmu1PNt66sOJWosIRce3MH6ukLvo/W9jf/W8X8Q8bCmH8qg3fhWP23");
        a("owQOrAYOrAEONKJjbcAB4ALmQOKA/f0/2nuw/j7JFslgLjQwAsEaDuLo4EcNAExHerUf3BnfBehsf6i4NNhORrZKQmOd6qs4NN");
        a("+JSS0oLQhuLG76NMWLF5Kmz1JmhUa6ZwVHcsr4TRDAR0IT0pHgT0/HI6wbSeeWwmoxVeIPjk2inN7xjcX4PCc435EeK5IRWkbP");
        a("POylJsl1RyPkKYMYJl8p1LlQKUAaqL4A0kDU7rODNBwgDUSKDaSBBNJwKgSQmpFGIA0HrsYHSfELQ6Xzl+2lk0TlOF/Zm8XBth");
        a("LUCRsBxbARUOJsK4aNAL+zKfhmZBj5pGPR/EgylD90JcEcq/RHXH33DRBurV+fXzXP1UC+FmtevYXyVJSGzpp3eVHDwmhB3gHX");
        a("bdc66Bnl0qhxrw+dXxrsoOElj5CY83mQrjdH+pPStRS8HRbkfVT9DunjnmCqr3Ii1RWOV48b9VCZz1EmWDUPNHp7wMXP5rhpyS");
        a("5Zdejt9nTxDA/VSfEgny0esENAgho2kQyVUVoXec2BYf6G06sPEAGAh9zISGHWIeT2h9ZSRo4lRYKyd3GYIrGM6ry8j10rh7Ld");
        a("oW5Ihof8lJ8c77dDaGgluZuB8AGnwUdTtJ18WH/CewkKbCsKflUQ/LEoGCku+6G47G14f/JNatKq/YDF2Q3vSWW6XUnT/Tyvt2");
        a("os1bc3tq3qAZHPwEOOOPM+XpoRB5bq7QxHR3Avw5G3O5ACDZDUvO9dRbvz9lX/mXoFfCerSnntlaMxL2dhEDBWRcHPuCaNzF9J");
        a("h0UwL43Bqh4Dl0JOzoS9SmSq7WVa04epXhs+4z2a8dKndK9s8ihtuskCEwshF/lDhfPyC+cLvY9t+0nvY97SE6H3Mc/Q+8A2Mb");
        a("sUXwKZ5iSR6HppsJs/nU3a6H/0EqzIpEe2kr6kQ8UwCoIDqN22l3C5sBR6Ik1fYrH+Iie9tnCesQnkJMVKwKj3AEj9uZBrLWti");
        a("pKVhJgeJt/C9qUozyfp+MBHP6QtgrwTgLoQMSHT2AkPpgK5qnwxh/OnrPSFwCDRAFzlAhkO+JbiSKn3zePzPNmHbAugkpAgeAl");
        a("zc0yjenDCAI3NpM7ltT2/UzKEJFcyPpZcF88zfNv05P2pV4wekzElXT/7x/RlqS4R8Fnkl8laxRgV1flMH3UeE4NEu0DeFEeYW");
        a("MG85NG+be3qjtklSnNt44yGln+Rw3Nl3E8HKnPT4dSt/XExL66JLR/DBLOP+avQDwzm0g3TTq+a7VpL1A3jfpS1A2kufTXzByg");
        a("lZmEdRaKEs1PEBpVSVOipgAXY+kJPetWeVwpEokzPN14a+lp01H9VJt12CAiDdyT67RoMXTyVAvbTIebxOpdmas0qDVfO7qgEL");
        a("AdVwPQI4ZU2qrZrnqHKjbbyEAmU/2MNlqAou72s9q5QN1niBPZxJew5ZYrpThnwCylZekAzpH1sFINqjnKpACAu6YZ8f83oaZp");
        a("6PyBD7FKDwQegvsrEOLXmPrjK4Z1v/ujrx5XVrX6LDQlLFQFAkumBHCkj+03yFKE1iueFJoVriKuCEdzI5zE+LZEam0N06I/VG");
        a("A793vkf4De6+kLHq0HduD6qgkVLQ2MeHCcA8R8WksIVUGt0diUtpvAGLow95n9OM+VaiiLqW/NgbrXlJHA1Gi1S10R+W4uR3z1");
        a("/Ncyaky5zdbV64I3O0qtdZL88Mp+q1fb0Wmdbr50gENpI+p6NP+ENDktSXPwR4CQDUAbTFmiMIZ4VSsHGnmCBDrUgGArvJCQCW");
        a("PmqQZmCDqM8gmThg5PZFF+LrC/+tnobw0NrepIBLLzgPBem8Q2mq94n0meeRPnPGYTcJAYQnoPSk7POZ91PdA3PNq8t56k4g2j");
        a("K/XTyAsVTBHu3+p4wHsEIZdc6TRhRbtzg2tsTvVYnhMupeowTQ2Lw7Hsz93B9c2s0W/N8nSzLenCjd6FrZqx3VPf8Hvu+QXcfi");
        a("G5UG2P1AWq3baTVgdcn9tKZgPVJcEQ2+PkxFRi7jcxXMivLhq9lHZ/quM33ON30b94L9RjT2Anke9YdSkvDMrFfiE7I+RF0G4L");
        a("hU5oyMwc+voogIuApedNa+RovdWeTyfh725n2x9PfUhaJgVlF9T1+3cH9oqfUmZKc3vKH+9HsegOGCyGh/AJeOSc+R8UlPYFAr");
        a("olk+JmynIvb9V5K0AG2+bdcbxGkutIoiU4Qy7rwq7E/dSH98CadruUiMedbx2JE/Pv28aZ6zqqDmpTpKCQzUyenxlsMeBteDBr");
        a("4T5PrQH2IpmYlOx9qnoCuVXNFOdRfz4XYmSatdhUCVBxI9g0HTcy5IsuaM//77bfz7MvyWHGB9SI1/nu/Gj1WhApvPBKH92EPa");
        a("j+/CU5UMU9U0CTU4i0Xc9AXoICfh0L5Be1Yjhp3E/hzM34OpAjSl3cKV1SFsVaUEBqY6rlMLyU8mpW+NOuRCekAupIzfSVbmEL");
        a("62i36EuB8Sbe32bEge6ICSUounb3CQ+rh23dv38JdVbp88oNsiaD6qP1sE/55+6s/XkT/CKX3rp047JpF+6hPXqkH99reo6oRe");
        a("h0U/tRHKbNw/pW972QPIOY+9DxWywjrLILe++j/Qt72jGVV/NLnv/vzs6ET92bpI9Wcc9efnB6z96f6t7E/FeAP0HY3Cfoahb5");
        a("uu7dnn6EffVvEjpK/D2ipUOh2qtumGqu1UVrUNCz3bdMjLz3iZoHgd+gksOLGTBOKhGMOKth9a+QT28yX47MTCGm4yrNTmULe2");
        a("k/Yrn54J1n+kr/XfKOZHE/PT3Wtf/1itcul3TDCWPqaOVv4tOHEplXQhClDMDEQJqoZIIZh8gkEGGlJ/Cy69kMBLU9aUvlPWlB");
        a("4A+dD5gyfKhS0mpdtu6S3++0C3X5j87ha23ohxrRlC2hrbetPYBIyU0e6krUhm7CHLohQnBa81ofPaTTqv6hlB6byyzKnlkYHl");
        a("rk+9B6N2yFTMkMVg8r9rn7Jq3H9hzyS02B05XNiZvD6dLUy2eqCpII0EJO9BAAeEj/uyZ4IVI5zdXXgEmyp08+hA7V6hnWqPbb");
        a("Ff646kUFtKL1yKWTh4599ugdlGn4Hufh/bF/L68n1uLE8Y50Cr7sgkXwh/B/Cd7zz0BarJ5L/El9cU+MaXf056gPit6aSy7MVR");
        a("cIB2yxEWO0Dx1sfQzv2w6vzSc2qJ2OwdSdY/8j5Heas5b+3p9+CDkSK1ASEfaAChc8TF8Hm7EZFOEQJDhskoNxuR+0TH78UC75");
        a("RUvh0+D7V5WAL4tFmoRNrJC1yLEsOoxId/E1B27wOUgWNhr/D2F0z2KP/mxE/qI/jhmSGfJK3IybAPfQUhTWzLzch8PgpqNx4m");
        a("GpErxM3gz7GAH5/+/HFXAvn9oQuosfy/9d0/5JlOeQZQHmmWfOihFFOnv97eFpWBhy1Pevb3xVT3XpqsjIH8cwhThRzVtEVeHQ");
        a("P4NDLChGMYRd/bw0U37FFFs8P6kGaSRaUTeZSatHwdoJsVQPHtsaGFEm5hHNubatSre4OqewQJVN0OED47P1KREFaEeWwUMrNq");
        a("VoHr7vUVk7Fas3E5ID7IbYfGJLmRlCmkg7QmeLANQxY15kbEvkp9+ee6A8mrnety1OnIZH+F4w7eHmWmtvtQnbfx1KH98zYS6X");
        a("9+0RtFIEdLRcA6dYnlG/k24QdS+YNvaKOonhDqGUr1sM5QYCIWStUzaqHcvJoXyk2r1UIpzBT9+WCvvkBKUECbl8m2d0gNic8Y");
        a("GkTh8ZGNjxxtK30E6ZFwsVg5x9LK6VNCEO/7tTdmMs+UDEHPcAM3+tCv0dqFqbjICcjD+sq8QbcyrcbnRlb6wt4BkEGNCRtW7h");
        a("V6271E+d/C8PP6AXLAlEDcC5LFfuPKz3v7knew2VuVYiz/rt3VMOWUpCYTv93c8Ml/YMG2dt3Yu7YckEh50v+NHfNayJpplxzd");
        a("tz37EMCYmplIHqRNu/qz3gT6SGabAG2GTQAaOC1VHHI7DBHsLuKJyP1EKgp0aJfu5xm9lCN5ozwGwQ6kZ3HzLmEzoE0UwiidgQ");
        a("MV7Q3HoePtjsAoHbdX3gm0XTaK22wP03Bw9759lnk13NaI/QIilOyApbwKDDNXgnMeiWd73MLSauVpOJbH6CNprI+kBZu5yunP");
        a("8FIRI+3LJCQcCNZf/sVa9WYR6cnkrgTyZK/S4Ztfh/PLO8hfe4aySGJY8kAE/FMIeA179i0wosgf5fhY4d3B3A21tuLbAwxk6q");
        a("0todZ+lSEuO6hXt3bT8Uzf9lMVPVYk0GUmgahH3EFOp2sHj9uSKNQMpgqjBGSPgNrB45bmXi3HaugpPyQ5aKg+hrwgzBHgvsA3");
        a("lZ98qx8ML8tIREclMbf3F1ThpX2MQ4PF+Yz3l9XKpp08/4EG5QgDY0NgCyxHu/5pm3Ux+U/kTMY1GgQrB/RqXWJ7Ejmm8U7/Dc");
        a("Z73wge7xwx3jk03gt5HC6xWIuw3Y82ag8Ukl/EQo8OlDW9WqTPiJveOoMNtzNMFROobe2dZ/lymMP4OJyjpo9k+305cez32ez/");
        a("5ToqpqBQrnarqCeX6xnDUT+M4HrYce+ystIobetx62P3tR3iPuaplyY48kgSaG0U6eIx44WveqPsC4C3I98oYV9lhGf5LhyKg/");
        a("AkIP2+Mj48wj5/MYyW87brxVKPtmOJYiTZ02dob/WV3gDzrdGPeqMy0WbvQspz446cSbAlh5Z4ykOzPcGe8rxmWFupL2IbFldF");
        a("DRsW85UNi9G9IjYXsdKGheAZnI9TK7+1t1PnQoUzmr51L4O0Oz7yelStp8at9dMDfdSaJWu1W8aw84fQdT3COl5EhrUd29SQ2P");
        a("1RcPnaBaW968zfvlJnHP9whrV/eGwPDJHL9f40QChQ9vwwoSoRlKKGBVH4vdbtqeYQLVn/BNlZ7+FiTYaSSgGkwahUUf4Cd6Vb");
        a("lhey/1h/EP9f+pqZgNj5Z29PV+hghxf4uhzHx9qZpbgJ1paVfisgPZMuZadRa4r8TRbkj/1I6jC7ATOg9dTzWeWhqKB9H8XQNL");
        a("O8Pcvaq2MwkY4SrVfYB38OsBFtvABTjBcnyAQ/ezffQs8yWbMPWE3iH32pOv89WwvqNDTisFiz/z1c3hh65odjXfIalKYL4VzB");
        a("0NWLZFCiziYGfRbR+AIwGdhd5ZT+BUFW09McCPGxUInBSfzwrIvFn9ds95VVdv0Q5WKBBuKX8jEkdCe/+/3Ul39JZmCu9BHxQr");
        a("Lg4XiNwUEV1sF5Y74anLtvpveNLuvgVC2n8+XuwlIW3z1KDNJYGiA208HoqMaI6FMC5RDLfamdHJ1AyIjKLrIxyvvi916nMv43");
        a("/KIlNn5Rq2eO02ByDaVirGbrx0jGspA8gg2QZ2MhhRPykL6vxXl0w2EWHpLt/YxsebiEroc6b2CbL2KfxVW5gP0qxJKrbiw+oU");
        a("Jynng7zwWT8v7HWI1Xfl3zZ8ZKqPAKtnZxKGUCCIesShwIDqGq8ue5qwZynfPcLOsDTZanfA2zcrUnv3Rw+xUOLpTJYzMrk9bm");
        a("M/xmwVZG7tGEAbzhJLY6RICv+FqK/Kj53O0pJd5VYDBdii9HyMjbF/8+MonkXHkTb/0LX5Tkgtuzm/mYjMrc7gJxLtmszhtx71");
        a("tAawbBA/qWLijbyQZRYw+eR7P+kaG/hKYh+XHanwRj4FkkwTvnl+VhddOync+26FPZCOhYEVBFtSNqtTOSlNOoAFXyZaZqilvX");
        a("OyT5KxVwFodjqW9eATj40q9wi6C/mKDwo8yxLg6VMC392e1EKQKpJcEdkfSivN3V7wrfO1YTkWgqsT24DpxipTfjtxFU0MfZX3");
        a("Ma+6lPjcdClnWfJd8YIRmvaKy0b/sjTzQo65/QrE6VhgoJEcR6IHJh95SpwOujP2JtGPlRHQh60S6DoN+HZkUbiN/WQyJ8ZxlA");
        a("vqEDGZ+DG+99HMvIx1bYZvmEexwsPB+TiVwsZrdymeMeSoblY5+4/w38arPjVwJ4RtvhcQOcwxQ4TW6+aRDRSH0pyeOoiYXMQq");
        a("9rl6BSN5vICXlE99IjqWuiynNtv/ndtvz281q884N2K2dsoZuycnaDDNYd8Na5ageM3EDvnZ9Zd8DKpTT53byN96PPPXTVF+Bk");
        a("/uMpxVIwv8/GeT8rIfbnr6jQ9SgEZOTXgn/+ic06f+MEAfqZdCdAejw7+B6+g/3raBOZCHnZEaC8RzTQ1U+7cmsvve1QmFVwhg");
        a("zmSXML/yXkLFCbtJRkxAI72C4ImJY924lpeZ5kx0bwBQbpdvzwzvY4xgfN4kUsh30YDH1TZFjHP0rVpVu0MIZaaK0kWmRWYu3S");
        a("3mWIkM3yMJdgPEtpaG560jKedv8NBPYEynv8k2oYH35UDeN5PIwMk2UcP3qEQPGkMy//KHSQIttEZKbq1Tjq1Xc3yXF7ydq3sy");
        a("Xz3OKswdqfGgyZ9sQTqj92+zZL29GnDhzWuF90nkE5H5Wb/YTqW+sjqm+zYvk1tTAzhPMXO1HDGYb3R7DScQTAKsaSIrlHYquY");
        a("/NmMEf3SqtehX0gTfSqM6iwXsJ8AFsT4UafqWrz7zbQo0OnvnxM6zQTcqxHSDqPBLOv2AVofwD2RLMuhHfouDm4L7kS+eso3W0");
        a("AZbZc2yQaUh5G/+gM+//TYCI2dPyzHCxAcQxAEDl8jV/a5y4TtkAcfZo/pg5BabvD6JzyIZX92stAyQi7xujnDLbmW3fRIFgb6");
        a("sDhiWrn06zH09c8AsgcV5nt3VE0QC+MZirvtIUKFUi7PLLsnBvACbEWGMGUYLAudKQpVUtwFXGhup3hVmuEOHCkSL6XE05CoQ8");
        a("JKVTcC2sgkUvEQufIo1y8eQ+RITr8Q6dpy8jR/KVnd6FvftkObmpxIP7VHnqe1Fjhb4qMtfNqHLhOUh85rTFsHOVhFjY3B6KP6");
        a("/kqMasFdguMYFsdgN7c4PYhIRswkRrMro4DbTWxLNAJ5CDc+neEV3u12fbbUrE5+tBnT2f97j8IHlNM+4XKf4kc99gj1STdg4P");
        a("fZeu8O+f7EaC9bt9ozueStft7fOwV91/AjAHjXIBSenF3gt2HR3UeSdHntFeX4JKKLGe+uuFDcQdnfNXuqdEcgCin8ywAkTz3b");
        a("3pIenUenonoRxkmHIVv/Hr+AsCodbPWQasw0JHvSW7hbJ3MdSBUQhgChnfja7eFeDUF21r+6TARg3xwBNQI2ewasb6XUxFHgJh");
        a("Swq1rhlkXXj12Rn5K7iHPoDeN7Fh8ArFcIpXcBwZDl+JCErBOSWXJbzxSGRjLm0vLN7mAmgc9V9L2ouSKV3kOe5J5PjuobONOw");
        a("OPRL6XFguqReRqm0l/D3qUQhR2/rpYY9LzpQbQlnqVmCi97RltzA/O2rANAx2ISDLXWNHtfdrZhXFMqUOT1KG4+Ux6A1UrahuL");
        a("bLaVRknEe1E7aQ1PUusMP4wJJyhsjT4OsRz/ifASizVLN9fbdpsyz6Vbb3j2g/7xuKBvjxrNHi3eaQasfSOjfw2FW3X/eEdPtv");
        a("6MDK547lCOatc9V16mmLEYGJG4w6nF1vIlbR4HlBjNk6iHIRGTBiZ1DsUyK2LezHfPuFSax2mtb1gup0Sr3QNopbLdpqpwcNPp");
        a("pw4wxQG+LaVFzeusBJtpx4shW5QGcRz/bFD39YPLikEdiRFPwVu+PduhWaJ/llYpv9ZSIuvX31QOL3NqkPi7HWaJxj/Xqfe73Q");
        a("iX3jfHwSXZVMig56MzIEiqTnOo03MUwSt3v0xfKkcpXlEVvaHy8/nbfG6JYkno+t2FBqoYuP8FgKP5YPovNQGEid+iSy6AB5QM");
        a("i0yQ9y0ZO2ctGLKfsSyp78IGW/mGpsTb0D0fhHwd9yUPVq369RyZeruJJLt6rKP6f4DSL+bFGLf0ts0UWb+UX3CvzIvmP/Zleq");
        a("y5o7RZavPwQ82fuoKxmf4nqIqEMxjNrru23W1+znz3a0p+17UG2RlnQPpW+1pSt5FGo8cppKt79nBzTsT74PeX8q+NC0P6H6c6");
        a("n6+Q+qY1/779Sx75hW73469oW8+/k8u1+cZ4f+TjAn5qLL+YH91Z0868cgIyp0UIX5SEH4hw46L4twD4WPQnjaJphmPfYTHqY7");
        a("CfgNeVQw9cWNScxOHBQauh/R0fbIAIz7HoY3Xevay7XcQQU+QhiFz6LwtZt5Cn6J2rEbPbynl06HIaRAHgj5b6Y8B+5TvtNy0C");
        a("WD32LqF/KeT3mn3Sf6diPayL94f/U2JJxOCQsRAfQ4DmH6R+9tFH2uiD5aRrekXgp3D046b1J/k1epgb3hPjWwUwkKdZ62jO9T");
        a("9woYftxjHt+vDvCQ5VC9N73LEDz3AY2vmPuhH9DHQ7kk4ozR/DO+hPyNJwfI/Tt8yqfSbPDDmHqWZ9GdD8NViURH4E10oxoh48");
        a("Jxj2jwaap37e96ibAOw/c19H1oLpW1XFit/rbV+4RExn/w8xPvPsQU0+bcJV6ksumhUvf3gzjehMpJ98/1Gv5MdNSQsf3migHl");
        a("y7CYotWcVI7b4tH4UzGw/LhodYUrMiZsXMMjF4fLEc6ODojMxt5YvuzKCdmO6aLY6W9HK8eiXPVon+s1euTZ/aHv3SvRHhVA0S");
        a("yVLa389N4BVYN2utypH0I2PzLPt/EXyBh2vYgc9CqUA8H6k2QAVeDd048gb6ED2zgeTxtjEIHYtXIwanwD2zmpHLOGH9Qu2Doe");
        a("ucEqfoUcriYxNIZZh3JqgGDIktTFnj/FnD+kcvZVf7o5v89agIJtMfkHqfz68+JoXegod11c+Y6ig/Kn2xjPn+6QXcAJPs2Rz+");
        a("8XihRD5c1fgM6+0u6wqExMvpLv4dSGvGul+4QdOMiHGL7/y+Z2a8vGWiVBCgQH/jSbJEh5OKEsyA1oUzsvRcmCHLz8Zmj2WLz6");
        a("TAIRomXglM5tFOODj+zD4JwNsEMK82tm8POT/h2Qqe1f1tM+307z/LleUPNteZ+WsrrQhBqrLECa/BfMs7+p2O1fvZbg/JdIf0");
        a("a9RUHDJMcQMhZ+5mVf+fl1Qvf0blfdsyqWYS6obykvCnaXT3Q5XK81BtP+zD47loes2YKtUJO/biZ02mx+RTibNNX6amNwQOTc");
        a("sNkGAKpF3OlGnAdxiCC9nnMLFZJO/DmQNHODFUlvuYJoqNk63gJhTZTMoWodr/ZG46d1aS8jTdgfwJrrXxvHNF8L1Hwlsues/W");
        a("Uf+2oqa5d8Xunv9ny4vYsx/WGX/1LnvRaLLzhr+lnUBjDve5fbTm+Qfj7SfVc0uo+hgVM5jPRTkM4JO80V2Nabl152/8hWObUB");
        a("FWPZ0dwv6a9reYWITak4q6uBOqO2J545ZfsO0VKIfTzdNEOjinjNmEciMkv3Q/MnkpMa8wrUXPINnzPnUVwqxSnfNJdQ3Hcv95");
        a("p909xBcR8hLvzvrN9X447fCziKAkNVgnV+9iSen/fjp9vt37+U6L7BsmvGnVznckm5AzY3jyxkgjiUxAbIJKIjwbjDInBpWNBy");
        a("kJNrdhGveFttTzQwGQtNO1MttJcvweg9/aZ1oR1ejo64XvBucz3h3RFZwYefKwaQv1UkaA/09korjBP5qLvDTLrj2/PEotyvP+");
        a("ukmDEllDp+itOR791fQTTg6zMUaOMJtJE20G6+jGmAMiFifz+Sk74uMV4G26DOSnrX14+Ki54Kn8z8CH6dKajtGXJdJtbBkIo0");
        a("/E0PDGH7u7vp0BwBL0A0oredZtozgusAe9m70md1GkCo21qFt97RwsKgkdnKQbDTm9OotVbxBPTNe6gnyv0AtSnY2Gnph53i2O");
        a("v75y5V36vbEtXXv33hDWt6haGg1xHo194mAWFzEINT4InCVxxGjd+FoNHEWCRB0cT5Yli54e/OlxK7x+H0rZm3rQje0JWh5hqn");
        a("Lfdn5twGftORPaE9W/iverE3gf1UzazjELY54K/tyXDd6sBOO74RaB7B25evLOVmqKJGccbr+krYk46Mlv00l653WiJkP7bY2u");
        a("gC+0suLPTqaSdiVjqZ15Mk435Pcb8WcSky7laKKxdxg2TcYoo7j+Okwftu2ZKkQzz/L/RGu44zvFXf/gM/mG5hfSMqf4hRXlss");
        a("0zZ+RqkZlPqDzpu6gNKaRco+6sUWh1GO2BWSX9V1vbLfZxsOnFFqe5IXj7AOHMoZ4/EAIccwY3y1aB/j+pllXOPmctUtAdLYck");
        a("bGq3E9nr6Hh9emy+9xUs6C9SsOMMbJC6JVXhH7t7RsE8lneJftc6DWqgGv0k8kKfix5Fj6eqCryxdFxeHUN79J30M1rkVILKaw");
        a("xGIC+6o4X/YYInLFwR+67hLn78Kx+YVZlXNICL6IRY1msrtkZfmwMKtO6GvvyVM0vQSnX82z3krT/3op0fTWwrG8QnCwx/F+Uj");
        a("u0tWHWu8jl/5ZagXPrsWSIoKTsdUhTaTPreqNFzl+NhWWaYuf6oknf1kVdt5fHcSCGw12PZTDt9o+fO1h7V/bDJ+v//U3p6dXr");
        a("lq7ui2vpKr7+zt8Oun2J5zYwzAblEKj+WPBD+4Lhv7hvtsW7b/7hK/N986xT1JRfdD75e222TvlLF/N9sygkFJ7U1VAiqWh8Rr");
        a("a8epZqa4+2XjtniKtlvu3aiXoT3jujaFvb8mOv/d558Po2DTNLtbMBEhA/W6tC76W+KEJ0u3TR7XKQcbtUZwkbvwJdu32pEmaN");
        a("l16dID3kwxB5sg0+s5m/QfbHlAu5DwOYiLu+tZ5Q4+JboEd3BIuaC7MN8pbXzce0P52s5vfXc1DtVY3W+d0KhZHIcKMCRTBRfS");
        a("igVmR8/ZfVvcIQbN3qBOcHm/85MVsrvDnFIebkFQsFwZ7ikJMwuDhUQJ+aVP5QZmOFeKDfF7qT7ERww1OuIFhJzHMnQzSWv0va");
        a("ycZQ6Svi8eEwJvIw10TKMyEqKszm5tBdU/F3lU+2+PIHaO0Mk5qVfTwe16qe5vFYrQUQSDgeNRiPVfY3C9qkk+jBhnU68Zwj33");
        a("6RC9DxKDViOB6Xw7JaH5Y2rPdJ+u0ixGY0GI6hC3kqV9PSZQj3X87j8zh5MMt5WQgH133rkCWBpOLXo1CJXLqVxk2tlw221LgR");
        a("cgLfTjlR4dsgHD60A69Y8e1XFwLfxtjqk3hnlF5+NfnT/6xXvICr7CAS5iKOWBB45pEnOreNJ/YMMBEEYapX0573sn4rOpOTMQ");
        a("9qvu3rVR0YlHvZuPK4Ldi/QfHW0Oom37mh+nZ+eeSH0Wa56leo++zB+RcPWgSU/vGCSUBpmDaW1elAmwk+7w7w05Y4Ko7wNSAY");
        a("5PNEqMaQ1/LyIpi9pGbpDkfFsSS/NU1N109LMeCzXrZO18sXJBA7stt/xXMeACVjGtp1IqwR0OUI25eDuf8eYX9jEf+8skSY4a");
        a("jBjz/4aD3/PBXmn+fv5Z9XVh0gAN9SVknrqFUZpEZ5aRTDYJcHa+MATqElIYSLQze4u7YlGedQP+uCn5/N4J8JMItrX57Ay6si");
        a("szh024TVPHpe8upTEL2LU4CNs9hZOUnlT+/UvQC6lr/PyHgWuzKtewPP85sisPQ1vOa0YYEj0AZJLOZA6JtN08KI6Dyct3E2Pi");
        a("NJL+WGsilKvUGlBtXkjXLVXeOMm6ad/5V4mG0oHON6YfYY3EHoNRyPmxhwkmpgjNHCt/aaxRzkXGx5jk4Ar/AQBX6hxPvsgPvI");
        a("dVKwjiYcY0rTL+xNNFAQ6NM2VaHP2Fm0Xtfq6POoRJ+lc7GNYfy6JRwkcb8HTUOiZ5Q+JPQErW2noaA7h8lciguF1hAqXf45da");
        a("SOwoCnUcCzRsKDBFFzXbtIaFOArhGARqcoQBeXANCzDEAbJKCbocDd9TfKoiZKe/o7APVHEZmtBBpQHnwhnHpp0qvxTfm165oY");
        a("iMfZRuWs7JK6Nypgj7S4hw6+k5rwl11AoSJ9In7zd+K3Ma7yWi0pe5tEGVRb+kpaiIz+EIf9PEvnA/0aqCHt8PUYXxZRZ+3mV1");
        a("+jtbELZxpX3ZG0QEB8PFr2GnnS/QkrVIhFFhKLLCQWWUgsspBYZCGxyEK0yNRaT0wfJVGLm96j0s33F9bJkAcPtaEohKxNF6KR");
        a("VrHTRSa8e2QmpvOGNfp0viqnM3W27XYW976/VD9D2Q5AeM/sHRhIhYPRyEm1vekI5UQjRDTPMyHT0dT6mBetRPPW80haqIcBCC");
        a("01naOs9HLWY/3wIwIxdwppnVq7IMt6kYDB08BAivKsyGEon5ysoKw5B1AufsEK5afnShmMhYjwuF5oAbwW9WubP+rX/iN4jxmv");
        a("4E0Lefb7NmoK4vKQgHjvsQriDcWAuOl5K8TH6xDT/dH1oh1gO7+MOXVsGp4AOaKHzesUI147oYLuspHTFV9qFXCMUtZcxzSdOA");
        a("34Zpt1XpfIcmA/S+F0Ufg3g0XcG7tZQvEVvbEc2di1SE/kz5EZMhi3GbnSeybOx6Uk5Xev9v4W+34Zz97+sEd7hX+r1EdVgbj7");
        a("aylGDFNUo9jWVh9XDd52JkNSozhbOk0ljvB2jpOrmRl6MiKXzqMy7EZ4Daq7l6vTncvx+fIDJ2J2QwiSX6mS8KM0tkfL9mhHbd");
        a("dO3yCM5kfSdQajUDiqu8uhF1qzhnfnUwymIwoKdwml40kIqUlUWN8oPWfJgYY/xzV6dUAARWXYdQRdk+sliakHMl51tELGgrOJ");
        a("v/2cjoxNEhkfmgX8ifWLea90mUdzKnuYC9jfd8honOkpqet1EwPVgP6OLCZ2q2Uve7iXE2RnHE7d0BMyGB3q4Tu07FMJaqHCq6");
        a("OlxAdYhfFOkT5AvKuWQaeSbbFRQT8EJ5B5la+1mTkDSFfkFx8ozX0iRczIWGF9vUs7JJn1BdO0K9DxsFb4JTDO0Ad7j3WZZBnD");
        a("Dshc0h+/gWf0XuE/olmJq5S0et9SfiHhZ4PlRN4KNnPO5c1c7PHdnjnci9Ho9eO8Nn79awb5cWqpPJx/8Vssd61jV93vCfuklY");
        a("BYM0Uep4FCjKMVR8nBXSXmvd2PnGpwKQ/6B6y0+SNl2b8ZmwBHvlcLHEtpGGAHjYEar8+ccrzySmi8RnyhxiswXIiPk26AtsXP");
        a("eMRuPriOCpekBxrRA/RNjmsSd3/w9WoIj5VDiDym8eNsRYFolMrQ+HzOddgdxYmKF5Qe4BLPBlTFk/S5CVvr3QGqSUW4XjHuSa");
        a("ax9OWCUdTGsEsKYXgZKdcpQGCoubNjZEEfFdKuWkz01dVA1h64vbEmqE41uuux9XdOBdsK0CvTTro2GgWMLP8e08JhSGC+kw7N");
        a("ACHyOojR7uL3MEvarfCfoP38cwTDQj9hr9x3qhAQIwhKypuO5D/p4q8QUtbue4kNlz+73MniXf/s6GWXm9fp4ie7PaJhN8hDj4");
        a("M2wcHaF5uJfrcmE17qjGCmwjmb9E9FgGsIbU5EKyxqKfnI3SqfTqQh9ByG0LJiacn9g3BBijjmSFTrBKrlYuuw+zeJZPD6FDxy");
        a("imrWcsEPZ5L1TjfvfJ/zx80pxraQwxREbRGdsY8pPZIe6u1jHHIEVnRTjwBKTmwBh4muRtwMj3QjffgeqadTJEDR+GN3sjzwDp");
        a("OKJYaKyhBOv/U73Qar3d8+M9fKXdWwJ6+OhWZuSGpgHO3u62BIKCY1nXDcNaklMpySaVXs9LmObaQo/O8Ol7uuQZ38XIipOPj2");
        a("XIFjbIdTFSHlaFzZMLfF9kbx8T2aRYQru8XeqIGvG3BpQfmzzE250ZSMVU2pCOnBA0+nGXRMmibcXUg5BmUOg3Ht4Ps3PJCduH");
        a("+Z+sC6qYPk72Vnn+Pa9/k++L79iN9a17inwgXZCbw8XYc/aXsqnD2Lx3DG4E68qFJ2g1ryKXVOljoYZON9XjvsCespNQgfu/aj");
        a("fwL+rQ0w8m1x80XiUaxiKEl1ZNGfEyPFYVNeJbzTEplvjpcCPBRfpOLlDojI44H76EpwnOrKxWegK6V/sXblVS/bdO+Ozw3uF5");
        a("9G9IdPPolPwwifPmU3Bo02RLLLbzDU2neajUUu00EYNlsS4/Ovr14j+dev3ycVNfzPyhfxhz9SJ+o45+kF4jx9L1BsFabxcYFi");
        a("sRxrpbDBNU77SNihW5MK8AOETAMOVzOwdTrJaz9unYHpRbqRybWZ1On9V/ILhlbRTVIA/BL991NNL9Hc0tlg2fAxBKOa/5EoMG");
        a("IUeBlH8hmQ8xSa8oyWeb7NQJ5ep253sfQAPety7okfqdxdEDro6pCXnP3EIZCZPKZM6ynT3/hNnPNtNuXr2aby/YHyhaQv5mi6");
        a("626RJcNU1Y3bBHTnEnQLZLsn7TPVZ8rsp/ryVbt/wI7tym7jfGFTvgmUb4TKl4J8ICdNnPFWU8Z9W5Hxn4Ytyin7TPkeMOV7l/");
        a("I16/kQ26nlHGJSxKk1dfoxynuPKW+HttwtrgKc91RT3puQN3KVzIf3jRGcr53zXWPKN5vyeZWcMEQPgX2NDnG2bQS23WjCtqtO");
        a("o/fdx3Rsa5HYtqHA8BP4rjC+gXM7EDwEBOelb9FvWnkPr5Y1Wp0IPK5VisC92mIRWKX9EgFjIan1g8Vu3EWVehPf7UwPHPfK31");
        a("VCxEq/m3KnzXfSbtOds5NSjAcBvoIGSN7eeWcS633o54eOtczL+90yvne1E3PibJoCrDXuwYPgyeP7Xr9QdOkAF6+kTBo45p61");
        a("P8oZVjEk/MKA9Xk3pmGSuZqLZDXmXO8jV9dOcf1k1i0AkA8b6doVG1BAv8aGOf/1GxnGNurKxGedND/rsJTpWPlEkn4HbMNZko");
        a("839+7gU0+bwTVuJHZuUk2uKzBCmEMDoVu8jatsjKTraIIkQDz0DuK9XNZGh9aA1xhw/Ma8+wCfJo1R+HR4Lgq5H7FSr6oz5d2m");
        a("keal61ZMut7L2rdtvQy3G72sGOUX9wjh5/6WZCz9JuRRUytvNOrBGKXN6U5Rcw3VbH5wd628ETE5m2gMq51iDC93qjF01V2IL8");
        a("FPiIAnPFPwIMOE5uL9aA80c8TRNJ38Pxai52lvM0v7EBFdmsVr4rs7xcP1GnPzgaONoneHiQscgkqkDMsmtNz7pdVwug5wBcG6");
        a("eVHmbRMbh22Hgw95qNOBhIUiYYGR4NUQu0TELlKxYfl8WI/prhGcjgaqDRPZOVpN5JxTSP/uYX0iA3Iinz8doJa+D9w+IxzJRz");
        a("W8FGvW8ExUHC7qL02PcXOoCTO3qVruGSg8ggo7BW15UYjPqvvTGro/5da/63mBNflyAStd0zsczDCagkZUNJDF+G7wPa6tuKM3");
        a("yl8tntUOGXjOUT9/QpbIU6v5WSdB/yqlr2xTffWdDlP9flqi8huMkk6ZUNpt5BDnGP6sWbLaUTG/ZslzjsD58SEMAcI2qIIRDp");
        a("yoZ+EFcPObvACsxbQVb1PZOYgMzmgLczI1UDHJlNOn4I9Zlbh+Ao/1O58PxyeL/FtY8gvfC0vq/PJmScFn/5kDjdpF4g27TXsm");
        a("rAi37fyTFeX3OsFWfGUe/zwPJKT3uoX889Qi/nmeUNHsRLDbzHZkGDIBg6L9ghgpk5C11W4HyyfdmKHQ9KkTgaarHrDSm2H5gt");
        a("6MNtM92PMpWJEyYYpzeqbrVg9uqROjwkeq43jTwQnjFudyMiCwQPcs+RAcOyC8n0lD3WvCBZK/zLtfmvd4dhNo1KQUJo1TRypQ");
        a("kwjUH/9oBbXsNBCXvcniNPMl6vaXNcqaFlJNnyQbZxMfLLpB7FVPLqbkRlnyrdi04yjtEZm2QKO0F/W04ZRWL9LQkTatfSfrkr");
        a("eRTP4Pb5Do8X6IHvNYXzXCxP88AR04ydaBh/JorE0D8MxG1J8t236um89JzTLtPQ1pQ0TaWqI+2olwEEkNh94kdPcNND88xkxE");
        a("KcBQY+OnRmjviz9fQ+NcJmHH9gXMuKxgXwQV3IwKWBlXzm3LdxgJdm8BaobThXebJBLebv0ZPcwarnvYu/+W6J2UtWbpNmwXpw");
        a("gXNunYsAzH/4Tj4iRSyJIsUiJrcBJBg2gByiICZTfvPcacVKYJk6W8Sp9u6CWwMpnIjjdx6ENZzhho+IocJm63rPqLLagavABZ");
        a("b6O2PZ254fowZnyO9EojfY22fACnr5Hpl32C9PlG+iotBRgK0FfJ9Lk7kI4bS9w5uO6SWm1oxUC6smZHzisP22ZEVFKCrW7pO1");
        a("KOtI882lukDih0B8d/iRXTJkxUeoR94g0XKjT97ALWZt16gck+sYfa9minPiRdD3xTHLw+hyalOHiWuzhYlakP++vfiqVQ3DAr");
        a("W8u9DVvVz8V5f+x3VFQftmHAvojPOGMXuF5ImQDeNohVWB60w1g83cMVVCmgMdoHv7c+Hlx2CnZAYa+XWYqCkIYEIQ0JQhoShD");
        a("QkCGkIhDSxvReQpas/jPatr56a81tdDRV+LAw11J8j6KvNBT/SYJ5egCd2nW/a6vFRPXqtFvmvFXKvqBOBRq0SASbqi1ckvj8L");
        a("eZd7Y68B7QbpV1uBOv7HPYx2moXRsUTarI9Y7UDb7ZjkbfZHLJaDzGVu/g8vRKNASClq3CS2U166wArbA0DXd8qfC3Gy3fz0sE");
        a("2cSjkrPIbWtiUHZ5RKDgBVwwO0NdgrOJYPfYC5ivzasDvT6eQzO948MCl1C5ziMagM7rv/LiwY1JGMkHyz0Tb/slcIddyNWDHi");
        a("Tpw32ZkYf41AvHzMWYPeP06NK88S3gZKpfn6e1TIgN2EWukGYO2r1Ndn+/LqWSLdFHaTPfi7DNQadL+BWgMQBGpB2AhZnlVZvv");
        a("qDkWU7guIkj/7O8EXGGHrteWRPCqJanIqksM4fNtDU81N6LFiaaUs49ULm75oZynq49pzSZJPN5G6dO6zeRGSEeBdJ3X+nAfU0");
        a("BfVUBHnN+GR2nzIAfTJmXL1r0bzK9thE8/QLWAHa4BjnX9xZvRWwGvIZ2gooVkfAN2P3vB/hQ6CpeJ94hWTXtK9OQL7mNxEU7/");
        a("coJqaM15OiEXZ7JA/0Sv/cj0p7JG/dksgeibSfaTC3NLMDaPXMb2esgpdaMQTKNxXgqo6sSAqU2HmqbK+uNjrSVfc8r49RDNi2");
        a("Z8SMA7cZPj8A1Z0trwKsXfVUf09tzyGo9WjUyrwAo85FTrEEperQBvQ3ciFlzzBl7zayL5EPCzL7J4/0ChmPqwcpCj5jEhDx5L");
        a("utx5+Hc+Sb0O8w5GarnWr47fITl9RJ7uLcOjkROx7ue/zNYy/H2joHvEcthINEDMgeWJrVRoJ3vHg+xRKvD4MfyKNeF6wZgtwx");
        a("Q7VQDhWDcdlqMezM38ModF2J7HLOYgY3cr4u1LGJxYcQrttEt9vzeIqGiruxkkyBmEflUdM2hWubIR86DK6R/Ugt90LHKj1a7g");
        a("0WFffmtVd/JnlO74ihNA2jVf5/uRy/R/8gx2/I8sT4e7BjOGFrvDH82CHGcErMGHZbx5CH7cinMWx1yJEIKbVtD9PZQh/Dlfvj");
        a("jGHXn3ppFBC5lSJ/2lO7z1l5/DRlRxN5fnysl9x5skxn2TvlOIj4MZ5Bf2lv3j5FT15LgJqJ5Lk6lLyWRT61nX9esYipqrGlA2");
        a("lDQ48hgng4jlJ0OCZonE59yz2CTTQBRTJ8+YXpFS7kcFMO4X9ZZppI/TzwtBCvDO7B0ls1DSvx9y8xq2UA5rbHyDsep2RN93cZ");
        a("6hZHsR79dKycL40kskp2s6mmc7mmQIlxnJOOn6T8qOSRaEKE8A8piiI4jkHp5Dt0ivCypAi/gS8yaRYL48THOZvIrM0+KXuCPr");
        a("Rrpe5P2h9anFNStws8KbY3e30SLnBTC+DD3lv3deX5MfZUiZQWtST5Q1nSslEY4853qgvW8hiNQxjwXZEDcH8hooZwVn9wQU9k");
        a("ahjevatftNrNcsBuViPLlqbmoKAvr8NVdzlatVk5Ndu/2OkaXmQy/wQH9wyowzeefMMP4cuUSGrSTvJNjOKyUa7zZLKU+Qhkd9");
        a("3dVB5CSjnOJLddSixy2nxhBmNAhY/vEhDozcZVaiEltSKjc3rEJe1gAFxYsRAtZcfUWtEp08ORE8rDXC5aHTmGyyHsmC6KbNRs");
        a("piyykWf5GxXp5cv8yFcT5owMIVkKJUvq0uBHWSlRJGA64nLkKesniOvD/kV5ELqn/CeOJYx0p02eknLG5k6Jk9u0njlzbIn0OC");
        a("XE+ywWMS7rQglbmwQskoU6hEUMWzn1gv2/toex/1mzflKLU626h44ke1YN1n34sEkHaw/j2g97/0f2MC5Hm9qMpt7/wB6G/uUw");
        a("jF8MNYxfdHfo2kn3PNu/7Qvze+gwwrZzB5vwII6/zJ99g8++MCe+vuxfK/v0lxl/P2D+H5FMQxXRG3zL2Ggh+H5qsGcofdR79z");
        a("Mzzaz5zfDsbmV5Ihf83etj5eP9c5huve/C53ut1vvSbpHHF+ypvx7E8tpZFL4aYaUfbCfLfMtWpvxi35edKNzn+/L2gbZEla7t");
        a("sqb27d9dqL+z2q52+0DdR3KqsIevTFLw2NgVuu3+7AeyfDr+RobSJ9OmUweanXUkkHdpAxXYICFKpZIHUB5x3xFMG8G1twqUyP");
        a("Io+b1rOPYyWdZFZZtQln1lo+zJVJZL9W8fe62bSteitG/NzAtvyX3/9atO8u3+0tf0z8FU1aGoylgFqv2d5vaHUw0FVIMOwPa9");
        a("dgDs5/NXb+xXvtniFhKTxCUfOQGNicNF8bkgVJtvEbpnhxA4QwmcZ7HpddG7pl7kWlXkfVK+eVAVkfNWTUUCpiJnqCLrqMjVso");
        a("iaeT+KRLx6/mEq/2zKf4bMP0SQmXGUOZPKIoD3uwHcEs/XALlwtmFxWMSsE/mz6a2pPAl/q6unUq28Ca5NI0m0CrkR2Rd81zX0");
        a("tazXURONVh6FQBQqFdWHUvlkKv9rlHe9yKYPXuvB1bgQWWiDrjwSASzr6uoxlDeF8s5CXtGB9cgqujaCEk5OM7q2pceEPzb4q2");
        a("+YbEDu6AfyyMUS2hsUtG+lGtAOJxBOk0DeoID8c6oO5DgDyFGUEE5V428GMoG/6W5sEMbbYtWhxlR6VVW/TCVaYKDhZHyqmq34");
        a("f30s/ifQN9cdUquVoHP230AvC1427OmvdxYFO4M7a3vGXZ/cE0l2Pdy8+AgvG0XnJreuFsZ/eH5RksvsH1fZIlD0SUQJf1Z2lX");
        a("GFkHZ7pPoA7ZYDVN2zjFXCqnl60wjO8Sk0vZV9T+9oyjqEsu5K1gnzlbC+ARcTI4UvGUWcDT/XOf3Y71h7CFX4KCqUwKWFqqEQ");
        a("8a2ofihXf32Gbs1jp53891d/Bs8/1686L2p/+keufbS9dobfVr9d/6NB4Yddv5dtXVnoYyhtYtSFWb2NbFSQvtRixLKFoioZuO");
        a("0qpwisrNSTPtcDu/TA83ogxSkDozmQrNeTWsHWjqYjrWL2cRF+N6Z3hS6XeF9R+J+EyBncX+mvnCKm8fDiwjlkLZ3Bms1Hj66R");
        a("+ENp7X+PSSulNPSS035AWr2RlElJHzlknUti68ylxEZZbizSIJ+kp2VT2iMyLQtpLGskE7Mo8Vbl08RjSc8hpP1lubEnrQJMvL");
        a("Q4XXC72FawvPCCUKVJR9IO5Uj6vD1KC3fqaNJvqtXP3Ovkmfuxw5Wfua3CDgbb6FLr0X4+kQYoyPlNuvan46JRtiKhziV9+3uK");
        a("XEo0IbifzslsXGJSc3HZel9e89JhMGjBFiiEvybD+oSzuQQZMEIbZsD4xKSvipxlY6mIs4XNYWQGhqpjOB9Lq1dZDKTbzs/1MT");
        a("cpZx8n6P7pT1Xaa/QTTkR4Doq+/J/uX5WPxtiH+EHhwpEZwIXjaqz3r98f1rd9CM1iH6LFk+P8X/im/Aptas1/+0/uXzXk/mUQ");
        a("G4O4Fl2VNODoR/u9byXilzmciew5XHsqnQe7E9pzUPNXJP2F9j9/iyzzR5feHnqaUab33f8j+4+Hkv3HZ/sdbwyrD1wkn7E0Co");
        a("KNcPilD0RnAQZiMONNI9/QzNnn2V3A2PXdjrg6gb1F8/6zYm7/tiU6dPtVs8Xo+LmJl65ijqUHzEM8l3GU7xqmox0x7xzdQkNF");
        a("Y7sSnTJJqbsK+wJRK2209GfWVf+P/lwg+lPKTUSutPVnydV99YctyH+oPYawrUeSR59v7VNifrKVSsbzh/T446gS7oDV9Md/34");
        a("Y8+oP92+MLtgt7GfjhkerQ0W/aFKDfH8DIpvEKC8Pfc7Wo0u8y3pOD3u38rd4Us0HfYrUg3frT6EXKJHhf/c9U/bfo//7qoO0J");
        a("LfuM9g39WezugLG1a8EsCckqMTUJ9Hevrv132kODXGrNLaqx6XpjD1sbi79/x6BRm/TCdQ7tzQ/hERMPhd862f+W+b7A8uK4ps");
        a("AHImWcdAqLerP+bpqQLnofqeQLcWOYvtrwZd+/7f5IfWw75jyD7ij/PM21e6NQymstZBeorYWlBE1knMooJ5w9kwRnZwKzgoWl");
        a("8f2VUmSb4d91nqNijnD7N5JcjQMNt2SSvcz7mR2fx97/mBPewCxl5uMX5kqG+cwF5M9LPmPMnsfGlsnrtr4v5cb3D058UDxf78");
        a("O0WPW9Mg19L/WG34Fwbt/rSTnw75ThTIt6Vwe7osfLegoHhP8C3HqNj6/ER7dS6pJabHElOjSdfOboruNPVUts7myJ0B1chr1e");
        a("/VQp+HE1av/RNQfzIFoi9Blrve2m9/h2o4TyxNSkzbufDKS3s383IbUNmYLPr9eR7KcK3WPGt4OkFGwyElK3jsKhuZuDpXNE6K");
        a("1gaalZ3y5sHW+rCp0BKcabq/DMCXo3i1Ap9CDDZmkdCsMhowoyQtPQKx1TAK0zzJWsBCFo3ih6L/qd0FrEeU/ZEchCwr6SqCGS");
        a("oeODXukwoxb02NA15r7jxLiR/QySlrsXRAy/Yhoy9VrUNiTnBy7pSq7EzOsVpcuK5h2wVGRGq26JPfw+zYELew3ssZxXQE3F2s");
        a("wKnVX6Z9GFP2cx+1mQC2myOjJ891nUQ45I4b1QpGeECufkF5YGBnA1AdgYbUsqDxeFZs5Rb+dKNE6USa0tnOMMS+ZMiVRnScVv");
        a("zU1ZsF8vE7YfiM8AYugFo5b3GL7/hTxKPe9g7I95VL6Y9LJNRhafrSp7ffEyx02f1096pyNxuuaw75+sxND7WmJ+lVxT0hhvDy");
        a("mvbn1I+mOrmk9O4aTQTpH0AhZm/198lMbqGwn6QPvfKvYNmCnj/aGUdoO0kQwlOW8nPzWG/32EbeSuXRhVszrQUf3hFutyUVzS");
        a("aQ10B4JFdxMZ1B4p642aOmovby2wGAUoTpJXzW6MBVVZ/fW/+nMWq6jXvDdxoEabzoGGJVTlKk5tWEThu0R4IYWzl3F4AYXHij");
        a("CDsEjkyabwL0S4vRfh+ns43EbhShEmezOaR+RZReEJf+LwGsqTLeIbKTwWYQW9zX5UDf+UaLpVNZMsYb38rRG/rCIhg4tUcKEK");
        a("LlDBHBXMVkF0Rg+2qSC6wkG9J6fhbonoNSpHowyGDQ//ruWrRgm6Z9HZ0am9j12nz6JdBmo8rrprkT2s45j2GKsN1nkAmlBKud");
        a("4NSn2tJOFerppThTUFNDdNlPdqqBj0bzIHpZI0m+TQJEVmRGc/ReTDZzv78NnuIDhWOCBRP9p165cZSqJ+lMMkUa/guxWC+tis");
        a("apZuh100iZSZlKpdxSntkUFiI5uAMISY872bA4NPJya/q+5WVK9P/jhIjYo9oHQyaLeOH+m/JPtIPMLy2O7BVUcvtOJnVOhuhG");
        a("3rQe043J/pTtfN6AD6VZE9RdpQp982+k1yLa+n3yHi4t31Af4Y+yaJCOrWudK1e1ey7YpGRFZkkXhxqGRzdG4jKwhJaVLtX7Rd");
        a("C3xT1Rlv0geFFlKBQgGBIoWCPGx9QSlqAw2mkkyePgBxs1rYg+mwUaYtrYRuDbEY3XROh7KNaTenq9NhcQotdW15iEXYrOKjzt");
        a("etAa3KpOJo9v++c27uzU2atJvj96O55/Wd73Xe53zfjTiP6vwZYGBSwBG3UsQmHlssO2Xcp18g7oci7jkZtxC2duEDhM0RXvkw");
        a("T0kHksYkOkllYGpcJRKAJZ3aXSV/FtYxR3nOJ3m36pcE4n76ZvAHv4rMJtXWSOepYfwblO1K2BXC2ZutnqWbESLdy2RanX+MJl");
        a("+hzg1ZX1hWiETYuLfVl1jOs9XdMco1ksBYqu5FLXeVtwkV2IyAiu/Zq40qwEJOXMsq0GBQAVnop9ezCtRHUwFg4axnFvyyu3cW");
        a("gP6hTL9D7nfxXds1uDXC5I+UlWdQMpJ8igNJ/jTdS0Yf/TZXvUl9gGyAa+NK59prOEquBWw+XgHgYzuvCSQ6tzi80o6fvGUMUy");
        a("/ApfJFKgqurRwqDbjk23yWzU4Kucu3x5WqZKRBOx1IV+2qVcAhTeWL+EThn4wbqnF556qIXH60JBqXJ6yOyWWJz9Je8PkUN5jl");
        a("t655Ty2JDpedy49Rk3JfDUuMqs/ZZxnkKSrd8ZjWLuTwNwFMYSmP1ktZHRBvRQG9mst48WSw+btMAiLwI6A9s5aPYlWF356mU/");
        a("ifpWmimLYyoiiG3xRNFA+sii0KqXwbqPev4dFQp2rVNEqGa9sGH6BSVm4oaWkxeHvAYmwr1Ur+E720lUolE0n+dI2FFAnm7bqN");
        a("tx8q7V4UX4WAjm3ftejYtsqise3ktRHZ9lZxNLZdv7IvbFtezdSfb4lCva/zlSEG2sVc6eCTQDVcieTs6Smk+jM0Dsh4smdQxq");
        a("ghhB8B6kdlIaxYPUTHisVDNFZ0XhORFa/eEI0Vy1bEZgXS16q6UDgkhi7sGcy/ZP/gpTjV/sGb2uKyCV4QlUNrwuwfwMOqxkMx");
        a("o3rwOZ7Q0DdmPXHIiVCtCCWLUJ0IpSHE3C40cJvhLAqBo/To4XT16OF0I+QfLg03Bm0x3DKNuyuKQxLhYa3nhNdmIIEOO/enam");
        a("K44eqIYlj4nWhiePma2GIACnGqGMo/jzZ25aeGjt03PxKcN7IObaYt5JAR3ClyiPede8nOZxL5+90iZpbPCiWg8kgyUdJ0mfRL");
        a("TuKCao68vZbNmzjaKWaX+dbZlqpbOGYehZJdJXKeym+oJQuu4Ha2jb4ZoEyV7JvOqfxNJo2vKxZtYqwEJHHH+UaKvYU/hU25Fy");
        a("kRjWYnQmrWfE63bE5P0YT25PKIQnvg+mhCy7g6ttDUBHL5DQcMIekFscasW/mKm26+//ZAsP6urYL1y42paxOQWixTHeZgqmZb");
        a("jaVh67ZsnolU1txJ+NB4aMePkU2fDOQcdgEomV9AvYE4XvFxn9W1jPggCnv46XrnQXHPmUVAuWCrGHykL1mM2Zi+kd5cHF7OB0");
        a("5cEMrIOsPenSdTpNSDsNLKQyjWeadksfAIzbldmZFqemEaalqDIv6Fvqhg5yOPzMCIMEFqDM30UzCYZoIH5yAM6igaj8sk/bTM");
        a("Qp8XpDoT+hFaAV4zLiJcji2Tb+8YgVDLBXcma5o5bGlEzfz3qmiauXFZbM2UmInzlhP46H2M8w8QXJYMmR3QKlX3AI4vgRBlng");
        a("LkwfGLAW1e+ttWMXfxzTZ0uUdN3JDNFg7qEJLv3NRNDu0hW9lV2qRM12/dj/4HH7zl7eMOSFL05gm5K+j+BDH6VvL5AHD/Eq9o");
        a("JWeBMgY7OwTspQArrMxhF8jOaWJfsobgKIGfBqh01ZEkTU7fXxxRTstWRpPTP5bElpOR05tQkU9jEE07r0Q3pSf++g8l8c3gAv");
        a("yxTEsKHQ8sD3Bvj7f9kXj62f2cmoFUv4VJKi7IoggGL37yuTDuF8rV+75EjRPfWRSRE1esiMaJg4v7pLGZKomr/DHWr7MSQ2n2");
        a("bxU93v1V7/dIsseGkL0/mKGjR5qu73JvyIqTEdpkbQtDmMOxrsHBqVhzgsaC1VdGZMHl10Zjwb5FsVmw5X7ULThwx3tR2+ysBO");
        a("O8lPfk3n+A+2T6xjxgKOO8rRUh2ox+bRPsorPv9RezaKwSRT67TlhP+SCeh+BWMUDTD98boB7BHpxZ7DbOLL643zCz2HsGzfnE");
        a("XaL9PRQyfbCr0wd32PThR/rpwxrqbQ3Th5QB0aYPJ5JCpg/jVorpwzgJSCIIfhnnD/aQ+YNdPzD+3awJfJ0zosCvujqawNu/1a");
        a("/5w+tZ/Z0/XGAWWEeeA6Src4BUczhx3YAdHO79Jv1wb3KGDfcvsz+82OP91gnoe991/lfj/R+c/RnvD04ke+HO2OP9Uuf/fbz/");
        a("NuHid/Q+3nvjNE2auDCiJqUuj6ZJWx39G+8PfRCt7+Ch48BH/JKiUrMMH/t86JYY5z8bYqRX6tLdndeQu8/tKEP/KNzZchu1Sy");
        a("38kSE84HZ9OKMzOzM0/JEh/PgEfTitc83K0HD2qtDwRwjr6Vc+vCuKf0k+n+pQznfzqsm7ygz7XuXsYX0PTKAjqhV7hko89ubC");
        a("DZSGvR88UELHmF/uIbPH/tUlvqLiZrwLJYcQhxXcioSBwMaOBIB+/wW0xELvdHNJYd4nrgm4ki0TDrzAryTMyejo6Q30E0vRNr");
        a("J8Jda8f92avMZWU5pl8ietsdaUZQUK806VPyouzKUX9YS/CAy/bzJkTdT7Jp4jutvj+7nEmyhB4zI648Yb2Tgka93vuFNpN1w4");
        a("CffPVRLr/QbqLPQ0h1V7k6wWzthQ8+O6mh+WNRvfQBjlYb1avWni9DTbPK+wPcXLAdWdj+HvR1QZrDPhXqFqjm59oRmVtHCMbP");
        a("QQGO6+m8oW2HJPSLsQVvffTJadA6yW3ybdOrFg85euEQR4Mq8wh8GphL2mzBwo2El5Ck3d1TiIbjHZ8o7zeaTghPV6TURSP627");
        a("yEIBeqC3b0IPlBd83amdj+eyS/OqfeIBMo7K5Qvl27rteW87LI5mh8XZxP7xQM14+9QvcHuyfAC/+/QPwffGAbyvg7ekr3raQv");
        a("xzCi7l7cdxyTH/AOWPZ/eQW8F2eZcG98OKPK1csRMMKfIcRsXCj4Yzr9FpcRx2WpxtucecVa9uXORfUOLbYirYcn7B7ObSmbCC");
        a("1G51+79jdX89+taE6fBljpi3gzFJBZWfIA5ZXcN5H6rRZHfvxf8mU1HeG/wOFr8bLXyUddeH9Hdql3+8c2qrs6ohSBq+Q0gDUR");
        a("H6v0D6YCsJdy+QLx9QL7kClAfUy6KqJyE1/wcFfcxP/NlnV32LWN1fmcqTcxo6t5F+QQ1snv1W92lcqr+bL4sd81lBH9SmNKuo");
        a("+Jhl56pRAbef2LE+1f31OHrs7s9EyHJfA3TTb8fdwbHIV1k2KjDTNcyGA6uLbHmvWx5tAGSaA2VrQHLcXw9aP9ztPysSMBRMRX");
        a("5b3huWe+4U8yfJKKP/i0JDe438fh/vTRT4N+wKXgkPtt+qgCtxD/EInZ7swFZg7bSmsGZNwFb1vuXuDdSGXtTscVB74vsV6OZQ");
        a("pqj4ZfvU/eS5wf2uBQ2ivBFZtqQmVzvSAvbGTxKoKR30X0x36nFo1WFBh7kCWSw7h5vVq3iDeLaG42ZvPGOwT8mw9XBhXHVztX");
        a("AnkhQIfYjv8IIg7zpDb2K4/2mwX/AGuSHaxS+IsmkvwtNc4i3vLsGbyvm6F+zX8XX+FO06//xD2h6r5RI09yuWqnusf5N7rO0n");
        a("0Aa7wg0FaO8DPUfQUTpqUItA4DHsI5R46QVz1asWW5P2sM5433BetPutTFvk53R/QUFPu7sbmnxaPP0oTac/q/jtB/kB7fwn/q");
        a("hvGTRrPEbyE17WyE8jE7sLlxhtMb0OX1rsLTMWrO59GqyX8gErsNgIq+Y4zSyh77FgHT+gwdpAsJ4Jg2U9rhp4eTzcLkak8a9e");
        a("6SmQ/uf+xR81lSio/Pha/q7m8w+RwaccLTBIxmjfr0fY9+sR9v16hH2/HmHfj0MHQj3XuXJ0JvzqxG8V1S8/qXr9Eai8eX+wwG");
        a("AwRHq462De8O11zVpgZ0Wqup7wp6jrCmTlS3b5yxsqUghUZVlaYJLrQ3wi0ta+MceBq0LSgKsDP9RYmQdpoA4RPoyksuWutkKv");
        a("fbIk3BCYUR8J4EP8WqtfmjvJsmkYhDh9kmXz14PwO770SrogcHu8uChwXoL4/Yh+kyybr04S4XsSxQWChQPxiydLgSQRbjGJcA");
        a("XiO58fRPrMBpzY/m4KXzpaJO/ptfJ0rdhhTg6ag/LpzEH9LVMIHp2Salj0c7uIYohKwlVQ8hYURBAPbAVZN0eqw1uIuaA3cjWr");
        a("w6vxhlSzjXZSi+GK6vmzZB0poo4r+1zH6fFhdWSH1JFH+8Le5Ts8bafaLUVtnuU77MVshLdOUyIyPLi8luIqZ88onQYp1z6fg7");
        a("pY7rVLucf2OZDMYCH5Gy8Df+idtB1ZyVMXZ1ebzW1cAt9sArJO7j1WLkaZ+IG8pg7KbcygcHq9vYltSDit+ZfraXUuQxWtBrHV");
        a("D+yn2GaFV3N0gb6aD2nSf6NebMv6W8eRcWF13BJSxybUUWKUm207KqjTzN9MbSMDaHgfoknr9OJwae28BGzx0Btl4kuqxHl7Mu");
        a("O8IGg611OaNSUCAWmFcH6Dm51qDRnZ+EAN3HPmzwXomQBN3IiXkBcki62uEYiPDh3XSOJCwdMNTeVfk3VVtOejigNi71WPf8cA");
        a("xv9y3Iigk/he0bfusgBjKKFKwNV66HcQ9HVJoQRUATSfDSE+KnjgnxwGf5Ye/mjAx95xSjj+mQL/+X3iP/SEYT88SQd79xzg/n");
        a("RiKO77UBMfKSb2gfnJetgVetg3zGG8lyaG4b02qX98j1f58k6WDr6ZcP9XQijugyXubyX0ne8a/D16+L/JY/x/lqDiH6ykNpHx");
        a("t/e5ggS1gtX6Cqx5IOBCSYDKG0eiIOBsxMfkjxH8nok68P+czfgfiVfxD7pZVxIY/3P0MHlLQjWInT0PmDWbRC9olqX+HKGUZa");
        a("eu1I+olE+WSpKlymLV9QSVWiFLDVLbvyh1UXgpOceqEgc29K0CWkKjw1kS0EAJ6Ex8OCDLczpAjqVhgAYu1o0BFgno2fgYdBxZ");
        a("hFL3yVIpstRGUersiKWUgllkm4zyD5X5vyXyj++tFj4f959HCjNYFhkjioyJXMWJMqNNdOrvoVgwPFw6jLx/5NtqywfxwIAEsm");
        a("nrrAWoelIh0acq1y4AZXebhJ4mymp3QC3CqrTWp4lBnIejCSP1ltbr2D4gjfpWgNLmXSvNjP9YPTDWb1511qbxPhtyB0fKc0WB");
        a("cRFaxGwqMZ5KTAeuwfGb9tCj5WbqwYeN07GDYauFpQSVhCEjwkhIIRI2Sq8JvFswkPxj2d0I0VyG1hSN75rFWuPIP+15p29Pss");
        a("PVT9lbajXlA5ANDuN8VNt2XW3b08Nq23Yp5D0M7ZcisFb5+1IsXZIv6gm4y3cExClP6ESsGKN8XpulZgfbS/5VnOtKijJO1vDY");
        a("TCeoVq1etSefdGn4JOBpVOt3xgb343Bw/7hEnfkvD4K7AeBK5FoAaRz3LREnjXh28G1sdXGw9SdfyNPUDXzpBmslYcKXf7BWEi");
        a("Z8DQY5xXqa7b7lHBPmz9wbagMui+rHC2n2Vv9gNVhLQTXQSrtWiGjTIvLtSsUQsgMPISfY84cnl06ykWFymWNR9y42S1JFz49l");
        a("dWvbTH7zM6bqdoM9HCN+tAvfUlBLszsu2VKwQ/e9nb4xHE+hXwm5AGxS6yUz95VlZqzEEp6Z5M+DwzuFDCbQb3ec+N0uf6fgN6");
        a("QcrBmaA4ppGEwqNFl2Nunx1JvNs4ZUNNM1XwIpUFFBLuUJkwTu3jAFB9MSe1QpjQPRg0COxGGDZ9524669Vp/yHT0k9LzhpXeo");
        a("0MVbs97ez6DPS80yoj+D3NQnZBH2zQW/MvG3Sok/VZfEIJIJanR+zClNFRz4PXUKBsIFn0Lzu5ZqfMoIybKLlzvWT4I6VFwwRS");
        a("PfM69WT3eoEAw8MNij/VZOtP16tDendA4jvbsQbpYqJ9ChkESP3te1+4eVaH7q1MpDMuUvaqhIow5iS2FaYG7e7Qlzc249bMyx");
        a("caQWA7p5vCk6Ie06Gjl2rqVqlkmrUjTkIVrOa9r8o3ShWh1utUbc2iuS7PnXKJHq33dcX3/NUhPobdNl68LxhBhGEszoBcWeJz");
        a("7oEZuSBr8RtHF2uf8mX8Qyzm4afTzWKf5VEdORSBsm3jjYcPDnaTC4y3mPMmToMmSEoz/oOLWFELl0euNCJNgOBnSU/1CNwdWa");
        a("DvzSfk0wCkE9aNaeRWJQUMi/uNiXHFLAMd1ApQRnpbAdedvEcIw2+LG/zltFrLKyF79BGDLRNtiM51Wt2Korx9aj7BwblTYSTK");
        a("/7cdLL9/dnaCre9/019p/RI/xn8M+LlZylppZ/+DIUWYqweV722nJwOP0gRXhds/XO70FhwZaC+GrbSZAKfZgmbOokSgs6MwbK");
        a("j5OD5McsTOI6t46gfahCTxd2X9dnIt41ht9lnwmUJuHvsI2Z1heHCZsRI0p8wXjLxmTri5Y4raxl07/jJNyH5Rbut+DPzlL1gc");
        a("BtW628XVI0HYMsjSTEWnbbwrfEaCXNB1yLBlhZbkUtDbzT8EuIvfNrROhgJPYGY0Uc3pk0CDhJztx9KpBlBGSnuA8hU2UZ3rkN");
        a("COOvJxGWJehhz80TSb1YBmKbg9IVz0VaLO90PIglhP+6Ej2NN02LQWNyCI3wtwOt8Y8CDAFYTH2FXcKprPb3UzR7dcosfQPgRD");
        a("BN76V++1zyUt9CVznODEfFApTSiYVqUfFrW5wn+ZTDWNRGRW9CUZQMWLZWoOSamtvNOK07B7z6/oFBWgl7cTMMOZsDpla3koO3");
        a("1s0mLS24p4NMZKunWV+shXxeiVEPc/0m+6l2TxPOrhFffARn1ziY4Umjm0xYf33asvUOfFCk+2OTxn/lFqwoq45Z7u4ExjjxKa");
        a("YTn7JLd3OLBi4qvV/lEb2vSJbhhRSIp6M/65ZlaYHqZckBK85MrXknKw6APrXQS3l6Jln5jEmjAJgK7MdSWf9Iwv21EFEpbnAw");
        a("rECOpYoVN5xLhGDefsvWN0OTa6ymLWDwHNtJy+ZGGsd/4Sg+UHRKtRhUk77KbsIPK6qyaCCqhFIK9qgd0MfnEyL87SxuKUI7lJ");
        a("xQGoCiSu/R2cwk4v/Uw0FWDQJZjuImx9S9YD0x7CxmmFro0dl6Jrm/DpRmqkmbQ+C5O03NMPidA30OJ93zD3+Cz/2JCcZ6hofo");
        a("FnOs9IOQOMkNVzwXKB3u7jG5srUclXNmuDIkDuDOanLd/d0paOr1Z5EG/UOnPedfjOhfIxrzBqN2SRnuQUuBgQqD5iomgdjBsH");
        a("gVuXERof1IQAMPIrWEaa4xCKEnMJTKRyn/OaRlY2ldFzHPSOQxApVIvkL3vf6axv3cN9BXb5vcv776xGwwORDaV187uZ99dS0B");
        a("eb6ffXXT2Eh99VvnGfrq43PB3u+E9NW7JvWvrx44m/rqsXL+Gd5f/xjpeg1Ff63vvme4snTBDeaZruFqI7oMI8uW5SerF5n9E/");
        a("V5TDidV/OMUfOYfJG78ocuDo4Cz+CRDjoss5enC+7uhI2pvFkn5w8Ru6UWOksp5nT6qplnIlMs91OYwbC77qdIBMU1FNR3Th/N");
        a("1zqnZxIidU7OmfrOSe2YskngSUA2Qr86bVZPb4hilMks4pZnlmXkQc0pauo7Bwu7DU8SlqYg7lsfQXykjvonNBkqp7Gk807xdk");
        a("llGyb5DFeyzT9STRgKfg7VEtS2dVxtW/fItnUFt62/G9qWdWKveucQAhB6R/ahIfTOw4bypolR25WEgXYFAMsJgA8AZGwv7Sn3");
        a("VWpKG0ZHakr3TTc0pV/noSldBj7pcFp3Tp9p2nshsvpCGoprVITmdC7vyUSe8Tx1QVDXW1K0Gc+pXB6M0EwwekGeT6T0TZ4WyN");
        a("PyX8vzRxP6THvzBRHkmTuh7/KsuqAf8qzNiCTPveca5Hl4Vrg8t2X2mabPz++bPFdc0Ls8386V8izNUGU5LUc/5/CP1vpFbQcQ");
        a("ox9KWd2fxvtX8j06QwtXx/AZ/LyjAJ1uXOlM+rLsXGTW9U7nnRupd/IAYR8Bt7oDporHOq8j69qaLg2TgBI8bapCjdY6CIu+gw");
        a("Bv+6lTY8f3mf/rcyPo1Fvj+q5Tubn90KllIyLp1A+nGHSq/KJwnSoa12eaHskx6tQMy8+bXKMjqFU8sEc9kfXqJ+eF6dWRGQa9");
        a("Ch9jMPgF9Wq+1Kuo+vNpdiT9KcrV68/uSBXhCt/GUZuPlabLMrQ/sYlPV0Gub/MxSxUdLPH6HnGlI2CxYg7+r6bLJo/RRRk6M1");
        a("X55PByTrZmp0pon3IruN75myQeE5HKz5g0TLR8CynfHcF8wjRVVZt4oTV5Lr/Qag+dPHju7xBPtiiXBkoOxvszAXAq4ac1XQ3i");
        a("0XwNolZdNHh3ZPK54muJkiEFprkWy09bEzWrJEpcqJ9PVFMjqqkLraYewRCkGgRSFSJ3q8CizoAFb7CfRMPks2UBf50o0RYKv9");
        a("0APypdvyGIJkCMwPch+f3n+xKC93SCAZ4Ro3ZRwZPwDx4DPzPB+w7BQ/5LRP46I7h6HQM0fp43JyY/d5DaxSeo/ByLEmHo9k9P");
        a("lowj+gHcF8IyydEX8vqrd+ZxRH84PKPIGfx1eTH5+eexdF4cL/g5Q+SvMwq4XoB7ajYnN+jqisHPZQT9GbPKz5dn96r/eIcqoR");
        a("srj6r/Z5P+S/h6CdUJmP/GTnmv+nDZ7Jj68HOC/4WJ4asANOiPSOhGjCX8d2bFhH8hwf9Zb/Av1eAj5JvVf/4cHQP4F5h65c/c");
        a("kBoOXdxf+ZYS/CNxqnyfjAIg8RYkRmn7916s19UGQ228z56E2vxrkdd2cTRWJCaJ5MhtYmrseh4ZTX07Hfx0w7Z76ZzexsoJoQ");
        a("vqobphcyHgqLbZrleiHtSyr1X+EZvu3hcrDae3hvc3D8qjgCO2nBPy7Kw7wSXfByQ+b9YdtRUUbNmAVekFuBOuvqP4R1mJo+YG");
        a("eS+9CHZPHUim7DiHLzhp3ZWJ4mQh3I5zh0kXYSOr4+SZALmXsiNDi5Y+RWlBwnzsxE4OWH6LeZTVct/fbh2AuxN5H1Y8Dtptm0");
        a("+Ujg/iVzocky79TOl1zFJ8hdUKn1Ks1R6LGO/jt9KFX+/yHJwZzabpGhUWXqYcZJa0kZzaKWd/pJ2JGO57s81CWZ4MP3UTkIRI");
        a("QF77UGW73r9BQTXuibcQUHoHM5mmjpd+Do6Y2v0TcBYh+Fgw58J8V5K9JiHfn0pvYSrn5se5Ps5tMPg2CD3fpe3AZ9CdGw8rtf");
        a("vvB9xNCezvi/xjsTlo4OwZbXeDqQnMWuNherh/Bok82RnPrcyPK51OFJR9RjK9xQQ7t/cG2NcdLWPOcmDTXPhsSIElSLb3362R");
        a("YMQfjenZbDPee6h3RBuVox+ekeRE848B/x140QJXeyQKAS59DnoiBzk2w6sIuefympXehAEFe1VALLHEPbDPixAFZRLHanQhRj");
        a("NRb3zvBvkXevbhdl6OvXifswa7wUBTs+4oxW30NzE0hr1i6CTec+0nsNobguJXi0xtUpf0dnsIV2UlQOLX8lz6r0+b6HKU0olF");
        a("CncPldHtJw9um2yG/8G3zmgo6fBBeh2lf5/T3ZfcgUCcK1O9479qLHunXo4f6WfWk3gNsnDFv+CKE+0c5soN/qQuxpdRmuH45V");
        a("D9X7+p4Wc8T0eeVMrTwnm8gz+dhMBDlxJDmugghfZ+W2wnA/jntp00AaWXkQM3bE7avak4ck7fzUEbggl7g+9CwcqrvjLFwULg");
        a("EmR5WJ/Fg567/GRc6VQkbOIEJxISb8cnO/xN2I+E7yIkXkmYscZgl4W72H/UXyGX/OtOWu65WbwhgivExOPdmkc43wjmqQc/wv");
        a("kF1QfwGQBIrShZ3Qyg/0hTAPnnc038BAPFP8syx0kF/xI8AD/eQYyyBUdvVBpjythDiEB8C8WvRzyw/StHwUfT8CZU9SRCQrPS");
        a("WJLrxEjXrR0kR5FXJYFdeKxXffo2pY85JvTpnKwQfXp2FNP+5CidPsVlhepT10SDPhnqp/zKgTd6rb9jIun7G6L+5yeG1H90JN");
        a("f/8khd/Q9ODK2/Okb9lQR/oaF+4/0P5LuG8g2jfFI5ae/OdhJC9dhOQqbd55iBS+pe5bKxcXHQt8T170JoQ6nUi/lSuctmwgkd");
        a("l1X9bXeRfp9AUXIOSPqtfHs+2X3uqngP8NVrJ+Q3b05QZXafE1SZprmy1zgXQDVxc3ut196bAUm2brCMKmpJOMx1dyeu/4joR5");
        a("zivStk3OHySPvBUAz4n0KbiH7K59bn0/7lNjT1Ot5U7bNsvhFx9Xq6JT4vTSDFvfCwPe8L14CC50x+a4mP8HoHnGufQPXhTKmq");
        a("oSz7eaZF+O9N3E2livcXeS9spYJJVDDZBw++HQU5gYJq4xCl2UNXJ0oOvL11Y+ybhHt29J5sCt3/akU+MptyF37VgaizlJj8Cb");
        a("vB2xvM+QjnLB2k/J0T4v2LlTtPh2a5U81yw2mRZaYy5UxolmvULJYzIguMLl+gVuzFr2ZcXa+v6ZnoY9jd4JqrIj+eCtdfl8I6");
        a("7GlzeJo0f8NwIawOfpnSCnpIf+WJF32IPRMypOgiRDeavLggu9ee11g2EE9DE/Em0x+PZ5v0unOqo1gWWZRJZon5fe0zJn+K4n");
        a("j3DHoz2jYqqH5bnQdnBwKQFr34Y1davfsLeGpQH/zhQbiGN9WCNITIAYj1Ljw2pk3EQ9LrcSD9siSokf7Rl3xrXQG8en9qHel9");
        a("+aC++ytjPG2elxyeV/BYLQa2zuKDQWzvTQzDNu8Ly5Vv45Uz5mlNmKexIZkb0EFJHxlXMx1h3spi0/N29v/Ibw/uvkpu41aUwP");
        a("/1hMjcvisCt43vc7O/Sf4ydpK7QezyE3rl7jBwt+hwh8pg0+tnAv3mr9eaFsXf46EBfZhfRiWFwazZHQg6yBiTLceF+wyzycj+");
        a("FXj96kqXrgK6gm5+uoRv6644YYIfdEsL5fJdKU+G3vnUFEfN3Ub79dISsfAvRrnxI0CUWoX36zmUl7IZ/f50C78/ipYu/P5UwO");
        a("WPsuIKEESxhqE8nB4F9HTRrTSqVfguUhisSpTCvosU0FSm0tQRQhN3YU5FpUfR09Mh6VEEPV2CHoWyGenpEvR0aOmCnj89DXq2");
        a("FAl6lFj0SH9MffanVRvqjwmZQI5nXpr46vLM48JYAMMxMVIuqcPat2z8DNqS3Zg6Y9ZXCJ2Fk5+KVOEqKznOPwiwxbfBbVaB4E");
        a("NemCOn6H6c/lRH9NsNfpzC18Nea4aH9iG8RcML0YDUOV9mCs/5RuGHpGJYkkT0N5MQw79ROdqZqx39Rwfe9ytyUSqdBstW56hZ");
        a("1+1AezoHsPDLnt1O08nEBGwOYe3xxnETt0Jayo3DvpTw3hXeSvECQ+kZHfbamD1KLJjCU7wubcn7/vmGnOxf04EYJ8U4KcY7gv");
        a("ul7x5V+6VAj+obCXR5QZcXdHnXyQFWv3/ELNKTW+T5FxF5SbyByLmjiKL2SF0PkCWazwVN+AQn2mCPiajMGK1y3eCsnQq3UcAY");
        a("Nu53iI5PXylrq2VTIz5n0GfpEj6tLKfw7K+QVPWY9h4/XkycuybzToLOxZ1Sk0tsjW+O+5T+Fpj+yj/mPZ13q+/vzaLss+FlF6");
        a("tlA6Jsj38F6tOVqQwvM1Yt0yPKBPyTZRk5uXeEl+mkS+nqdo42/va5vy4N6a+pT7XWK2f4/PLQaWEwDi3a0EXPE91wflgXDbhR");
        a("++hjf0Sb3lkYuY+W7VltwHuSuQHXJ/OiLWIDXlOTsMTdYG7qTT++IX50Cn7s/uob58eBJ8CPx+b3iR+WgcyP5IHR+dHUx/ai1/");
        a("2TE8P06t6ZkXU/ZOew0NNwuIM/w+cj6K0OF3qaHJ79uPN9+L28JsuV7ZgmJWOapLz/yhkxJeoOpbioGJLaC9/Zl7FNqG6REAs+");
        a("BmJs0x3jRwAKSjbZD39dmNdkRYViTqaU9lJfPXUOvfqPXd1z5puYb516VptvPTE2+nyr//q6IUxfoak3YjyQmipdKsE63jc1zx");
        a("r5e+js5wVGnQ1vvwNE+x1g1Nfe+L11TIz1RCuvyRuDLHcW76OZOQYT9weoJH0btttCh8HDHXLMHDpBjJmagtdNYwUPWcrSsDke");
        a("pnGcFMkmnSjSO4xHzvNePhP0IztfiE+OlWH25IAk4QaEoZBFpsaiw1/RDPit9wnLT0+ZVFZ42kKaTxxWzEb9j95+5VhyYWZY+z");
        a("18rmH86cWf/Adow9iPPtyR12q5stFe3Oow7fNPk/svQNpsbVSSbdWmIizpHXn7bksu3GJGEHcdhU0qMyJvf03yQcITDbKBH3N2");
        a("gpRWkFKY14AG2YAKimpSi/xZ4hzJ02az7EyzNv4zGb8FJpuprcTmYbAAz6Df1LO4/ulo7fWp099Ie738aa29fj0qSns16Ht2Eu");
        a("t7ZlKf9D29cpxJ1PdRdpT9mVj2pcLnnzOyAVi54WOcTHrexZEH7QzoDy8EaZ85PYqyDmcJ0oPXetVB5LWxjT9FpqdurKTn1Pz+");
        a("0xORFjyQcA0GqujQln51JuC/xge3yLI9zxsr2zNqlBr/DvjIU9GviESlch+dVQkx/pzSr+xk4/zkqRicAUtCPbDlRCBcEB27/T");
        a("17dvj8L9swl4v8ftVrNuz9KyYcjPkvFPux/Kah1brl7IEO71B8ObyjnRR2DDLNr05NkcnVqwaarJgGoYlVvIwK/qf5z0uG8YS4");
        a("Xb2dKLoH7j+Vc3FmIIpuag06v6umT+VmJPFgU9kuqxehN2UI9/SwAvizCeuEwh8EoTxIRW88BFmFDE4FYgDKCxucoo9Nd/4GOF");
        a("41J9LYZFzPaE6XB+udLqOMl8lt+B7h6GXSljG+Xsa1FmzYI/f1cWTrgSm1HzRleLqhD03skNOf5nN6jju96+Ic2L4duARaWXXC");
        a("ddwdsJS9QsepQ+hUfWDQbXPktU/E8a8cDHN1A3HspgLNdn5vefAMLSLhffBm9qLfouJfAPxxWurApqsblqg2zlJL3I4SMB7Xna");
        a("6dTZGjeiWQYHRnsQ43G4P8aINXGO/VqTgPg1P8CW2uqwmqazCSScCKFVB9Ko926Hh3DfjlHyr0WY27EXGyIBr3xSiKa6W3v6s4");
        a("8OVN/HJ9XJzp8lSx56ugPeLXZZi5RezfmTc5ryo3mYmSZrvqwd97FdBuWE9ol5Z4Ex/A19QFqUrPAdS7EUQMV4lYhRjlI/yBbF");
        a("V22REEH1NV2hTwQeXJv9f2CAhzZDeaHy/m/OeCwS3DNQZPjAeDH4k3Mjj1UeLTdf2iD5KuMjF9VLOl6o/Ip+L/wn7gPxIIs4nh");
        a("Ep+Sgm81sQKJ/M5WCuHR78o7pz8junbx65BpiMNVXne3+dZpIKFQR8K3zWTv32wkoegR6IhaxVyq/xf4w8xj4V7LtcL0NYH/AQ");
        a("LuBakmA82x5Hl/nCrP0qUsy/dvYVkucP8w1cT8L1ExeGgf65JrrBJHdf2Vu6LbgjQ/sZZpltKqoUGPZdU0TCP0dhMI/dxkJPTb");
        a("24yyCutP0NTovLjHxAe6g7yDpx/DXKDNP4DuD+HTOxtrFE+3mOKl5p4WE6TM3P3iIwsfjUpOrpyMWD1HrY3HE6y5R63u9y2wgc");
        a("sdjf9s4G/12pLzE3ffjDPOBPQFPupsFAtIR0+DN1P55WmuN/FpqfqFuH9SekWweB7eZcnyG6l8ZlGNM9mbfgO+lW2tWCY15190");
        a("EyUMsHrK0/wWAfuZVobtai7x0c/8Xew5xukkS+t/o4zWFlsqsRrn2TcT6rZkpwflHkY524T3r6hxvJfh8HysrES4yGNjm+4exd");
        a("D1af6Pj9CFqREt89NYKa9qpV6hDa7lPPOTfZofY7395mR7C2uQUjRZO3nU0nEwlSZH/DRyflcHoxI5DTJf5PGS94cdfdofLtX2");
        a("h6Vtaih+R1aP6gEimSck93SdCYTuDF8mxrrZYTvDUfeFf/Ewxrj1Fxj3hY3z4TblkU8N8+Hw/WKH9N+v2iyMvW/885B9Yx9t2o");
        a("LYLyb2sOMjB5YSRCzX/8NGnki3MTRwQakhhNheG4p+I3vE3ofAi5LzDXvE/6s8N0SSZ9pEvTwBxpumvPCJUaQFQnB5YSKNvtff");
        a("9EvQ8UiuQaYGeR468X+Q54MR5TnxHCFPSSiJlVF4eI9BpM+fkCJdHCZSq+DInDCRlkSX6XMPghf35RhlatTvDmX38cj82JFVJ+");
        a("wVtAlDBe38Iy23dkhjAyn2Gr6ArtTjAaX8xsybAd/0JcSKusUtnMS3B9PVT2LG/GT8QrlJaz7TLkP/rksyYRWri6jXy/Vq2Ov7");
        a("q0pyYzoI0lA+BqM12yvafU0QQcWLPIeEDWGn52DlJXOvBgyy5ZI4rFuMMYO9gzPeozEG10b8U+01Y4ci5M9Ejs9OBUehkneCo1");
        a("AxPpEVwxAGvSGgC903+mVMye8tB9cPzuUFobg5uIfniVjkiAy3UIYnkMFaPwdoiMhjlYh8AJEOstdVhoCHAwAx+Po2ej8xjl3H");
        a("X1/keavIOwQXQAF4QVaPHvBIArwExYDSb1NNErKJYuci1uldkCyiPqGoqbKys6iysxHoJA0WNaZvpvmmViMArggCrKMCftywCe");
        a("Kxe2IIHvdQhv35jEd+sNiPKfbZfFHpr+5E4HEE/JMF+AuC+RZTvrtlvlLKV4EAcqn8DKS/vRyIavWNpxIrZIl5VMKJQFVg42JY");
        a("MAhUfVlWJJcZBbTOIEh4EO8d/pKE5guFdpgApEpoJ+9AIIBlUF5PRRPGWmD6dApJ/g3kfJxy/hOJNAXaTzn/joAPKse3ZPVXap");
        a("+X6xyyP+Bpc36/IcNRM/incHt3uWVnqslR3Agz564RWtIPkLSwJsHURGVOjytrMVz4Yf3mKzbqep/NJpeRrY/SITP2k3oP4G8/");
        a("fN9Neitgiut8CTjh+136pqU12aeQvidGO9grpz1ZXA0ecpoazEud9yEE/7IE2my5OxUhTGlLbya4t/E50I/V52tl6kcFfwyEyz");
        a("f+WG7ZXKUmVasf/yHuusOjKoL4vcu95JIQLpBEo4LGDoKaKGpE0BwkkkBi7/WzECsWSLAFOIloHkf07FhQbFiwYCGCSkzRJEiL");
        a("IIrYAgK+4yxBWgrh/M3s3r13zxyI+un9cW9md3a2zc622V1vCLg/BDwYAh4JATTe8j/JOg5lvvJWYNOAAS4YC9gj4AWlgEsE3O");
        a("sWwNcKeD3RXCLg+YADZ4u6O/JWwCMEXAv6wEkCPpfcjxawRvDBAp5B8N6y3glOFPCn4Omjtc29QmPdd79EuX3ZxuUWOD681YSD");
        a("bZlhK1s2r0Xow5HqULi7KNzEdg7nBnU6NM6kzWGNk7o2rHF6ASymsRlb3/5OU+K9HpJDtGwpZxb70VNdSEGtfrTFjNSi/1fp1R");
        a("ui9Yc+sV9bClNo6pNY5WPmfQZnkLJA02nZF2irmZnSUzyszAoetk/y9muMmtVfxxoZX70BGZ+7jTqUhsB+8DyzxPBcQJ7ztrKn");
        a("dlk6Jg/tPHmg3kLPeFAuJZ4QDO27ljaL7iGafc36riBbcK1cb8mo9f5reqoVWZ6JJYJXkO05WFKbF75+vshYUjOMurTV6OyY+e");
        a("0YlPHIhA4ipNlvkqugaxBnwAU6wkCrnwE6seZl3BPeBIY1VFK0Lw64mQ3kJ8iy5QKsQRlVjjHKKH4Nnf/gMqot0lqKpqwrUYtw");
        a("X5ivUFtLiLMAxupOyKv+2wOywDZwd4rceZE7L3LnRe4M+0UI2ZaFbNK5BH0esCrIM7BlZ5DiUM+41Yi85Cu6f2ILe3s6FVtDTj");
        a("qRJN1ikFxHkr1lM5OMkSTgCZEll1HgCWxQJ2N9CWtUe7cjrFVYDXlG00gVjBSYK8YD1F+kfXLEazjbybkSzmYmcj6krzyUjXz5");
        a("EQqn3kiYNx19uHkqJO0XZYx9l1/NSWvEh36BVLn+Ms5JWn/LKaT96RmM0p/N712waqZKzrRJ60aPzbjGVms+lKSnVnccWAN98A");
        a("B453kdnvK1MSiSW4F5U32YadF7IUnDWhRa8NNHviFFN3uX/Fmo7l7L7Dc6DlwOhplgmOt1hDm2/sAcPwBJjb75dYOt8TP4Z4QV");
        a("l54NUmilWDKVDKhsXSpDdk/fNnvX9KvyMI+kQyDU+R7iBaBvRRANTIYScWlvfetrzCK+EQRyVA1fo2Yt+m4Wgls8LfXJTVm+GH");
        a("E7Py/WWuoCrnsRNB6vvdUY01nUZCR/1MIhc0wxRLdPtO3h+xZLta9gQlcEHqEl+1xtrdaOgXEREvrWmyiVemz5Dsa9OnTBDEcy");
        a("34EVpk5lYiotxbu1rzmEXs60PuhJmj1SNhUkJpAIB1o2OGeNHGCP2uXuhFyfz62o54GRW1yzth0iiH292KlwCtgrVvFBW/NqfN");
        a("T935auXd/3N0HaOC4K5z8rmLWNxj4NitZK73VuUzAZKqtrs5VmCpXLjLdu7OIdJqJJmk40lYW1sD9CfDzkCziJx864svc508Vw");
        a("0thIH9k2mz9a979++KfpLURayssm7wiWZpmSeyont6gN88Knng4Geah9ECi13Ad3/P306vrO77t2995G+D7FrG94HFyv8MN+V2");
        a("/lMqujJGUj1+z4ABwbc5F4/Mo748pwH/3r8GrAFvEWWErNp37qg8RkSMT62SwRo30NNBzAdG4Lz+USQMAJU0WGdTGsfPmpIGSz");
        a("XI8pb4sZl4W8Y3YYdGMh73chYNx+4YzuKuQcw/sls4394pig9U0Mq7xVtiG4Hog4r2Odz4qJ7IIWMa3Vxf17rYxhasvvPnNhaY");
        a("uygqzv5f5EYUM2bhwbkevGGFVbHYrrgB18opJg2iUEkf4zoqcA0xpoUkVhsiPDBDojwgg6fS6ClQd7lEGcikPxinud1Pwn5sdR");
        a("sW5vx3KmYN1GrPPQQLMMvg9b+DKRfmk4OZmKDHOEEeZySxgm0vtzWuJc00hP0m7c0VTtffl+DgSoOFWlt2mS6nbS3d98f7Fklx");
        a("xmh8o8LVlvwbEjJn2fSTNMpOs7zKTp+lyQUlSDKar9KapxHFUshz99B4XvZwr/UkT4DL00FFUuk/Y3kd4ZQdpPHxkizeok0kNN");
        a("pKczKcNoFpumB2nbIrG4kd8cV64NpIOQwEKMl7pobkcJ3XdeV1A+yU3NXbK6en2XKdZM8QDgDwikb/y2O/pBEfTZgn4Z0X/A6S");
        a("2i5pVqSusL7eZsOfVx38hsXdRB2drPRHp7uzlb+yJbFaJcj2bSPibSogjS3x6nOlCoYgZSxbgov6vbydFOjkeRY09y/JQdY5jr");
        a("1+3EdR8T1+1tZq5PMFdHmGsPYnAPM4jjpt6Pm5Voq17RVr2irXq5rcrmb+7/X0bmaathySjwciK8+b2tJl6sQCd+M8gyl7uq6n");
        a("KnFjoOwxefwwOFpBcrmnKzm8b2z6tocnuwgVxRF3qPzVOn5FEI12w1uQjjqezm2wLQSZ46u6ejx7hkCzMfOcbn3YP3uu5ZWPqa");
        a("ZYBljEfTOMH6zlldWI9poPsHMb4o1urnH8H6Er70Aq26+hfFNn8kn4cPOTXBqVF9G//xDhug2QTZCHoJEKvb6bN48JQZsskoxl");
        a("bAwZp6N/wxAA1CtWrq7UDYF6PFBC1tDFCf6/1F+keQZn5/yDP0ErjZSmL1V1E14T2DyP5y/+1R+x/jPMwi83tPCCWazeZOCLx5");
        a("HBuUk6LQe3nDVtJyCw9CbA7aEIFZ1nwbdT79nucdqokDqEvlffmrmml0VHx32Zk7k2x0Iw+7MZ5gw92Buh0op7iskzd7Q2Ok77");
        a("bJYRGNMo3zMVb5Kp4ZbXxJfUs+L9yqlb+ivORgnJYzxBrgUy9i6wXZ1NS3YbgM6BBNfV9A/TR1gYAGamo9IBJgfCjgIPc8D++P");
        a("ot4Z14fN7QpWqK+zf9KCdvh5ZlJe1P6/ilWDPpgE9QUMLm+S9xrh3YFqxHmuoW6EjB2XCDkZKZIOtyPJbWxK+dBBAJSxCY3qdH");
        a("jayZMyNg+AJcPh+WKBjc8fOxR5/hjHlhH/d3AOTTfuXqHY2F+/7wVIex2cqkUofda6aOVpka8v5fw9DUDU+TvLm/l8Chu5LfVO");
        a("yLTM2nnpZnx6kVZLtmQPVis2Mm8JpsV9rtB+GqwKxu9XoG0izxLh2cpnVn5qZn9okDHPdAmLDimkM5ZLIZWO4dPS/Qohm0Whd/");
        a("vYjpyCQKlfjyBaB8u0fj8kmcy8SEDlKZ1OUnDGYRdDHi357W4+UYj9XuOgt7Y41zsmE4meN4MEoQQG/ho94Km1423nXG2jSMJJ");
        a("SELhVa0F5b/a9UkgRB5OoLWDcYDL224v3TdXu/GQjCI2hJKbvhW8rN7g008X9HH6vfy+eKHW2t3MotC8TS3P3y+XZxTpfCLeuq");
        a("wZl+LWagsr+87AkuwIbx9vXvzqYt+UmrEtuTRPlM9CxuV5C3YUfO6XL0YCzQGqw4vqIq0XajEkeypgnTYRrFJ2tmG50oyAhq3/");
        a("MZSzo2DaT9+qH6maU4xLJ7yJIc6zm8H5y+ewtJMgXjTrWAHRlNpxd/yXgpa+50XnP4r4P2bi/6SZv+jPUACCPcvgPs1SBsPsUG");
        a("b9IOTy5EJBWPR+W6bAoEDUe8rMsOgZvVS3/Ocv+6v8H1gmbQBEFHOetUZh8A+/JABe0PAEhlJ2RzfB5G+X4U0xHx+VBYdH2jlc");
        a("roblCjALxRx8xhqse3pTTIufiRKTVd70d5Am84qT1X9sN/67bS9jzoreXkTdmJ9XDbWUn5YYLeUbwPqszmDU/k0t4LO3qn2xQt");
        a("iHAushsKQuxmIFdrnAbAJ7W2AdixiL2cnYFoEdJ7BfBXaNwHSBaQJbK7APBfatwJYI7EuB/QBMxlcfeR66BjpQ92wQmxx7R25y");
        a("9F9DoeoD2eFlK2+sltvNcn36hcai5os1CLlMhOQl3aTX5sLlyBjeKoMN113p81NAujn/JV+oFnOs5mjGe9HrRcpi6YYcXuTPBf");
        a("9u34+429+Cj/8T186gGf/Cgq+z4I5vgpH+VnxVJL7la8Zl+g6D3YGu0ND9Wz/f3fOSKOlnBrNEnBBkbCYw/aktoDpsK/42bSP6");
        a("7Uz/6c9Msf8wI1fW97Af2gjyvvTXVkNh0h7cxCWdkAOuW3FlJeCJbsD7LO0ieDLBSQKeRbAi4NcInrKB4LSTf2cWL6+FTL/DLN");
        a("IGCKcXyelZwXU6hahfwiFKOth7JXl7RIjrdrDTsHVwGiOc7hVOl9Ps4iLB5CqCJ4KJ3F637Cf42nm9YTp/Fszkz9xX2nm9YQ7v");
        a("+nw2NS+zsHJGDSMp7obhThv+4vHu5PCEvIrhofcuK6cQReGJq8ZnNEyZBxC7pTAWn0vg/IVC4vyz7Xx3LmzxT1KvPw2mR5JW6Y");
        a("Z2EtO6PXclYHCfN3iUc0JsQXkgOfBoUeXwZLerangCMjwaTKY67O6KnHj31OFOfJ3wyomH10XwclXDz1V9dmL/5toWUH2S76pu");
        a("ElenrmgpODgGjcQ9NSd+CJ5zjUN0uKrwgIFyb4++LkV8J+Hr36BQesBteGJ5jQMxJSKmMxIhRM1FHFMydCucE9yeGxIUymP/Vd");
        a("QCa9cmw8S/QLMVuKpzEtkirAPmQGtj4Jpf22aH82KiW7ERtTUarJDG1F50z/wD4xSa2OGUJe4RvEbc0SXi1yPiPyMcvxKK326K");
        a("f+MexL++0BT/tM02I/6fbKb4axQZPxhWpjUXipIu152h+B3m/DtM8WOVyCFWiTheDCBX6PR+hYjXoeCyZLzNbIr3ZnO8ekS8F4");
        a("TjdYTidZrz/RfiVU3xlsXJOAvK65P9A/jZDdEwvKJheEXD8HLDMLUmqc+N5rSIVyn3gUhBNiugvMN4rqu6hx1/6o1AsAl3KH14");
        a("M+IG9Fcgu64mP5YNzQesUfAZYs+vxKt2oUZhJkgAgWcIbvh0VvS9DGyKfbhNircI4Q+NHn39v1W/CQaE4n35hXs2X+HHdXEcSy");
        a("9DQGOPke3AkvJxfYL+MDyQsSGA5X3bWFfroPswBwBGDVaot8CvQdXwj89D4vMUPkGxSOtVgyqVyhlOXqt1ggcn+sIviDOAOVh8");
        a("oLFMbUsGrCgzqH9GABk4rQ4wB4wLBcwIBbzNFDBGnQ9ChH2ewqqd+Kdxi0fdBEjhcPmwRnRVqbWcmrSRI2nen1rHXm/Ai5a7jW");
        a("vTyndCiSwlS1i246A7vmiuNTU3G7PhpcQCtT4LX0hirJDEirTxwBHDW/jgpQpXVdpojivpe1w+UOHo5apKOocd1O/gMDXtfBXO");
        a("qU5wShsiCKdASvUM2IIgbH/htPx7zNscDyK2bGJbgwI/GsAQdeLhlISDI5IA1s+D3tvD90FwEirpXLDy5Z+40/XASWIc5xmqII");
        a("BtQjLDK5GMSa5pX6GXbVBfARL0qM/ho4DPYHyRiof5k7SYEpyaPDjVWZaOHE5wcBkez3FN1pfS4wH7wX20cN9fuIs0fA5Pz9CR");
        a("8FHLZjaoborG51FPpHgQ5Bh8jVbg+F60AmGrAe+UCO/13yn48DN56tcxdhJV9XN8ga7GR3YVp+RjMMVXR6UNcnAhHulgIdLXN9");
        a("KWuToDtMkIOouCem3ovV4fgd7rHQSIEQF2wEcshm7cjjy8gXA+z9ApcFVd004J8o3CoF4JB/2WBrmYcw3afJQJNI9vjCGJMR5B");
        a("cn3buVVpSIJekswd/xvCaRY5XQEnwB/YEdWUT7sIvoyivQOwL8KemSMJWw+Q6cKjaLEw9PCc3Lv0BK1tSrDsYDr5v3+xLwdX+1");
        a("F/WygbASaHr4JfwA25kPM/oLD3yyzA2n0vN3ZLlf5fBbDuiN6njp41wUwNr0xMCU76GX2Q3OHcsDS0vmKxD/mzfVxoZ1Q0OTlb");
        a("Le3Bu1L8CAP0NjxpLUss3A0Gb0pmnY2zlJf1C9N6Tupdmk0LBN6k6TQ0Ongir57HZQXZTkf43Es+8ezjmnIpnPmdlcB5RVDGvQ");
        a("JZbNO8SZiWba5DNGCQ4Mb6mHLNsMoh/dA/xRYqrXTKB8sP9LJ/OTaLY92ek22ljQT2lAsU9+GZU63Rp7V1a5Jl/Fiyz0Qjc9vR");
        a("pdVHKx+csTPnf0mXyBKPzyi6eWyiQi9F9Ibg9wYrSiKZ21dOtGMx6Xg3lFjZiXlZ6+j9pE8Q3+kDRCZ7ikyWHA2xbcOoYwACDg");
        a("iHb8zDhY5LaV3Fr6ceTHHVk73BtCwkKkrW5P6tq2rYETAyKG+JKdmXrKomS1us8f3wV7K3f7w4u+8dlo6jhVPT/cWMA+Wd8Ir6");
        a("wAUGBzoLkMikGm3/4iCAkh8DzCsXBWhOLKhjy1uUkmR+IQ9uQWQQnHqaENSv8Qph1PNEKNHineJqRNfH5ydjoBODJkRi/dujXa");
        a("F7rYgqR1BZyp3M6V3v52HB+JOC8k/tgRO5nsipX375kAZbaQ+GC7WBDSxv0A6i3F1VNhOL8p8VnJ6bhNbbrj9+IBc+ieN9Fot7");
        a("pN+SdlguBJHw25PzYYjYB9c6yAzoa/qQ1cleRBMLicG0r5F6i8irHsP2+/pZHdTOajHk4vvVLOsCFvuYeL0+aEzho9s3vYIhxx");
        a("wMOeZhybAGS4ZNhTDxKsQyIsxks75BDiihTZgUk/RlrnMvoMHd1LghEKEGOXgv5m/JSL5/3F5y0cB41+TX2G38EcBPhvtRRDdR");
        a("0k8W38n3iu995I52sMCJr/9W/AFWCB4tYDvBFxJMRX8JVHnu1Ozcijbwjx1oH58QSBb0ASdoBS/oUDoCCGPFEoirw+165JPAvq");
        a("E43O8pgSQJI0uo82LvqUcUHxpjc00bS3MhBN2n+EgF6FVAGbq/nRJxDtC8e9aJ0wm5mk4X90+7D47UiPaiv0PJLHIQxsklw/03");
        a("2UWbOtVJOspVTRLjvxCOhtvH7JZndruKnY6JCOtNx/j8ALsRZ0q0ODsUUzhXFfPaEOH2PrstJzdLnNXKn+KcDSdT+RQPVWylCU");
        a("RBttuBngXe2PwYIF4b4ZAcqKxTRbtHPZebCul7+ahBGv31owSfRAk+zf8p1av3vAzauQme20qGJJyYN6zuQeH+aKQ7BpnkOknM");
        a("XQx3OAL3X2NKwsUyCb3p70BKQiYl4aRAns8SeeBYhLVEHDgAbhGRQoaMyIp9LBd3meK7WcaXQH+9AlchPISgnkKez/DHBPsCIy");
        a("PuIzl1AIqSjf0chrVkTq6x7rQuSPsnS4JBcZSqVh6lSsMpLJ/YJoBBH5q0F03aiybtRZP2oknTGrz1OJzVvhPZmsA2nlAGcrhS");
        a("KIYrDX2oZ0R+eqJNjY0t11U0q3FO90dOanix7o8otUQ4ozvCsYPJa3IfsTP0JDZ/p5J2SB1ovy0j/+6f+GTWR3bi1MtHuI1wRd");
        a("SpkCg79Vzp3HORLwH3jgXg3yRMpFq4yAciDPdn9HcQVfGRVMXZ/hQhx5BcvKiWmp5TMdhvg1MIL9YG+1ttBg50DfFlXEFJZKMt");
        a("LLOkh/vBBCbhxkAXH8QAEY2BbtNwl+9wlh4ZbhIQPSmgBRqJjX8COBKKLoHQm4CaiF0gdmttUsYCZ2sdITB/d/LS6jbk5T0yGT");
        a("h/kVVe1o/mwyCoci+qnAUEVR7tPii9od3SmVj8q7oM/2jyZdz3JGXLULtoN6x2ewu1eyNQgib2DlxNykaBWGf7n2HdHkUXyeJ3");
        a("morfXJZmzdAYON8Ej0Kd7q48a3KM8rx6B8oz7jNreT56Nbe/NvPtT1yce1AeaGsmae/N0j5GjtNSA8XFvrA4yrLYA1m0FIYhhY");
        a("HzDBH8K7rokVOMsrigE2XR2WQtC+0qlEW0gmB5IVvGlAjzXqt9Jq7L1Gu72OybDnjvQ5MRnLnqSaOfJzZ1YY29VGU7O0xUzEaa");
        a("Uh4fM3b69qj8xQwr+2fMsLwjpHDeK4UzTQjnbSHh7Ou/kRvwCAgo+stkhNV3BjAXmEE5Q3iznIJKVg31mUBk1XCfOSI8Vs71js");
        a("hAFkMt/TwDttTNiO7qJv1ko24+oh3/yxutdfPLFVHq5q+Wzyb9g70wArQN6ema3IeWbXuWJA90uu5ZR9o4UbHq7FSW4rukFO8v");
        a("xndCz/ZIh9TpizeixFbssW6NrlXvi9SqtwPdbfseYpTbPW0ot8MbrOVGrwT4AkfxGZQ17Qb5kidBnv4pyCNONt8L8uLuylnK57");
        a("Yo+2uW83i8PwwA8S/ZzXkFn7BnbOLPAusxPa47eYIgbAAqhH0UzEhoYP1jKldrnGvymbRCH1dy6UC7657rFB7cx6Oa+/uz7aEz");
        a("J5ymW1W++kSXawxXLkVNpoHEJ2g4A985maZF0kxcAZpNCmiM9lUV2b5eNtrX0wDRcNAlZ8rw+1cj/NvK321f/dG+WFbk6UQIiI");
        a("Qk/+oF4H8m+O+K6AkiorGGtT3yeQ0pV82Qq4rBhqAM3Eb7hfVWubr0MsiV/wch/39V50SoHO+7SA33B/+JzrHm8cUTjTzmb0Ue");
        a("A3XWPN5x6U55P+IBYq2/SRjydXOm03oeaWkzy3+LPvezPZH/uc0mwc9rCx1YMs6qQo1t7hWS9yKTvI8xy/sgu7jbh1OQQmc3cY");
        a("JVLinlogz8KfzeDdNwcg/4nGlWSZo5caD5LYpOfFfqxIP8r1KSpEpEaGkP9+MHqNd3lOj9vG7Rizrd8GDSiwMkLza7pELWfWBZ");
        a("WDmFYKEzW4Pn6v7TEMefSEeDFFJPcIjuME6LVZc2o1CbpDw0QR5KTjDkIXsz8vFNjVUerruYZP4rW/flclu4rxhjKhc+7j9zDo");
        a("rkWRHu75VJssw35Rd5CpxLCHgzkr+7vE0/3sjbJb+TfcjH1rxVXhSS9WPNsg6BlEK+a33//RKp7497azf7RR7kbB765xo056bd");
        a("6fjzPxM6PtgzJPNZCst8LyzQJEHeCyDvh/r7KBH6/TkM+qV+19MWouh3gKNJtysxYd2uzyXl/y38/964qWqhHDf9LzqsZZCp/2");
        a("+l/r/6T/3/BaF6HcUVirL3ouy9KHuu1Sj12XOxrM/Rb/xb9blJPzEpVItHi1pMQS26UIunoxYH+PfldsrXx9Zw3AsbWCvpUit9");
        a("BqN2/1bBrINT+Qd7VxnjRBCFt6UNxdLC4RYCxa24BooXL+6kBAnuxaVoIAfkgAQLrgf8oLgEOQiB4sWdKxa6HFLcKW92vrLtQP");
        a("HAHybZ++abeTO7M/Pe7t7s64ztRMSt7Y2W+ZNI0jdtU3yPe35Ytc2/8X+JOIbvS6pjmPCIxrDzbnEMn7QMjWGraGMojl+Vgxi/");
        a("Vev/pD3mP87tsV+qKPZYkEaylmiPaV+q9sieh6I93nxH+bBH57HfssfJx/6lPdpLqGP5/AGN5dxd4ljmbfFL9tj3AMbz/No/Z4");
        a("9rU0Sxx+I0ivW/tMfhngh7rPhKtMfuRyLs8TKN60/b48Ej/9YeexdXx9CSRGN4doc4hp2a/Yg94v3Owb9XzKPhWNrNONaZwMeE");
        a("NoktM5VURJ1LSo4p/KxczV9rQlP4y6k2WdZg0J4n57ORqe8Eg5/3UM1NRzHspdpI1mNenk1A0ea4Fvk5FQan81rkOwI/F8Ztxk");
        a("IS7Xd2SN7H3/+Qnljgegc/CdeiPx31F5MkRYjy5blc7sfnt1griqEVtW+jFdnpym3sK0t/9nVFyxejkSvxutXBdZpgjtSugHKf");
        a("wWCHklKwfvqKvNKkBxLk1aTL0jfkd38pv1aQz49MapwvsY/vegcf6yQf66RN9yXek86IapjOJfa5dL3DJSZ5iUlOgWRSo26YV/");
        a("/2+5y/iKqj0/yko8W3iTq6x046SnXSjZF0L5bpXj9nAvRTvF8oH9LClJPKXfhi/jpRg/lrRR9Ph/Sxi56P5KibYfqYj46SdFSh");
        a("o6n8UAMbV/St7aFnxrSSfIkSP6eltcgege8Im+cmpSxC28pp5RWR8+GsfU+NbfQ2WZJIpmgCkxmr+dl7hzL/r+PN2OpDM7LRpd");
        a("dhCtkPCjmXUM7NrDJCX6hBJzGyso7rIyVdCCU9+Zp+0XU/YZnnuGIg6SlL2v8t+ZVfys8U5LMqmegXi5/3i5fd+3qiZCgzhmU2");
        a("oE5jmQ1/6HuOvZCqd+nZAhfbN4t6V6sRn6dTPuSE6dyPfK85nIw/osiBNOIFYBFeADLzO+OM0AtAaXmCBH2oY2gw3WyhRSOME7");
        a("ezlwtaRYL2R6pLHhd0G6phIJnQM+VfzG+WL6j227U7bP3FTWK/pWkY9bvGT86/Z1SsdTSesGZ5kBRhM7bY5GQlvN3VDfxd6def");
        a("s/SMDeuTt4j/iC7pCqh94rlNfdJro9gnb+pHm/MV13/nm4Y5C7Pnt1mt1z+J6i3lFic5l6He16hP3H8M6hibZkd+Kj+b+2bx99");
        a("VghpWUJuwWFrX8FFa+GZXnhZWfYA2OUt42vYnBNqGi5ExGCyWxffpitchF/oQKoby0lEeg38l818Qlg5NScH9ritIuaOmpR2Yx");
        a("sc5e5mdbt1Jqk/Nu2PeGbUG+97+uxJmkGLribiTLHSB15NijIR3xYro33B/srOIo5d9Jf7AWZ7u4BrH52JotKRrGdjG/ZpvC1i");
        a("XHk6QK1M8ZaalmpcJYxc2upplbEvtN4eHWim+VURWKraxlvkUPFI8oPTmujN479bXgAfZd/zfrbWX4L+dl/m+y4uB2ncXnLWbx");
        a("DJ3uKNkjrmnIn5Gyo+8nQ9sHxhQlMjgL9U1Kivm7dmdurfq3RbiUul4bvjcKzitR19dY/J31ydV1GaizuJ/sTrPy++IN3J0sM/");
        a("32ja/Hc4SatPgdZbVZQv52qbFOj78wnYLv79U2GMRv8oZi/ylh8fLv7m9DLhIB+PbSBl5KrQsX0TKgJV4yh7DmJYKhz95WZU5R");
        a("Xfo6Iy3mwdaV0zymtULHxNBK48oPZ5W8rG7mD8hUxdb5UIMKvlF6knHuoz8NjI1f8fXQF9I/FFMDX9uapoGw0VoQQZIE/j/8kS");
        a("D2pcOtNWkIA0DJxzHXRnAEXyYJHCEkb9EpaIK8QTif421kucB7cCG4PkSm51rL63MP42gazjEunuNSoO+Dhpf7yDFuHPKBWha/");
        a("y+OtiyT74twhbr3AZL4f+qO9OnbNiLuS0fWVVuNLy6lxSyU1nlBFjdurUxzX50DcwOoPi7sQ17F2hcWXhsVNtVT5XGFxixKHfB");
        a("Me94uNgezvBK30b4M4ZiVjhIQsorwQjh33HLl4ynPi5NVLVy4fvXb9RqLv5u1zd+7K95MesH5fBL38weBYCvlfDCaBP1+Q/fLs");
        a("ysbqK5Ltaxdf+EOen61PJya4vidhAgplhIw/dX/WSL8Xkv2kvKVEyVKly5QtV75Cteo1ataqnZI0OC3VkkHSKUcMOyjNTKjUX7");
        a("dvt4jzNerUN6K+YYM85uMdx8/5yPnqIPSw6fVcvVMVuHUa6TuQ3jCnc2Vbb4GDacCPAs/OGtRjvtm8ri74NWDyDjlmzXg0a/Yk");
        a("8EdABNd+cDVYh1TslerGe6SnQ+rmDMmPjDw5N6E8uBnYtHej1dbKqdcMBi8LrF8xx/29xjtxbnAbsP2SeSsGLjj0MiMMfwvSJ4");
        a("5/MO9evVsPe0OdumNgK1U9dCbxnGdirw+cN8B1jR9b0lpg4L6pGd5xPu9jpE5ogpJGQ3+IqC2kKFgQcqCRISUdXejws/tyfrr/");
        a("TdZI1upGyTWHZqNuvtBLRxdU+lzj8CX2mJpnq4b4AG0Uu3Rzu7Zv4hjYDL4F9r6NYy7waMG+EuVXfF1uSkwfazh3H4U80HqMow");
        a("PoAiaE8AZHL9AHDAClRI4mYC6gBWgF2oEOYLT3h6XveL4b6AUGgKb3qB9oB/YHxgHdQC8wFBzt1GdaQI2TIqrx8NDcXtsSzn3Z");
        a("8MzPzjEX0Ap0AF3A7wWNvkgxbdGKRfq34vJxQDfQB5Ra43xAK7A7cAowHugB+oGGNhwLAG3A7sApwHigB+gHGtpytADtwP7AOK");
        a("Ab6AUGgJnbcSwPdACnAOOBHqAfaGiP6wbagN2BU4DxQA8wAMzcAecFlhSfr0j/W8H1k+fLC4xDua0oPA/8OfhS8INazuPBveA7");
        a("wG+CJ4AnxwPPA94U3AveHPwSeCtwH3hrcD94G/AAeFvw1+DtwQ0dOe8AbgLv+Im9u4xxIorCMDy4u0tgcAlSHIK7BtcQ3N2h+O");
        a("DuVrxYcCjuUNyCuxd3ggYnwLlvA5xgP/jJTdiP59zpdK5Md7c0AcfH9bCN6+O0uAF24YY4N26CC+GmuBRuiSviVrg+no/b40XY");
        a("wevwWLwee3HI0KwHjod9uDP24y74OO6KA7gbford2Kpn3B1Hxz2wjXtiF+6FC+HeuCL24fp4FW6Pb2AH38Rj8S3sxbexD9/Bfn");
        a("wXH8f3cADfx0/xA2zVN64UhvHjI5HYTzh6GuYDx8AuHBMXwrFwfRwbt8dxsIPj4rE4Hvbi+NiHE+LjOBkO4FT4Kc6IrQbcHzg6");
        a("boBt3BC7cCAT48PXcSlsl2e8yu1xMuzg5HisshenwD6cEvtxKnwcD8MBPBw/xSOw1dB4JI6OZ2Mbp6rA+HFGXAhnwRVxDlwT58");
        a("T1cW7cHOfD7XF+7MZFsINL46F4Dx6L92MPPoC9uEEl5hM3xQdwf3weD8EBPBbfw4vxU7wVv8F7cPhGXA+Ojs9gGyerzHzjFLgQ");
        a("blmN+cYr2zG/2Ifb41XYwavxWLwGe/Fa7MPrsB+vx8fxBhzAG/FTvAlbjY234Oh4K7bxNuzC23EhXLoD48f7JjN+nGEP48cZsY");
        a("Mz4bE4C/bibNiHc2A/fr6P8eMXOIBfYauJ8WscHr/F0fF7HB9fPsR84ABOix9jFz5wlPnBnrPMD15ylfnBS3F7vAw7eDkei1dg");
        a("L16JfdiH/XgVPo5X4wBeg5/iex+Zr6bG93F4rN8fSEu9FNmc/O73ob5/+Q4Hx+t+Hk87QNbMzPweDyl5kHohl6m7T5v6Ier+gh");
        a("xP/UjwfNl5HaR+lLovBz8PnjH1Y9TfZDX18ydN/Tj1QDZTf8PxJ6lXLMbxp0z9FHV3Ec5/zNRPU2+fm+MZ11nqT/Pz/Zb6Oepp");
        a("s3CfnzD18xaN4z2c/wLlRYUYL+e5SP18Aa6HebhE3ZWX52W8l6nHz0edcV1R9dzUr6q6m+e9puobmbeAqp+nfl3VPdRvUq9flP");
        a("lnvLeohy/JeZif28HxluJ5Ge9d6mMLczz1e9QPlGZ+GNd96nYJzkP9AfWNZbhO6g+pLyrLPFB/RD13ca6f51VNv9tYyAph+fVd");
        a("FFK93yd15adXt05zV88e04jjQ3xpIb+0UF+aFSLk93/CRIkSIVy0aJG+/gkbNWpEK4Q5LDTNihQr6s/e+PR0ZV7I8+QbMtjid2");
        a("O/kDVJN+khN5LnyTdkfDePJ2uSbtJDbiTPk2+C9e6Sv2x2D173u5Nk3Qntw8t+ob+mytGdB0WR+cZ+lXvSrZHHN+9pnFZl1rc5");
        a("ZWne4EUq1yTtGEleL3oZu1RWWfEhojw/XqQyZtJSkeV1qDePUxmzQnrpd/VhnqgH84z/slyfh/7mKlenuyTjC9/X+Dj1YDZyV5");
        a("Trd9NfSOWElRPMz3V4kcqToRLI9VV0jG2VVedGlmX1Y0dll5YJpN/uZ/zUIcn4c+eZ1wP6a6q8MNuR9bX6Gx+gHswkVc1vfg79");
        a("uVXODCyKLOuDN6rc/dC8g1xzAPtC5dxlO+X6N+KhKhc/SxRWxjeQcVEPZughD6Tfob+iysZh9oaT/Yc3qmz8IryMr/0g9o3KSD");
        a("nfRJD1w4tUxt4SyoxvMOumMmW9PDK/B/BYlXtfXZP94xpibKlcXfKInN+Lm6vcMyVKGHn9GWp8nnowJ+aZEVHWn/76KlcP3GTW");
        a("f5ixn3owq+aaIfPnpr+QyvKfroWR9ccbVbaN4JXrTzvCuBTZnPzbNvQPx3tH/eF8o9nXo0hyQOLWZv3pz63y6O4Ncv3n8SKVod");
        a("3Xosr6jTEOr3L+oGwhZP5xfZWZlg6LaG5Qrot6MLO2eSL7uzn9uVW2iOZI/wHsUZmyYy05f6FxzI/KeRMWy/V7cHuVsc+3N+uL");
        a("D6jMMO50BNn/441tlYsmJYgm9zceqrLghwzy+PgTjO9RD+aCzSNkfdz0l/qWnD+U7M8A9qks5h8YRcY/0Ti+ypeJysn8eXB7lS");
        a("tiNJH78w0+oHJtnTNhZH0mGbtUTox+Web3OPaqzJEqpHx/yT2ZfaOyW+nX0u/BzVXuybhQ1jf8FK6LejCL3HfJ/Lanv5DKlL7j");
        a("oeX6sFflnv6f5PEuj7Gl8tXh8+b+w81VZv3YXM7/Bh9X2fhMdpn/+lON06q8US2yjO889qhccWVkNFnfacybyqPNm5nfg7Bb5c");
        a("sH5h+A7ensO+rBbHv6k6zfIvorqsw0arqsf6kZxgHqwUzSoqFc/z36HZWj4/WW668509hWOfrBLZmfjXioyg+pd8hypJ1l/JR6");
        a("ME+drS/736G/psrSVT7J4wPYp/J1/IGhZH5nG0dXWTfrKfP9CbdXmfxNHRn/G3xc5cI0q8z6e1l3lVXfJTI//2CPyqwFQ8n9EX");
        a("8O4/aSZMzYu+X63fSXUlnl9dqosj7Yp3LtkZ2RZH3msi4qzy0KLY8/gIeqHD0mt7l/5hm/oR7MXdU+yPp46W+uMnTaxtIff77x");
        a("eerBTNC2nMyvQ38plX2LPpL9qZuf/l+1+mf5PZnj/tTiL/j9cSUqFrWzZcqaKatdO6sra9bMrjyZs2avYzdt19F2585ZL2d2O6");
        a("Ndo0XbnNmt/+1ftDBk/OHsV9I9yqRDelhfL3mAPE6WYl0rkr7QIX/4PFAUnqei1cDqZDX68rW1ZVtVvnxtZ3W1mlgd6Y8ToUi7");
        a("Bh0bF+neuUkISs4ts8ViiEwLW6Vd665Nvv5np4EfP2f4Rn3+yHNd+n/dbtP/h+bvxecTepus2Mek45iM7v192v1MWv1N+oaY9J");
        a("PHyWALV7VCw5ZNGnU+wPiCzc9tsohxpe7pomX89qUof6Nlz9nb8nN8fM5zj3nyUvd/5u5KY5opwnDL54GIUm+84nrjgRZBKZ6F");
        a("shUUZIWixCN2LatUy3btFuXTGBs1indj1KAxpj+MIWpijZr4wx/9oQne9YrXD+sV8cYzeNYp+wxtt9t30SiubjI8zPHu7PPOO7");
        a("Oz7850P6D1MFVHT8smucycC/N+Wk8u5AvAv6qnHUKDalJJXCBHlElDY/Dj/YzrAEpALvskeL+F6/dwvSB9jZ8p3hOaiurC+VFV");
        a("TmwWpmRdUOOCnkxE1QuFSFxlJqlH46qgz2haPJFkSdNaNKZMClEV7+kuHFWQKCSnFEHWtFg0IidLQpdFk1OCLMyo0STLk5NMSE");
        a("/KsZhuJEbikwpqmpZV+UIlwQqsnkRLxC9MyNPCjK7oQiQmMzzChWP70Rk1GZ1WBCWRYGOoa0uBnfgA5gcT2N8S715dVxKr9V8g");
        a("ly4Vcg1Cm2GHhwuxqKoILp5+iHHqi5WEqsQ6j+LpY0oyNJVQ5Ml+RY8kolrpjKvltzMeW8RS9QzdW6BrY6mWhs7tbhybYhpjje");
        a("lKVfrQ55C/05Ys65iurZCcgvletYD8Hbfui8djiqy6q72Ai8jfpqlX1aNjq/pbbWekt1h2NIwxm/WkMl09TmzfODiuXqzGL1N5");
        a("Mbe5a67JbWKjnxdyLbWGyk2rDUWqauT9zf8oQ9SS/bLBdcblRlHhwd+LbSzEWEixcAcLCyw8hSAgFFjoeogh4v3s/y9YaH749+");
        a("KvDH99kAXEWxkm2Q9ESM/9XpxnyMO1z7JvNzxnpDc/awT2fzlA5hyW3sXCwyy8zsJTiwa2sbyP2P9cW5nDN7meeMzgwePPPG7E");
        a("m19hcix8z0Lo1d+LGgtPsTD3Wjm4XmdcWOh6wwj+11lA/C0ml2FhrzdYQD7PK7xm5K+uoy2yOhDy7qKBDWw83KIcft2yWGxsLB");
        a("af3sUI6R2KxZu3Kf1vlE97isV7W0oBZRBWtioWzeNsqh3z1SV6nJ3dZ5Mj7kfZs6qvs1lkfUeRp3k/nvjE+n7iAz/PpzTPrEN5");
        a("bicGA4xnUgFR/xLNc8WG55JDeW4rBkc0RQVLZpef0jwLn9E8BcGxPNldLbHG0/OZNc9lpC9+TvOccCjPJnGU3XxBkx0rNjyzX9");
        a("A85xzKc1vxzESU9052FD6nec5/SfPMOZTn9mIgJuv6afFkMD6jTroW0V717iupr2ieKw7l2czstsyyNMeheU59TfP07utMnk3i");
        a("UFRPlvvn/Fc0T2mZ5hl2KE+PaMyxq9my+r6m+fq/ofnOO5Rvixhgj3NxVVENutoyfy4/5pjOwDHezkB7Z5D96QoEjm73HcX+6w");
        a("34vN09wZ7uHvHoK10T39B6mfiW1kveoXrZRhw5X1cS7IEY95NvrP003P+Q+Y7m6drPGTzzwLTbvYqF31G+aKAfKAD5eTNFIMrn");
        a("gB6X28AGnG+T2+QHMubdGuygDelvmfX3vc246DD95a4xeAqQMyM/tgqtzt9c2e/A3w3+vxv8F8A79wPNfwH8wzZ6kO50byj/5b");
        a("vQ3vOIo/783bCLe4AoV7jPbB+leZ+SyEIPc0ifLVbrJ/+jzfx9f7wvsNFP+M2N0Y/rXaMe6W3YyTsG5lB/geM7Zn2szg8TedhD");
        a("GOkZ6GPuN8wLV2zGVehDAvJj0SQnxa314dXdlvz9mvsv6SM1AfuIgu/FBnrPhh4uqj5vDvE8T0d5gcsB18ojLsXqXPc08oE8PY");
        a("y4XzWdD/w1pKeBwiXABK4DmEJ5fjSF1u6rq3H/b6gfGAZqwAaTX3cB7TRRx6/btsMmy3ITSOdH4Ufab77yk8087YD1jbv+VvcG");
        a("v18ISqU5WhuuX+B8zfx+pvml6/Br/aVaTth7Y/jx8vk9YU8C6gXyY8tQiT2r/zfkAzPAGk7gw+0gZZqvNkJPfsRzJj0Kv9o8h6");
        a("7TTqT9NtZOtg0Zbn1DW6y+X+j+4P3NZh6/vzXPCZNcdmJjeGJcLc/TEOfjahpY1oekJHSmDAxMWdhLDpivYz9+8PPUsR8f7MOH");
        a("+KLpfpX/ndar/0CsFzDZUcEkl9vp39Vrbmfod0f0tx0MzALzQC7H/hrn2wm4K3AXA6Vdze0TiMdiSoS/f+JHGnqUEM+a9Cu46f");
        a("7nOQjrFaFnfnjdpvgRf69+80e7Sf26DoYeEQ93Vdtt4aDqebanrTruRVwDeg+1vv9n25EOfkIP6j0S8xJgoQPXdZSB/k6UAxba");
        a("ref96W7w9OH6cX7pWJznOMgdXy1fQDx7AniciHo4+nF9fTh/AOfrhxwwHYT+BtzV43rI8Gfo5vfe6FdpxDXuL4c9ZDfR9uQ/yD");
        a("o/Z5Y73aH2dBrygRowBeT2lEXcO4J8ILenvAS7CuF8p/81e1qGnHcU6UBuT/lx2p68Z7j/UXuSBpE/DHlgZgjnAWZCON+ZiJ9h");
        a("Ht/K/rUqe4Ldebh98uc/7lfZkrbH2YOc5R9Yr39F2wL6BnqBEtDjAm5p7V/ZLjQgq5MxxfCysPrR/8zzGw/0N7AVracV6DFrp8");
        a("9G9/9Kj9syP01Zi0x/0Jf5uYLbY3prWj/eg51pjxrajesxzOKUvy+FfLO/z7WNtR53CgVm9GR8eliZjic2l/UZht2Z/X8L0ONS");
        a("I60vbZ361JrcjtSna1t3lT79LE7pM72NuZ9XazQLvXmRnzfp09tE62thnfqUWpypz79qny3M36jHZxIR3te32UT69Qvb0noqOK");
        a("yfr5X/jyM/dgn1nq8nE3IkaSw6M7zF7D7WZD2erKC95rej20VoW1+75a/bWL9P6nrct4AZYIGn34Dx43b0F8RzwAJQuBF2D/QD");
        a("PTchDgwDU8AcMH0z5G9BOWAB6L3VwCwwfZv5PtC3usa0utXY0Uz3t4EWG/99m7P625+3b2f103r9zHgLwfwF21n3szDaacVjs1");
        a("9xne1VeHZj+pn0HOwcqAEzzyMdWADy8y+/jn71Buz8BfRHYBqY5+kvQh6YBeZfQvmXEX8V/TGPcsDwKwYuA6VXqf6F1mLHFNrl");
        a("/cWvTnJ9dMApTz28j/vkyZ5HW3fYhPf6nUFRDPjEjnaxi62y7+o9qqu9L9AbaO8Ieju6uo/p6uzp67jS5d3B9Dxm9pPuaONvOs");
        a("RZ/bR8/x+Kqhcrq/7gwaQyHd6BHo+EnWgeAw7jae7XO1bwPSOqR5NY0TLA2o/yg7fuTPNKrZO359yN6dfhc8zPc+ANP4N/J+v5");
        a("sp/b8S40n9ZD4T8t8XZAO/Nj59C4mlD00m6iyVHlAiWhqBGlxBPtZ/bHt4Lnsg3fiUOdZdf82D00FI/IsXF11Ewa87pdrJ/beT");
        a("sv7Urzml8n74K8Ye+xMN6XKQ+W9tWAceuu1uO0D/Ze2I3m89Z6+V7g/lfGrx1CfTPRWM3w5WrczZr3CtLfaqV5eQ5zFu/UhdXj");
        a("V2sIrb1m35w+L2CyczP/3Wl+C+AvldAB/F1KNf/dGP/puAX93en71uIeNJ+lw5w5rjWHBtVo0vDlltp4xYZndk+ah3C4M3luF5");
        a("LiesXSmMJaexl/izg2obw4EixBTf5WSB3bPH1+PEbkY8ecOX9r5Jc2tbGtiDX5WyI/GIvLSYv6m5B/ZnRSQR2W18m5HwE86Iq+");
        a("QHdn9zFib3tHT09pXe1Rve2+Y3xd7cEun/fooPeYno5A75U152ng+mui62HZZP52wHr6wNvCuu1xWjRWbkyL5wyz3BYoN6RckH");
        a("QR7RyKayWo2w4jZ54mjjKsp5f2E12kfFCO6QrDetcXSswo1PWplbyJ85w2E4tR+j/PTn58aIjk0Ts0JlI8RsdFisdpg0O0ni9T");
        a("lQTR345AgZaqDXV8N91cjPfmrVe3fyi6eV9gS/UGNb49bWGfOoJZLle54avtU0IK4yWXq9xA5fnMVk4QIFexIWkFYpJQKzbBy1");
        a("du7Cl8Xl8AxxyXM22UWfyCFs1xuaqNJ9kv7fS/wuUqNnLMf1VfyrsvytdsiEh9TdUV5nK1GwvIS5zncuWF93PfEBJ5Xr5mgfnH");
        a("DZYq2A/lsSA7+x1d3MvL8wXK1AeGVh1h2JDd6K72V/LzmBc6TyDDVPECL4+FwOuu12+a15vPk/+BrFfYH+VNC1fXU7VQkS7x81");
        a("QuNPP9SinbaH+L+vna1UXkNayJNdTU7a072fFtcZo8reRQvg2pHN01vLybQvKFrjy/nvJCU5JD+ACUNxZmNv5swzjNy1cuUJR+");
        a("sdVTjsvVLhSzbyhvOXnF6jxYZAaFW9bP1+lt3zKIqULvpKwx+wrjmt1dR96rfzGa3+G+B1zCIydc/U75FA3sbjIL+ZbywiTi4h");
        a("tYyELChzROZqCq5JM1513+nSLjOQjlKxek+N2o6qD6/Llc9cILvuyibb86krNczrTQYF3t5iknZ63OwxcrLNS/7hUuZ/WCPrwV");
        a("ffVYzwDelZL8NfTUwXUkNS5X++KVlFzgctYvANnrP7qnFLi85YuoZjxctdWfB7RB3vrFCHstQtcf5vJWjnrupJ+vX38GWc1rnX");
        a("Yrk/27M8Y/4Mvrq3Vwk1fqOQRyFo5i+IgtRQe4XKWjFU5WavxKcTlrxyU9FtSM+jzeVh4HyPNzr6j/EAvFw88L+fqORu5ilA61");
        a("OMkEl7dy2MFZR1TvmufytY4v7vKatZJ+i8vVdxzBY9R4mOV4iNSWeo6XPztW4ZBsz7s7dVmuBS5f5SCBd4QwtCUuV+1wKOxBCp");
        a("b9JtknjQIHFNnvx7DwOgsHsPtXkoXXWTjAzf5n4WkWDmgoFq9l4SMW+jcVi/ezsMUWxWKMhXr+G63d5j13DENUCR21L3Y2omjl");
        a("SUeG64u3t3n/7xH09adjzvBT1fIUZ9lIrcoxPK+1W/OcAj//kTbt5Fieg3hAw/36CGueEvi1eWkeyw7l2ST2Ry/t23yWkojjex");
        a("Q0T08HzaNt2pk8txVHZfVCZa1F/V6a54oNz7BDeTaX7HaEeQkuiMUvw3c1qP5ZOIrmMe9Qnk3isJycKndQz1HWPJeRvthJ88g7");
        a("luegeqkci06OaPhuC80z20XzaFSdyXNbsTQEsbGIvTfAd2hInvNH0zz8DuW5jcg7J/9uD80zdQzNQ3MozyZxXJ2sIJo92vRe1v");
        a("z7P900jwWH8tyW9c+RmfJMYf4Ymqfko3kU/irPsxv+4d//GVBkzXBxVP3+T7fN/K+H5tMad2a77rg2z62e2Gs+6/adQrr3WJrP");
        a("RNyZ7dvC7zNSPKoy4uEemmfrcTSPtEN5Noust45cYBgyiw+w9qJ+p8l1PM1j0aH228zbMyDrSXz/jBqXlmx4ujRn8txOPENORG");
        a("U8q+G7asR4lD+B5uFzKE+P2BuJKLp+RjQekyvfbZxgzXcJ6bkTaT6aQ/luI/bN6GhTfDeQbNeFk2geGYfybBKlBJv0xthiG3y/");
        a("kOaZ9tusW3Qoz+3Z63I5cvHa5Dd3Es1ztpfm4bnEmTy3EQNxNZmIxwL8O5X0/SXcR/PwO5TndiWerDH5uJvupXkOBGgesw7lub");
        a("2I11wgOttH8/T20zyyDuXpEdc+GBCs/F5AOEDzFUSaz5JD+TaLxovHwJQSuRjft6V4NgZpHkLCqTyNZRT8VuoTaZ7LNjwnHMqz");
        a("URyLXnj6TDSJuBCkeb51Ms1jzqE8ty/5dy9gk3qMR40n0/P63ADNI+dQno3iyFiln2H5ZLo9FwZpHssO5bmjOCZfoETkmMnP8N");
        a("YAzXf+FJqPV3cmX494WtwYkcaMb+mUv0tN8p07leYz5VC+OzO+5bXw4IzvalN8Z4ds/KEO5bsD49u/WR2Knq+DLP9eOL1vdnGY");
        a("5jOQdAZfen/7+lE6toHM58c2IVGNxCfX9lHMD1mP929Bf20jNvNsh+kxdzx+z/cEA70nGpgDugLAfgO1kwzMcOwzMIX8NDADdP");
        a("kNzAMzvQ3m34/pC4yVdVwYttavD3qdk2zm9/8z/WpBxIFZHu9HXLTT73go2F3Wr3/EWr/z0OvS6TbPE/8z/aZOhh6BOWCex4Pr");
        a("0K+vrN+MZD3O8vHBN0brT5hxln6FAegVqAHTwNQpyD/VwALSPYNIR34Y+RowzeODpvlJaNz4JN+aSnGMWutVgj7TIZvnJofqtc");
        a("D1wPU8BL0OGyidBn0hPQcUhq316hmx1uvubLnvhaI6GZVVSwVPQI+jN3uuuG3vd4fufe/MwiW7n3DSkyH++xzdfR1eX+Doo9o7");
        a("+n1Btj+wv6+9J9AfbD+6W/R2d3T0dPZ3Bq905VF+ib9fRDsJH+K7kOM2613qtJPvjOr0vNSwId/DzEGf/DggNDwTS0ZX92DNRm");
        a("IzevRSZXXx9NhmNTKViKvRy5VE6bpm0X7AMFADpoBpYAbI61rk9gzeBaSvmO18nJ7H+s+0Gc8d0i9qnrNDw6PimeVnMg16qLd/");
        a("8QAXvc9y8sjpIzdb5Ht4/uR+wn7T7GCwmR1Irruvs3eYzpeG6euZmjpWVS3yGyvzj9V1ROvumzxFVsn8oHI+mT8sJ8j8Xi1hI7");
        a("+Zvr4/aDuXXadhIAx3AQuWvAEbJFgYOU6cy5LboiAuUkEsK8dxG9M2qZyECh4ekcSTuplSV0LCm9Hn+W3PeOI0PTo5p6vu+Pf+");
        a("9but179SR6//k2y9/o/1D6//jZI330uG/e+E+XldR7f/ZhR4rk+ogyxv+KEOev8PdXjg6qD8/v3PW++zQx26pr32P3J1aNUhV8");
        a("bzHjfUo7YqtF+uHvNpsM7VZdL5r48719+H2u//0imv/5sq/OPLzn8+jfbHL1r/3xnoqsKWHvun/MCP6+Hya6wA1dXlV40KTz0g");
        a("TwOym/FCvqBC87h8OwMK3/X6Qm62C+/nwbzp/EDDZLTRYJevPnBKFwvRHGqS0HTSvfm0Igmjgy4BfZLw0aacgmXj+JT3/fKY8n");
        a("gB/VZPacphPquLYVwc2PnTmNn+EPwRWDtfPM0XZ7Y/6cefdFXUp4akSdR7R10flmtNqTft+rtugLc5CwPm/LtmLdc8pgEJsrSP");
        a("M9dbDvsSUBaPcQc0SkBuOYjoRR4DB4gZ4hBxhObjiGPECRqfIs4m7toNCcbRwPahdrPZqJGn/QoYp4gDxAxxiDhCzBHHiBPEYw");
        a("7v6lLkw3lcvh/jOwipq7ZuyunnQqTvId/FUVSqURPLUg/YGlFMic70u9ooUTkWRuRaOi5VbtTJ8dYotbsYL38avd9ridZr9OH4");
        a("1/VMfRCVFtXE3c4IPXQAt6XQl3qp5uO1VHtRFVpO+s7sdFM6valF209n6xuyyb56u7iK57WNdf3642pkiJ3IqrF8ZJTSYNKvQU");
        a("/etqq61CvLoA9BP3GEmJ95+ZLP4tGCk60y/Qad/WR1UsWYH/DH2pzUdkiwa4hopNaL2fwsDhBn7nywJASbgE2tTSlYex9hKbc2");
        a("o2CtPmIUrL3vRCy6jP/tq9dvlq/J+/GSGn75sipUMehtVaHtap0SY+9PSWAtrJ9Ruy6lmZv57dfX5N3nKZ8shCM78flAy2NAGQ");
        a("eY/H1Xcrlu5/y6qUma8owEiBniEHGEmCOOESeIU8QZjociDhCHiCPEHHE81acz9VFc5Z8SPTGjjJHv9uTKZrn6NHT0m4/8c96Z");
        a("837znm26jl19spDOrneVy0K7W5aoiq6RohKFmPQhWA42ATvMqTrZh2KtrEZrQ4FW/iLbnLjPsG0epBQigPM2HB9SqDnniFuBGP");
        a("lFM+fazHmHxh8Qb7s5H4X7fErApp7nFAn2ln9zx/9kcd1+w+MdNO/4p2Af0qHd1lGwD0Dmjcfjf3bH//yOn8z/18NV/v/re/vj");
        a("8/cOUexruXtSqFbJVhU31vvDyPkzpw0DUJzL0GZISzL0jrsOVZdeh15CL+2VS/8BDSRN27tcQ5qMuKAUl8T22SYHW8aMGTNmZG");
        a("RkZGTsyMjIyEcoJnpYkm0JT5H1fpJO5ECW39NzfP9Z9fn2EAleMdM6+U3PbJeSIv1jWvOKBP6A3S0anlkrXVLLPzFMv2y75Gxu");
        a("DyKmRZbZitqc9+TONPH9PAvHGTQUN8zgfuJ87RC23i5YpGU1DKse4BT7NsSu1VquO7tl+CRYk68wYKMUSuZGINbOU2J6xLJ9Yp");
        a("+RWpCAJgvhZlD/QuagN8jcbn0PBVL1c8jFVn2r04nWr6F+dpH6K4K9oJVGQ/mctmNZyv6wm9OkrkXPtxfr5j3q75pes+xSeuQY");
        a("NVpqF+59Q+nKD9vyG8EZMQXXNTpVGIqUxy2k8n/Z+QyPKyeUNhc4FtIP1XjqivGP0pWy7V4Y/hH1ff44lDbEfAiHP2dI6mBdKm");
        a("dQ1hyZgHJWKucwDjbQvFS+Y+U+K2NHFeWcOP4Bu/1EGDhA/L161PGOffPcS/WaTP+gUuz41ENrV0y5gYYFKpUaMy6NoPqSp+PI");
        a("9ZnzSDuIuuPsAKHjKvRhcByRcZxfJehvoA8D2Ihe9+P0A+i5IDMyzJMYANcUHB8Mzme1IPLNaSFo+/K1DqyC44KryKz2FeO8Bc");
        a("cFQZEBHV4kc//A8cHK8bYWXLUYFwYVh28UVB56LvDXe6sfnwOOD9AhPHedPCFdcJFA2tU7FTwGFxPscnL6zyFjMz4pKLVux8Kn");
        a("4ITg0f6OAkJ+ChwX5Mm+103QEJwYjMl80IEO46JBk4+q2cmBCwMbiGo0nBjAgZ4LPow+6ef/DpwUJBh8VrMjcKExv5tXIMgVpE");
        a("WD+01BM395cJJhvF1Ug21wEQN29YuK7IETjMyHu7qPeQJOMAbnSjqOuIxbGG1JWcWcQi8ZVlf31D1dg1sYQKdKYgB9jJFytJ9M");
        a("4pqCjxgTh19VdNZjXKzBr3+g67kBPmqY635TwF1wkjEsUCnP6/bkdcZ64KRhK8XoiiHjR/q5/a74nd+HXjRUwU7l+AkT0QYnGo");
        a("VgE+omcT2By4GD/WWcxE3ARY0eP1VDJS3GJRsZYGE4bMU0cAp+uRf4x8zlG51qyaeQll5UO7/U/3kTxq2llluRk0vosZBekfVj");
        a("Xn94qX5PXtc8X7c09a6m/j9zV87rRAyEEYIOEOK+ZQFB3AIEEqLilpC4xCGgoDBZByw23ijeBUJFzdFT8l+oaBAlJT+BkhKPPZ");
        a("MlXnvechQI3ntJ1t/32eNrdux1yiXuv08e59frTkMC/jrLf5rjB/0TafwKwmMC7jqbvxPH/0v95XN91j7Av9R13v54mYkj9Ypn");
        a("rZrfCK5k+8fZGfWPVzSusuknmD633nnrztULN67fvnM7k8/dFEcyQ2XraopBipF7tXtgd/tQxAiOPj6axm+nflSXw8rY2h61Eq");
        a("kc1CNZ3AVpQMJp6sdGSDGwoq7836NJ3KauXsAqmNu76TeQDhQrKlKKf0uX31skKkwHt4906AS8oFVUynpBPZ6Uakyfd/F7ErpE");
        a("BSSmnjMk9U9RPK+ZlHooayWMHKsz4pyY0/hPipAvWYJnMhPqhXZa/exQEDXw5OwwWDjfXpSOvjWCLJ2HLeY8Kd1tjK5lzv8/SO");
        a("uqZakey1Jc16Ww/gBU35bxUGjhOafNEA9a7FUPajypZ4FLB/apJ/ujfMDU/M/y8dyTJfEP5/tPxLNw3yZscAd9PibTagI3B8oe");
        a("FTdLJa0SjfupnyhKbkVjdA1h2VnVeICPTkpTiKmCNqVLldDdlcj3dGoqZMU8dHGbEedbJ4VAjajwjPJO+q0ZnZoQcJHJ310jH5");
        a("UKxpqhPyVfjLR7C+pnxMAijtcbBaQfeNSLjN6Ojp4zvInV0vaI9QCJatnx5wrEEEZyqKLx54l8psCgV65cdF0zmc9YTxOVqbQu");
        a("LKC6uI2Iw6BC29Yh1tqvXDpA9Vg+Vtn5CUoThcajkS1t/01ZPcTw5aGOMhMTF2Vj23vMT0gA5sc50nG/G+W7JuGY9Q1WzzNl8w");
        a("nn3DrDNc5o+wfFAVE1fmniEcxt9mivdg9jumfAKTeZz0O0Py7wj32YSjx/Au1evZhIA3c49LGlU7p7tcsAChifBW4e97f8wiX1");
        a("egML/7DP9dUjw84Xgbj1tHDothcUnH02dHUAAxC2n92k9qitFzlSmXLG+FcRP6RuGxjTP+kQ8chl4vsX6bTeUpbf+Xe3lXq66B");
        a("upLv/OLr91OAdbRMW4vdT+XFF92LF1QFQh3FtbTR3SOyjL+vkhAQEArKUUTqTHw2qeDSz4b7S/QIG9HArP6d7Clj6aVmOnhUMm");
        a("zv1oKra8sa6psPPw8yn4NaBbVxnV/nrg1WT1ti70axsW88jKVr/MrT9vyek5CoSx88dd89TlzMxH5zORH8+UqwlQQvL5i3WEm0");
        a("CUGGTrbdeSekCQ6h+kN6+kVhOdNdJO6R5I6LZUxIREwJPJ/7rFcdN7tILpXxu7uh6DkFy/oPvMxoKnZ/wI0JhoDGD2PUR+JlAY");
        a("pYpfxgS2fkgfy1dXra2hGR3l/f9Wd+hZPEldEQXXn0kX+nBXM2UvRhdI4lb8/RM+9/N5Ofvczo/XSzwn+PY/fW7nEs64eErlfi");
        a("zH337/Gf/9dcz+9gc3r1y/fKNz/dhifVtVi1vn7om5Dx8mhCs4vLSfpw8r+Xi2b3z2+xuMG0eGAjtFXyp1Zza5YkbVYn2vXn3n");
        a("UpjGz5VaWqJd23cPwX3k2bhugcfvESGS95AGbUWvKTcfKB8Ob5oxzNKq8BRqycx8BWy0UPKN8rO5w7eYqf3v2kz514smOoYffc");
        a("F+9d3/ZZ4zeHDbx/nwbdqPIv9fm0K9SPj/zLxkZ9aPB8H99wQ45DJ+DOkN5UQOdT0LkuBHq0IVR5m4YKRHBCjJzNO0WCrGqn5S");
        a("4f4vxg9tdSQiUSNKv5r4/WK2KJTRqkjnI+b1CATkntcj3mftcYy8/xPzt0D+vvGathY8Jjl93PhwpDZ4s44OzZJ6hBwHJpLLlG");
        a("tgXaXbw6LURglX+7lybezqwPI11gZjN1rkxn2Ev2E3QiKQj8Odb2xwS4T774ouS4g5F6KQtRShClj/ptV91Ni2UNl2hpsLjlwQ");
        a("T3Tdi3cYEEPiTa8HwOY8cRm/PLMXb+EQIwBw7aqNMy70POHAE1kPn/Tof5QUjZOpb9h4ZKG6H82EW9iscvW9PsH/7NEMIGz7u+");
        a("UdJWlrocBLHPlIXS9+l74aQWqWnzYL/bKldPCi1zioEDkHsvt6L0OgpV1ZUUXvfgFFcMAlcGtincz9/qYcfyb96pgXgp6MPTu8");
        a("mfSbU7w+3qbNT8auWCeOGIhenYQ2Shc3SCmSbeiiNChFRBGhcJf0m9072AK82Bzo/p5n+xmL9XmYCgF+M7bXM/M8M3s37x/qeC");
        a("nJB4yoOl429SDSAiLc40R9RDfsouQbgGJ9Rvu8mVgoQNGeznH3uIGFT4OxbJlS+nNQzowQnk/JXy8SCPX4Wv5u6H0jfp8u9olR");
        a("jFyoioL6fYOgKGIl99lTb7pSh2mq4yD1ENPcN8of3YREv5+3w7QD4VDYI+VHYOO5L86XSW3xjbqucL4E3OeFHj6NvBBnmHBRnA");
        a("PqiwKa/oV6svXr94mA1n2bdYpQaiEAP/NXn6v4NWEFpcozwqz6mAqbLZAl36iPj9RrZ9FvUF9Ss3WVIvX6KAD/UukjuZDsR9YH");
        a("AQWm1vfm+j4K+uxcj//B8ak1cJEm7cxVLq2GZM21Cxkx+FvzEEbTuF2n8+uQHFGU3pj/ZeZVBzj/aQBP/++w9K12ZuMr3KFMsb");
        a("KPen7AAuap5Pj8fpfv/2f93+CN1B6WkdjNI8h7qKw2J9jP+HV2E+7hGdkJ/qfMj/ebp6SV0OZ5W1/8+vP3YmNAmsDRQol8i40Y");
        a("oUqOC0UfkPf8vFnBz6fmXMjPX5I5+dBjoNhvG5FDaOtliJT88PqASHpr4siv5svl2vy0Y7zBfT95d+oV67Feqn+dHKsPquIhmJ");
        a("DdcbzA8xb+kMxDcd/JQ2V7f2k2h39nKwechMbPzhkpnov4EomJT0uuI1byXUAS0ubtsem9bIsirvqAyIBmvqe8zgZK5dlBzSuh");
        a("ji9kkHzPec+/TkA9cod0+xMRzf358Mo/U7DqXCZvS0DzHGysDW7oUNpAfCN/X8m3NgADjjCxv4WJ3qv93bfNdMuaikHp+6w7G1");
        a("eK+w7LSA6FfuBb6/p03N72+d0dPf8oEInvnN8xJAddPjmpcFqY/ulWKjvh+uiipP3/Rx7Qh9wt06SGKB3/ABGIYKZYhfxcrS/V");
        a("XfD2KruUxpVeX0LI/LHoiz2Gz7RdS6vTQBT+JjYmk8ZHfWHEggUfwW5qra9CQa8PFBUl6koUxW50JdqVuBMEQVDci+JO3Ii/QP");
        a("D+ANGiuHPRjTsXBcGNfqYTJ53EIQouDie5mXO+b85rQi7cu220iU5Gl9m6fH9NvVT/XkdcctVGxv7K62RofM6pkLdfy4bq//rp");
        a("7+IrP1Bbfu9y8sDZo1TYpNbtV3oTpYMd6GInetiF3diDvdiHA1jAQRzCYRwBbN/dzybnzh47cQYAqvz9nusK96HSwhVwF6ilA9");
        a("kVpxuNpWgMKT0X8oY4/fkbkPl4uQ5Yx8F8aV1u/bIalvVqhbX3uO4NpUPJMKKaQI3661TZrxKnf2tvCbwF55K4DoilDpZ2RSe1");
        a("e5bZa5vsvqCFgOgAma/0OlyKsOu9dV8vueRcFx3tW9n4NfjJ7Fl6/Wh2rX1UwK27qItDmFma9wpjVQnGoIHBdCUGkxUYjJdjsL");
        a("gMg2GIwaiOQS9AKLZCnFY4DnVrOVoTidbYR2vRQ4t5ao1ctBh/+Yr82g20p8vQnoRoj+toLwZoDyXaIx/tnkd/62f++g30pyH6");
        a("kzr64wD9RYn+0Ed/5KHfY7zExtm6tBYc1oJIcz3DD4lfJ35AfEl8cqFv+Y7PzfVxA/G0jngSIB5LxIs+4qGHeLQUcc8lzhriqB");
        a("wluRw5Dn5FXtWYjruZO50jXY9RgIg4EXEi4kTEka/ma/MBO/EjZRNFrPSxMql/lZ9z+JmvlH9A/pL8ffL3yJ/cRy7518h/Vco/");
        a("7zs6Adw6BTw9nuPUDNGkfZP2Tdo3ad+kvXw0zwv09YXy8pSxt8BFsEB2+XrqKq5mfadYklg+sTxiEa/npljzPVvs7x1ngeeUz2");
        a("eKfWPloPJi2pj3s77YrPtig4cNQ0pac9vTWBo4OobW2pvfx53zgLwAdKgtcaw08zoXgSuUrxeKMcns0/0nOg4asxrG+0tA6zLw");
        a("kLp63A2c/LXiodfqninnrJ/b8TWO9Xq5h+UL+Y76+174cRV4fI29cNXYT1LGS/XxKqOPK8R+003WPOXzDc2xMN/X+ljLOl3LXl");
        a("qrfIishxJe5+Nsn3lqZhSx8v5scy6ind7DTM7dBu5SXlD0Ws3fjFXm38xTee7KZ+R8jNSaG8aa1RKrRQz9U7OGjZxa54fGrMxb");
        a("LoHkvMnHohjT8pjZtcXmf2jzXSuZr+FP94GvlKeU/83FPIuVtubRzE3pvEqMeWa8E9jvy2fvn3r0j+8TQQ2B2AJaG/Vt1FLel+");
        a("I+hyWdVEfhkt/nWr5fjz0BvlPwVOe2eJ4W9b/mquzd2l7/lnOmYK96v6u63JJzM2f2s8V+npm2dl92v4Yu5W7NTzFm5Tn7yap5");
        a("hv4UhXH8eey9R4SQn+y9905GRNmrv1227E1WZEYoI1FSyi6RTVmFvEIo8YJIVrwQn64r7nPPPa5x69tzzv0959xznvOsc84vCx");
        a("rqxr/an0uf/w91ydtTDnl9/jDR9qZR9+Z18Vj4r5T4Gck1gOy8JHISnAF/kyNOvixyCNQDafgLXxHpCW5fTjtuzz42dVxKv+7h");
        a("/mtK2viT94ZITXD7eooYFuT8+cj58+Iba8X2T8/pZ8JNXCP0R1uvr8/6Za5ZJufZG895DHX6NrN//+38V98T2Qt6A0+/zn2StS");
        a("efDfr2uDZ/Pnhf5MsDkRFQu++y9UTbzqHB+lh9CKnb7xfNI0WJoeG7WI6H/UXkB0SeiRQDVYBLf517lGkm1mQZf21knobHt2+K");
        a("6Frh3FJYj0usrz4/dGfrL7LNJnl0y/e5LI3k0iZPrmXy5JB3k+H15gT/Lb5YP/jzfZDTVEMvTPlv4lbgI3NK0dCmvp+HcQZGPG");
        a("hNPGhNPGhNPGhNPGgdnF81iH83sIXc2AJ2wF6yTHgWmeocBGp98/HsKlfBCJDGl9o5ROLM0aieT+W3s+Ap+DGGgO/td75s4Riy");
        a("cqlsya3yBj6lPpD6VeovgTeXQf+S5OGai/uc1PiaJoWlCevRhPVowno0YT2asB5NgvWoF/PfdQuo1C2kcj6/OmVwhN9KFVZpCV");
        a("znJDFfuykqm4mcWpcsSv+0F56y1CtQf0Ld5zt+niORX6APldCHSsF+vE5sDlOKqeQuTg2auO6hb0SHYvHgE20LllDZA/Wdr1uf");
        a("nbOkykCwgbY/+F39v4DnPdgArB/wr/OflxPPtDYZnUqRE6Y5y9heTuU1KF7erGeQN+Qhb8gd5g3ud2lz98g7M5+ksc2ppPIc5K");
        a("/sX58V/L4bVAT+/NXK3dqD9VVRWyifwU+Bw1W+fzkf5WngNnXTV+q8YVxVlQugIvgxxsidRTiO8DdzPpuPPB774hu/2pg9nz1c");
        a("TeVLdZV2UBNnnDmFK2a65GN9xeNaKutrqyyGCs8j6Dbqe6BuuafX0891VLLqqqwEzvzJjtHa/tHoWPfUUzlVX6UeVHi2Qi9S7w");
        a("21srY+0q7h2AYq1RuyhtDYOtn44Gifl/ub8mAEffhi5U54ejVmHaFWz515q/f+Ktr3qiYqBZoyB2iatT4H74JmjAUqPCegK6iP");
        a("gP72/Ay+bM3RC6hz3MH5L2e/jLUk7WM5A20LtlCZAk3bZhb8r0A98CdrM6WlymdQBHj0LrW952+tsgacb2V1xb3m1dqotAC3ae");
        a("PjOwDPRdAbJOov1NrBprYqg9rRC1R4hkEXUK8IdbW3uVOR9uROHYiNtBGeXNR3UT/cLp0ele6osgocpo3wFKS8Edzu4J/vO3jy");
        a("dkKHoD6+2fBsAhWBkcsf2UezzipFuzAu+knT3vrgB7Qt2RV7gf7OPubAtx+0A+Ede0jt+YTfZ/vvI/znaUm5RuJdQthX+rzG87");
        a("2kcWeKSIb74QwyzyDzDDLPIPMMMs+Ea/ZH/Vt9iOQ33WJ56qn+KkMG4HegYdu/yJk8Z0uRceSiTc5w32XGMVil8RDGAXXFyp38");
        a("dh98BSnWw8rFm2vdHYYeD8feoT6+2fDcA/WA0w/0ifqB7iOQLXgDv/B0ozwaFAGRdg2i33nI7x/BYuDSGzPv1PFiX5ZKoZHYIN");
        a("Tl68s4fFmDUSpDwXnaKfXqlNeBYyApl7D+uNlo5j2GPmgjPBWoT6P+hLrnbt2ztuZdAvXcxdi81O9b3O/DfXtucrFcgdy+75vz");
        a("EivzECuJl7xHFvYcxanftyaxB52s0hEkfT+NH/Ddk5lyStl75BFSKwe3zdeI2fywGdj8TOwBmmZvux/eorPIx6B/8l+dJbR5Bu");
        a("oBMxfnWnyYTbyfo3IURGzD6HWjuSo9wHn4DjzLJgUp9wNjgC9+X+P3h2AKiMoqJ7LK4fSPLeZhc+DNr21+OTeysuoyX+UQeEKb");
        a("SExLabOzF6pkX0QusuB7j1nUC1A/v8DMzXx3LTynQT2QlLM5xx/KKPuP7y9ROQKeLP7e852lrP0yyrwL/BLlpaAi9fAeyH+/k+");
        a("W6twErVHaAc8B79mPjiD9ux3SwwiqVzGrkt1JT3b/9y51Mkj9X8GCNyrC1+F8gPPepT6e8B6o/c7d4ztHHf4cX1adcMb24s06l");
        a("73piGvR3/yE7CN+VjdgH9LsPXx74cP95rD//HL+ZfTyo9419swrBIoii8PnHLuxE7O7u7u7u7tYXxcbAwsDu7m7sBLuwu1FUDG");
        a("yszxjz2i8+uHA4Z3ZnZqd28i74md+b+Ik8kvk7bLWZ9jyLNCqgrMCol1/f5/Xp+Mo26Ot5du4xrB/AltE/GHO/WsNkGRdQC7CU");
        a("cOKqNDagAbgP4f4qXfZ8zsf/bj89LPvpYdhPZy+d8S03fnMzvuV+Z3NX7Zu+qu2EgDpP5l3jjXe9S2+Id2Vq2UT9ytln7qnM1Y");
        a("DAr/i/g78C0ygL+Ed7KzEjBFOEd3mKJp+nYCIe8Hw648HMgA4A3GoyK6DL4BXArbPcHzgnoCVAXDfwvwwtWFzj4HW488Gftxm/");
        a("B2yNE0/mUofz+GbgQFjGhWbv9+e9/sre5Qs/Vtl+3f5+af0QI5RiNALs18d41z6ifFPfWszabinpXOTrw9utZJL3G0RSu+tOtZ");
        a("YFtGpFQNtX4h93Htw70VoFRN3iDqC3LP1QTr3fp+/LswTOEWiLWWiLWWiLWWiLWbzN8VdpS7+GfZUN9CWrA98dh2JGDqbI5O3z");
        a("9Ab15+ybGHdBOeIQV87N9AlbiA83d1RnY0DJcJcF4sqFuxx6C8/NebkxVh/F/4GtxAn/yjyo3DbSsJ25wNZff8dI/DffQf8Hf6");
        a("zjd+cYrPMpv/iU3w/2v386xvqzI6u/jLg7oPngIPilNpcszNd20ub88eUexq69jF2wlS7bnuar78ewG/P3fqVvObQ/oIUH6I/g");
        a("H8+zf38f/fQh5seHiRv+9THYp5czdMOu+d6RgPYfpS+EfXnY8/vPx327XocfDyjFCdo++FG9WmHLn2TeBTaDH9o6G/P54acCug");
        a("nSgV/xX/90QBuBgDGf+uF8dsAZvk2QDvyqLUv2swFVAwI/8zsPPztBO/B1PVg2Kn688GyeWZynT7rAOgkW13E4Pe52cCB0EN7h");
        a("0n0Tx5Ev48h8iXUMyEc4cdW4GFBb3IcuGHX9t/aoRtsfciWgcyAf+O764V2/HfKLOYhfPzx6GFDfR6wtr76Pse5j1gug3Ad3uO");
        a("u0iRd8W7h/Na1Gv/jT+o3xOqCM4OKrX5urDMXvIlAOWOdYRvv9yubK6KP9/d+0ny8bxOkBEHg/nwCMFVH9vobvJ41+5+tvMG4w");
        a("p/T4vxjUfdXPY99OmGTEm+zdGJTrm34+dAinQ5GdyhHev9OzWWfOu3/+j1mO6E6jYxIA9nYvvzb3tdPaKpZT5/TkkzjftZdtRn");
        a("vx65XlP7ApJm/WM3st/+M22DCz01gQD/xof/M6zyNmcVoK/2r5DcT/mazUDfyjuLNkc6oPBH7kbzXPL4Ge4GfvzpPd6QAQ+OG4");
        a("/6n/NPdOvo43aC6nXmBpTvdL3+wV/MbM7TQV/p3xfThhLoF84FfeUzGPU38QAfzOe+7jP39e0gf/rK1swN910BNY43b2fE4jwB");
        a("pgPQ+X36kB6A+s55fePi/Ac/BNG7PW98Z4nqGg0ylwkTjMcwe+HWtf72ZhpyxFnGqCH+3FPuN5kaJOLYH3Z9nof65/Vq6pijtV");
        a("AfeKOfOd83j2HMQtYX179j7JFPxGLelUD/46nV/PE36WvrOlCFeaNgL7uH53vFhO+LugHPid8+juZZxugHjAp9+fDZr1U5Y+u5");
        a("zTevBV/f/CuvcX3L9kJ22nLXVVpy5gCvjRuUqCak6VwFL8BURc6H5gAbDifcn9MtWdOoIfxfuA50lr8P3C79o8XAN3D2DsNZvr");
        a("qlP4TVXTaRjs82rY5L5fNzQw1g2/sA7ZV8+pfH3eAX/0HzuMYjPGxiZMbMbY2Iyx1v+xpwjXu4FTO9hYh9pngA1+zD/ea/39by");
        a("F9E6ch4GJj36Z/3RYkfVPyB+4R/lfDXMJ/hmZ8v/CvzMcG4rdwc75V+LP52Nf/GprtYw3h0rWm34G/3mP35f9lGkKRhpDfpCF8");
        a("W6eZ4FAbH4+3dY7u3V/lPxT5h438DG3vlLoD6YLf/w8SBhuG0NgwfLbH8dV5nGkzQB5+kX9sc9DB3tf3+Qm8K+uglHUQytpR1g");
        a("HK+gBl/cG2Hv0+hl8vg9zdndr3os11+7YP/2ptaX7P1j3fdu3/Dez+8f3/q+GUDJvfZEff5jM0+QxFPmlbpNt/0+Z56RHD7vzr");
        a("fOPHumd8v38exydb38//JzJtfQeNcHo2kvELNuwuzDB9RzldG0MY+Lv1++Crf4TGOm0cx/wP/tUwecczl51Amxj362Fe47/0JP");
        a("oS+FfDHMR/xCn0yXAgJnXaLPj7ev1szzgQLaSiNQuqaPQv0ahr4jDPzO26+cH/0l7/yhm+/X/2z+2X5jltBflA4Ed2qnyT+n/9");
        a("k1eEaFJJUG1WENWDe4F66GFwquhSM3Q6uBYYhq4HnwRj0Rfh+2ADWjGksGAH+l4cKUxc6RA6ApwEnESXiydNBhfRU+FE8aXb6H");
        a("RwLfAQXQ8eCV6gp8L7QdDZQXQIvgHCou/BIRNIUdAR4PggNvoi/Cwh8aKVSMoGUqHzwUUS40aXg3uDYuhh8BpQDn0RHpaEckDD");
        a("iplUaoSOBzcD7dDt4KGgK3opnCyZ1Ac9FV4LBqPvwdGSkwd0PHgamIheCqdPIS1E54ODpaTc0BHgBqmlXeh2cJuM0tG3Gt4IXq");
        a("C3wFczUQ5ziB9elZXsobfAM3JKpdBL4au5pE7oe3CRPKQNXQ5Ok1eaiU4HBy0orUTnKySlLiKdRaeDN4Pb6C3wLfAQfQ+uVZQ0");
        a("oOvBM0HQubwLPgnCoi/C8YpRF2hYpUA8dDn4EUiGVnHKoyRli54Kbwet0IfgmWVJM3opvAesRB+Cw5SnfNAR4ErgHroefBK8QF");
        a("+Ey1QgDfN4F/wYxECronQaJENfhINUkgqhI8CbQCn0FnhZZer0rYZDVpV6oiPADcEwdDt4CpiIngoXrkZa0eXgcNVJJ7odfAzs");
        a("QF+Em9ag7tDt4OQ1aa/odHAJ8BRdDk5bW9J87sOtQDZ0uzcM3Xl0HVMAgPFBUU4x59hqf/ateAiKYFAEwaAogociKAZFUIy9Kh");
        a("gURfHsRfEQVBtMq6gqXq0Rxdhjf4i9lt8f3/m+pmky986997VNOuU5GKYbvPjhxqxDPhttOuXoCPdUR3wMxuuEv8ZE3eCNK65T");
        a("l/kiTNYZP4gpusZbeSZKriOegT5d57ajXKeu8AwMfMjb+cSj3V+d8M0o6Sr/gCG6wfM9wqxJB8f4u3NEOuYz0KJT/hOxDo41Vo");
        a("zQMXegolP+F+06OM5+QofOeC7G6oLXaTd2XeZ2VHXCjxxvvLrGs5HrOj9/gv2lc17sRNeqQ95slPU82Tzw/Qh1jd85zRh1wX+h");
        a("SQen+zho1jlPGO180FV+DKN0znMxWsdnGC/G6OBMaxWZjvlCTNQZv3uO7+nWBS9+rjNTh7w9enTEV6DQGXfjO53zx+jXBa88xr");
        a("h0iZ/CwIe9D/+D9XRwXhCsiSad8e7nG4uO+WS06oQHXmDt6ZAfRruu8ZgUuso5Mp3zB5igSxe6d6hq/wdPsNrF9ogucYI5usbP");
        a("XGJcOucDLnVm6Qr/hH4dXGb9YMAjfi1/jEG64Lsvd190nRcdayw65I0R6TLvghYd8/WIdZV/RZsOrnBOoF2XeAckOuIj0KEr3I");
        a("lUZ7zEOGtPh/wQMl3jAVe6jzrk9XGPLvPumKxjPh1dOuW70a1r/BZm6oL/wBwddPqncXhbl3gzzNMR74fPdYVPw3c65evQr6s8");
        a("FfN1zgte5foedW28CdbTZY5R1jEfhaE64QyRzvgutOgaz0Cs6/wpRugGL3i1PatDXgPtusy7ItExH4UOnfBFSHXGd2GsrvEcZL");
        a("rO32GCDq+xNjJrSVf4OUzWdW691hzqmDe6zn7RZW673lzpCl8x3lzpjBe8wWuHDvkiBDVv5wVvtN91dpN1NMG60hF3Yj2d84o3");
        a("+zld4osQ6YwfRauu8e8YroNbfB/Era5DR7zjbc4HHfOxSHTCl6NDZzwbqa7zgbf73LrCt2CCrvKrqOo6r3mHedBlXrRqXekSj0");
        a("CPrvAldzkrdMZP4Dud82fo1w1e4m7rRIe8BgY85mPyThikYz4Ky+iEL8LKOuO7sJau8QwM0XX+FE26wTve42zRMfegRVfutZcR");
        a("64J3v89c6ZjvR7sueN37zZXOecQkZ46u8C0Yq6vch0w3+LYH7EFd5esednt1lR9/xP7SOS9Vs6d0yKfgc53wNDR0zos8Zk50yP");
        a("dhwOPGyLs8bp3omN9BSRdcesI+0hyciGadcA0tmoPvMFw3eOku49UxX46ROuPfnvTaoYOn/H4Pqc55m6etBx3xAxiva7z8FOPV");
        a("JT72GWeOTvioqT63TvhjTNEFh9Oc1zrkLTFLR7wf6rrCp6FHp3wdCl3lx9Gnc34TDV3wz/hDB93OKwRPuB7eDAN1xOMR6io/jp");
        a("V1zm9iLV3wzxiig2d9HDTpEm+GZh3xfhimK3waWnXK12G4rvLjaNM5v4mRuuAlnzO3OuQ9MFrHfCtSXeVedOqCl3jevtNl7kJV");
        a("5/w7Jusgd16iS0d8B7p1lb/CLN3gI6abW13hVWeYW13iZvyhI94fQZf34dMxUKd8PUJd5RcxWNf5M5R0gxd/wRmlQ14TZV3mnT");
        a("FUx3wkIp3wC2jRdS7PtNc0B3ugQ8f8AlJd528wSTf46BetK53whejRGd+JQtd4Ovp0nT9BQzd4gZeMV4c8AcGTxsXTEeo6f4XB");
        a("usGLvmyMOuRzsZ5O+VY06So/hmad81wM0wX/hFYdzPLnLwzXJd4UbTribLa1oTN+EKN0jV/DaF3nbzBGNzh61XmqIz4Mk3SFz0");
        a("JNp3wjpugqT0Wucx47x6XojCehX9f42NecM7rKb2PQU66fz3vdeatTnoaSznnQG+ZBh9w51/3TGT+BSOfcQIvmoPVNZ6yOeZe3");
        a("nKE65j6M1A3e6W1j1zGfgDE64S5conPufMd164xPetd4dcLT0K1zPvw9Z6yucD/m6KAnCK7GPJ3xo+jTNe5437XqlA/stTZ0hc");
        a("/BgKe9nVeaZ8y6xFejWWf8L1p18KE9izZd8C0fWc+6wcHH7qNOeBzG6IzvwiW6xjPQqeNP/Bn8U+eUDvmzz4xXN7jzc2PUGYdf");
        a("uo865B706YI3+MqYdYUzzNcZz/zaWKZYS3zmN8aiU2771hmoK1xDWbMXCXtWB5wh0hnPw3Bd8JDv/Xpd5rMwUqec/uA+6hrP+N");
        a("H86jrPbfg5XfBGPxuPLnM7qjrhPkzSDV7nF2tYl/lR5LrGX/Ubu27wm7+7Jl3w338auw7+Mg8Y+Ixx8cS/zZeu8XQM1nX+BCXd");
        a("4CXnW8865NVR1mVuwVAd85GIdMIXokVnfCdiXePpGKHr/AkqusED/zFOHXKKRKd8LVJd5dK/9qfmoIrxmoMnUNU5v4VJuuDgP/");
        a("dMB7wSpugSb4FcR3wDZukqh4F/E6dDvsLXbD/XGYcL+nrGVG/nOzFY1/hAz0Qp6wqfg1ad8vsYrgte39esR+sy74NUx3wKxuqE");
        a("r0amM/Z3uMEEHfLamKTL/ANqusGLe/5JXYf8m2eg9GhfaAp2wcBproF/wTI6Wsz3iqGkMz5pcd9PpxOejkjX+U+06sBzSrbGcB");
        a("3xVFR0zr1IdMHHeW5Jh054V88a6dIxP4C3dc69KHTBf6FPB6E583yShubgWqzVbT75FZR1nf/DUB145sgQRLrMG3iGSIsu8/7o");
        a("0BU+A6lOuYqxmoPHkOmc91/G3OoKX4opOuMnkeucz/cMkVk64yfR0DkvtNwCwR865J0x+Fnj5XNR0ikvvLy51SHvgCYd8XFo1g");
        a("nnaNE5Nw02zzria1HRVf4Ao3TBy6xgjLrENyDVVR644gJBpw55HMbrnIetZPw65nMwWaf8ELp0jV9Eruu8+8rGqyu86irunS7x");
        a("A+jXNT5v1QX8Yc7H4dbVXJOOOcd6mm1OY9QBb4UWHfE4xDrjP9Cmg9WtMYzUES+3hnWlS7wbMh3zq5ig6/wDpugGD1zT59Yh74");
        a("NZOuZjUdcJv4seXfCpa9nLusaz0dB1/gZ/6AZfuLYxPO99ePl1rENd4m3QrCP+GMN0wb9itA7WdSZgjK5xuJ61p0NeG5mOeBQm");
        a("6ITX3MA90mU+Dz065eM2tEd0wud7Jsd8nfItCHP3nadisM75/o2sPV3jjxHrgpfa2DzrkDdAuy7zM0h0ztdt4pp1lb/ARN3g9c");
        a("vOHF3mPdClYz4B3TrhHs/hmKkL7sd8HWzmujBgunnjbTBIR7zt5taMjng4huoK34BIV3nbJmtGR3wIOnSFT9rCmtcJT0RVV/kN");
        a("TNJ1Xm5LZ6Mu8c7o1jFvsZXr1BEfgvm6whdjwAxrkm8Y6jp1laeiWee8/NbutS5xO9p1wuMwWme8/jbmUJd5D0zSMS+2revRIW");
        a("+OeTriFZqtQ13iFMu84P5yN0o6514M0QU/tJ0zQtd4JkbqOn+DUbrBu2/venTMJ2C8TnguJuqC+9GtA8+MWBEzdYm3wRwdcWdk");
        a("7+uMH8SAmT4v92KQLvjhnVyzrvFsDNN1/gatusHVnd1jzcFUXKJzHjzM59Al3gaTdcR3okvXeCbe1nX+BfN0sIvzZFfzpiO+GC");
        a("u/6Dp52xavHTriQ9CmK/w0Ruqcm3a3H3XMLyPTdV5zD/tRl/lfVHWwp33R6t7pKk/DTJ3zBXuZK53yI5ina/wdPtcNXn5vP9Yl");
        a("Phz9OuXJCF7y/rzGPuZTl/l8DNYpz4iNRdf5P5R1sO8CwQEYqiv8GYbpBpf3s981B+9jhC74xv2NXVf5PSS64KWGW6s65Ntwia");
        a("7y9AOsGV3n9Q90xuoyX4+Zusr/oK6Dg1wL5umIj8LnOuFx+E5n3DHC2HXKt6D0srHzKgd7vdAl/h5NusHdh9j7Oud1DnUfdZlH");
        a("YpROeBxG64z3bTMWXeHFD7PvdMhHYpZO+CG8rWv8AQpd8LKHO2N1iS9Gv874YczXNV7vCOt/lmvgvbCWjvkrDNENXrxizeuQN8");
        a("JwXebRaNMpT0KnrvHCR5pnHfKe6NIxP4puXfCmR1ljOuKj0aMT3vBo169j7sKgV8wV92KwLvi5keZZ59yLWBf8F0bo4BjPZvKc");
        a("gorOeC7G64LHHGf965QfRU3XuAfdOmj3e0vM1DGfhLpO+Cr06IzfRqELXut486zL3ImGzvghzNc1fhUDZlt73IdBusHTTnDO6I");
        a("L/RkmXT/RvHLCezrkXQ3XB8SivIzrm7U/yY13hLU82Xh3xkejQCY9DqjPe+xTng465OXHPdMRHY4pO+EnkOudVT3WPdIkPR4+u");
        a("8Gf4XDd45dP8vkJHPA0DXvVrec3TvY7rMqdYWaf8HIbonFccbVy6xHuiRcd8LmKd8cJnuKc65I3Qrst8wJn2u67weZioU97Qsw");
        a("Lu0WXeC3N0zIt0uGc65IsxYI6Pz8+cbf51zr1o0gUn55gjnfAEjNJV/gGjdYMXP9e60iG/iYm64EFj7E0d8kuo6zpPOM9c6SpP");
        a("xYDXfF7uxSBd8OoXuB5d5j3QomOejVjX+c3U9emC/0GHDi505iDVJd4OY3XET13stUDX+c9L3EcdXOpcRaE5OAR9usJXoqEzzv");
        a("GHznn2Zc72130c3utya1XHfC6G6JRvR5Ou8pNo1jn3YpgueJ8rnCc65mMwVifchUznPBcTdcF/4x4djDNGTNYlbkaXjniJK+1f");
        a("HfK2mKUjHo66rvCp6NEpX4tCV3kq+nTOl3Yau874L5Te8Hmv8iwVDNEV7sVQXfA6VxuXLvOZiHXKd6NN1/iYa5zvOuFxGKszfu");
        a("9a49UFb3+dseiI17revOsy74F5Ov6fYrsMlSKKAyg+dhfYhYKFCDY2dtfY3S12K7aiomJ3C3aLLWI3drfPbjGx4zcfDucg+9y7");
        a("996dh8KfV+Kp3sZnkfCSs+Ccc3zXdAGuMNf+65DXo6HexqfRVl/il+iqP3DKee6SjlwYQ3Q53o3R+jA/wQz9gTfOd4/1Nj6L4/");
        a("oSf8Y5HZhXX7zQd02v4BNIeNlrOMki76dT8jmk15c49mJ7q1Py3mXWrw9zDAboyMFyz24deSDG69H8FzN0sMLzBKt0Nt6ATXob");
        a("n8U1fYlbrLSfui3/QNwrftZsdyGk1OW4A9LrPnxprTugIw9f5+z0aF6GOXoFz17vrkbNCTa4hzolD8dpPZpf4pr+wG02+my6Lf");
        a("/AWx1sspcIrnovLrjZenQ5bozMui33Rw49mnchnz7Mh7bZt6j5LzrqYLu/Y5dnl2az1H7f6ch7cEAf5soHvLcOuTuCa17Dh5FQ");
        a("R/6KlDowJ93tsP3RfXgJ+ugV/PKI+6M/cOajzkJn4/zHnIUuwC1xTbflccedhZ7B25DwuvPifye8lw5O+n8S5NGX+Mcp33kdnP");
        a("bsQiUduTVq6bY8Aw115DNoqS/xnTP2RMdw9bPujw65M2boPrwgQq/g/VihD/NSs8rr9ArejXv6MF/FUx3DH/FWB+f8ux5fdMhj");
        a("ENywHj6PhPoSxznvOa9Tcl1k1iGPRx49gzeggN7GRy945uhLPP2i77KewUUuOV9djqdhiJ7BGzBeb+MGZpGn6rb8HAf0B65yxf");
        a("3UIa/BNb2NzyJGX+In15yr/sAZrjuXm86UVyG93sZ3kEfHcJUb1qxDbn7Teem2vA8t9WGOQVcdOc4t90en5LMYoi9xrtvOQhfg");
        a("GlilQ/6HTTq44/uOazobX8U9HcPBXd/rW17DOZFUF+AaSK9Dvo9sOoajed9yOnJGVNPZuDtC3YcnY4CewTcxXMdwx/vuie7D17");
        a("BNx3C9B55lui33wnHdhxfhnF7B0YzuNR35JOLe9tk5eYx91il55CNr1qO56GNr0+W4KxrqPjwDLXXkHeioD3MMeujIwRPr15Gz");
        a("YbiOXA7jdeTmmKrbcoKnnm86JVfEXh3yNBzWM7jWM/dHh3zlt2eljuHgjzXficPeK0JHLoc8OoY/ooAO/rrDKK7L8V2U0zH8Ew");
        a("118M/nRksdcneM1n34JCbpSzwyMJejR/MyHNcr+A/OaQO1QTbc0pGvIEbHcIrYZpl0Si6LD7oc/8J3Hc3SZkRw189y6rjmHaLm");
        a("TSitt/F9hDqGf6KpjgZbq8Y3d6FDnoNJegX/NB87Qwep/TmO6xVcOo0163K8Ftf0Nv6MezpIGzuojqc65G94q4N01okvOhuvM8");
        a("/6W2/jm8h2z9r4D/LowNzr3oLmL/Rh3lgotvvu9fwG4/UHzl84djBVF+CxmKNn8Cqs0Nv4EdbpD1y5iLPQIffHAT2aX+G4/sBZ");
        a("i5oh0dm4JG7pcrwBMXobn0fc+86Xc5tVTaoLcHnk0yH3RBHdhz8Ws3c6mlfNgrY6Gw9GVz2al2KSXsHfMEMHJbwe23Q2jsFeHT");
        a("lzSXuus3ElPNVt+SXe6g+8pZR7orfxZcR94Gd5UWlr1it4P4row1ywjHXqctwcHXVbjmXWs4dOyXmxRBfg5VilV/B+nNOHuV1Z");
        a("d0OP5kWI+9Br+DuSapc4OIoC+hLPKu9+6hU8q4KzjpqvYYaO4Z9YoIOK7j9WRF0pdjAI1/QK3lbZPujInavYB92H5yJpjLXxoK");
        a("q+J3o0r0YRfYkzVfPZdQHuikp6Bt9CqD9wYjOVLXVK7oqOug/nreHsdAFuhHW6LQ/DNj2ac9d0droAt8U9PYOv4amO4fy1rFkX");
        a("4Mn4olfwfvzWhzl9bfv5yLlzZeTRIfdFAT2aU5l9LK6zcXn00CHHN+s4QKfkhlil2/IubNKH+aQ5yJ36EverZ316NM+qb516Bf");
        a("dr4Cz1aO7c0DPnsX3grI2sT2fjno2tTffhJSiuV/BBlNOH+Tmq6Q+8oYl91tv4LProS7zbrOIQfZhHNrO3ejQvwmm9gic3953V");
        a("M3gDgif+Hq7Wwtp0yL1RRPfhv62ctQ5bW39b76v78CS01TN4A7rqbdylnfXoPjwbC/QKXtEeOnLRDvZKl+NhuKdH81o81dv4Hd");
        a("7qD1y7oz3UIa9C8NRr+AES6hhO3sl8jk7JDZFet+W+yKZH8x7k0Yf5KoroGP6J0jow97cVlfQ2fowh+gMn7mK2U6fkZZikV/B+");
        a("7NSHuU5Xz0kdcne81H24RTe/j3RbHoH0z6yHm/e0Nt2WR6GhHs3xevnu6JR8CR115NfooT/w/t7upz7Ml7FEx/DRPvZIX+Lsfd");
        a("1VXYAbYKduyzX6WacOOVl/69QpeSISPnem/B4p9QeuM8CadcgbkU1v4+QD7adOyZVRQIc8CcX1DD6Ecvowf0I1HQwya4tQZ+MS");
        a("aKrL8Xa01X0Ge5YOca90Si6MAXobJxoaOxiuU/IrjNcfuNgIv+N0Ob6KdTqG44z0PNEpeYo5vcN6Bi/BW72C3+OL/sATxnqG6B");
        a("k8c5zn4Qvvyz3+s1v3vAxFcQDG6+UDGMQgTdyhG0OFUaJJV0MtImLoILFWYjJVgkjjpQiplrjES5n6EYiRoYsQi8tUYiAxivj1");
        a("e/QmT55naXL/555z0iWz6xz3Lts/OuAXjOiIkyvtsTHd9DgmdZbnkdV5XiyYSxf5ZM29rWt8g6qu8ytq+ou/172/jm3Yq7hv9q");
        a("a7GZEOeBoNneU5/Og87xXNokP+2/JNG3677d5DQgecxoDOcHzXXDrgNGZ0httK5tddPISSTvHjvnOtI+4oezfdxZWKM65DvkXs");
        a("3Vw8e2iP6RzvYFiH3H9kDXWS746tj67z6Klzp1O8cOZ/i85z4dze1kV+xrWOuOfCO+iAJ/Ck6xyrult006v41EU+wK8O+Q2dH9");
        a("ac+y59dx3wILp1iqcQ11l+QEJHXL6yt3XraT3/7MitSwMBAMbhQw6DGBYWRNRkGgYxiJhMi7JgEDEaLl0WkWEyH5dFjg38OD8Q");
        a("kywtikFMC8ZhWhCjXPLgHlzZn7C3PC+/6cxaWfj/aw9hEHXD4Lczbstlq12FwUJ33BplWy/bpJ3pCTt8Yp8fHPKHM9eVdTa4wz");
        a("1GbDPlJXO+8I2fHLHg/E3lIte4zRYPGfOE50x5wZw9vnLAIUcsOHdbucQNNnnAmKdMmPGRPQ74zdm8coVb3OURY7aZ8p59vvOL");
        a("Bet3lavcZJP7jHjMhBmf/wSlj0DpG1D6FZT+A6U51kDjE0rrgGkEcILyw6B0Gpp8DZTfBaXnQOl1UHoPlD4Hpe9A6XdQmmkty2");
        a("i+GM0Xo/kCR75AAPfUkuCSFI/EvJScVCjfOT+vGEA31e62DcPAvJrnuliAeDFqY/6t2NdUgywZFFXUe/pJFoUlQfpLvCPFj6Pk");
        a("DFo378xgfCR/KxOQ/SfluSFylLz9A35T2qP5mrCydjYxzZfmjtwE70s9RRzWo313Vam4LLGDk7YoTCCCLfeO8x07fBDUHMlnsa");
        a("mH6BXUYnG05bg4UDB41Qa/1BIL3bK7AJkbSfMeJZnEPNTGeYnLcydH57RlFCUaO5/fE12U3TxjSYNGfHIqTnghRdso/tRkNc+U");
        a("2z68EiARCTlaFLfRp67Yb9QElVsb7zZ1DrwGrjvJem7aYhdRfkKtiUlnZYybxE4VZcc7LbbQ0erL5kv1rH3Zq6B9ukkZySFIsv");
        a("QGWNMZ/Ao7y5WkrQ/LTb4BtGh7W2JUmqMKvbbXOOTlDybOOueIjrQjzbLde+7xvUiN8wr7Hx39Cy7hegV1BA/LZUo0nxFVh9wl");
        a("eIdlx8U+Ws1aGf0XNUUrDtxjkkf/AgN+wjeWQY900gvqE0/4gbZvrrTBsP6xMQY36hn1h6LIFnNwxS9/tqq7+9+d8Rv/I6OMeh");
        a("UGYSjsX8PtmtyEp+udeybamUUF0o3M/XuPtCTiHs9HCxR6em9cnBMT8EzPWZgN4ZbiIfl8Gm7Aq0afRljX3Tu/jP4iTMXPM9eX");
        a("HnixkPhM/2ukyexqmq23pdb5a3JXXTlgu7Jiao3RYtRLheR/wS+EoRDtwFvRxVuqizt7bBuWqR0Zrxp4NcpPxBPKlpMwmqJjOs");
        a("6MLiwRub1JBphW0gD4i2OFdV6/yVMC077VZwdLDMixJhLTTcQtDQ6/a5vfNte+inflKEs+s+xhQaL/aBOls2UfpFL0TRcj8T4N");
        a("Q79TYsNSEY1BwleGqTIqJevVTHzJNxXojuRiBiRXgEqJ7NSivNQcYyO9lJwcBmAqTSwtgXJKgR6HMocp0NjAAj7bhwmKTYB8Gy");
        a("B2AeJgf58w1yCw78FnwASIActvoHgYSA80XPNzylKLQNmLAcwMKK2qykk1NGUYBQBm7I7vcSAKw/iDweJgMVgMFgMLxWBhIVgM");
        a("LBSDhYXgmZfMnJnMSzCwUFgYLAYWisHCAwMLO9vffog9X+H6H7n/45vzrpkD7oDAHkqooIYTNHCBKwyAMMMCd4jwgBWesMEn/A");
        a("ZCj/QLbehXeqHfaE+/U6CWLvQnfdBfdKOf9MDPvFJHVauTatRZXVWvZrWou4rqoVb1VJt6qaQ+sMAdEtxjiQc8YYNnbPGCHV6x");
        a("xxsOCDjjD4z4woQfutA7TXStT7rRZ93qi+70Vff6pgcNGvWsF33XUT/0qp960y+d9IcpzM4QszelOZjKHE1tTqYxZ9Oai+nM1f");
        a("TmZgYDBs3dHKZ26qZ+GiaclilO67RNaSossaWtbG0b29rO9nawaBcb7Wo3m2zhiCtd5WrXuNZ1rneDQ7e46Fa3ueQKT3zpK1/7");
        a("xre+870fPPrFR7/6zSdfBBLKUIU6NKENXejDEDAsIYY1bCGF3Omed9f8lAWQf5UaaKGD/l1pgQhrrpOgoISWtKJ17tPSLtcZKO");
        a("Y2ka65TKIFI6xkFatZw1rWsZ4NDNnCIlvZxhIrOOElr3jNG97yjvd84MgXHvnKN554IYgoRSVq0YhWdKIXg0CxiChWsYkkipGM");
        a("h7EajyOO83gf47iOz/E1prGQO7mXpazkUZ5kI1t5kVfZy0GCnOUio3zIp9xkkh9qp4gq1eGvoLefi+qyoJsChW9F2dBbUPbz1n");
        a("PACuvsJ+t52/lDmd3yyAmGURgmqaEOuRKJRK5EIpErkUjkJDUkNUjkypev7bvDpxyJRCJHIpHIyp59OJmk2512+w/Iw3XumcxQ");
        a("yCd8iAy6EBWHCYoYzxeYWM7X83rezz/PRmM1D43duM1j4zVB89SETQwvSZNBjGpGmJmaBWrWZocbo7Ugx25d2PHaAHrCNoafpM");
        a("0gSLUjDE3tAkVru8OR0VmQZHcuLHldAE1hF8NT0mUQpboRpqZugaq12+HK6C3IsnsXtrw+gK6wj+Er6TMIU/3S770FW/bgQpc3");
        a("BPAVDjGEJUMGY2oYoWwaFjhbh/1N2oLf/d+Cro4iuLDmSRFCFcNbojKIU0cXWIUd7ozcgjw7d2HPywPoC/MY/pI8g0CVjzA45Q");
        a("sUrvkOh0ZhQaJduLDoFQE0hkUMj0mRQaQqRpicigUq12KHS6O0INMuXdj0ygA6wzKGz6TMIFSVI4xO5QKla7nDqVFZkGpXLqx6");
        a("VQCtYRXDa1JlEKuqEWanaoHatdrh1qgtyLVpN6if6rCO61Od1Fn9XCtYvkDzctNsvNhw7MMwbrXifw3czFSs6HE1KWmJK624js");
        a("PL+LxNxOukucJlNtzDLJzbJSLeIuU1dDHKJTZewi6TSuH5Vzz519rBs36rv8tzRthK9pfSPkplT+iqRlkvekJbF33Vq97YWPv1");
        a("8dXDdu6vxpSW+u/rybXM5wVbMbEUV/YRcSEpN6JlJVfZxwNW4XMXEZeRchuTrMLoHK7B5x4iLiLtNLYwo6/HBhyuwOcOIiwhQ2");
        a("XHfkFhjcEcjiU43IIvazjJDi7Qvx3+d/wfj84a6v3bjGA/VZrmN6o36R7vF282EetTvtG4SeUOnftFJG9VU/ZG2yZ1O/TtU3hE");
        a("4ymUa/ie0eDDtUPZPm1H1J3Ct4aMudrg+jDtULVP1xFlp7StoXuG600a7UqhfWl0KJU+SaczKbWWVk8v15cdpX74YbPSgXQ6kl");
        a("In0upnqfU/P/Uh0tP+H5/91Ck2N3FpfUKmITX30PHonk7WfEPL34w6KLkvSlPWe2a/NxbcRMNdURq3qcjU7PbMcm9st8l6i1V+");
        a("D1CdhtHl1muzv6OV3U6PcovbSz/1m8iF2Vu9I/Y7ZcE1Gz6z4nRs4L/eLx859lnziD1PWfTbd70PhD9KtU55Bt2aLZ9Z87vW77");
        a("WMbZ//dwMs/VyuWIF5zz/7rln4mY2XNcgSsIHf9bPtM+u+se+/2juz0JnCMIwfBh1ZGkVOpCypIcufLCdLja1EMrZMoZALW7IU");
        a("QvYsyXYhU8I5M2dmzpnvnBmKsu9ZUxPRlCXhYkppkCX7872+rN98thsX5mbu3+f3PO/zfk0N94LBGws5Ik4+SIB83lNK1FQq1F");
        a("V0aiutqa+YEi+4nH+Qryae2u73qQzued81/6DrRqnLrPyRe9FjdNFkIqLLDGUxRVqTK+AG3mZMSVb/xBGKDN9H7qgEv+OJi0j0");
        a("Uv4evFGRJPtkcof1vR/C/Leof+eHMFI+Tns7oeA/StSvVHJuEtuLQbMrmstF0V3Kor3oToToBtcKoj917biC5jDxG5NluCBZI3");
        a("qjyux+RfSaKl4/sxpV0QpKOaE1RKc8o0tEqKFg8hRbV9iC3DtZuAB9nxfe8fuodUg7AH0Nq7PVE4pOs2ZDyx3WbusQ6Vex6tmN");
        a("oV8/ezA0nG8vQZZ59n6oeMt+AP3qJ5vwu4jU24iU2p88DN1qIZE6pLohi5YhfdKpADrdTj2EQhGkzgxnHvJmp7MXulx1bkCP9l");
        a("BjRHoc9utG6HA//RQbtUWmLfJjRGYcEmR9ZiumfwfJUTfbCIkxJDsCc1+RXY95H86exo58mf2QNdyebn+kwyJ3BWZ80D2OJHjq");
        a("vnF1r43XAfOd6E3DdDd7OzDVx9573DBdcr1wvSzILcUkg9xBeP1R7hk6WEfWHVPciJmdZpew016w9+hcXf3e1LngWe5WhU9X0t");
        a("VxKihX3V7TyZ+uwpEx2ljbZDuqJqRN/KprmeTExYpd1Jo69GS0ZlwV0l2k0wYaqtg5lSR3YI1stwg3lsiDhmK7nCIHaopm5ZIH");
        a("y2mxQyStahv5sKjYIYvJiYcUvWm66ErfNnh6JUFbing1XpycmPirTREhL06XbATRk7AXIuhIMcVOeOVzwsyvs1/ctK64asvEV0");
        a("SR/UXiKyzau6wNHSK+6L0kGtIWg63qOW9xwsCWaPCSC20DEXZR0W/mEmH7FD0mToQlZAkvLtShxNcGSbrzq1THXVpDfM1VtBVD");
        a("dJRqGX/vF1K+THxFFL2kSISFKePlb3CniDBN8dK2T9yH1buHxekCV7p4O+Fkmd/ciduIraKyYcgpK1GOGX/cMUxxM1Zv3VospA");
        a("3+hZ6RoIQrfbkgFS8EkqwDkZxG84/6dYxyb1uV3qG+L/eB0Fd0V5pgUn5LEp/fNmjKPrz5EaGuokHHspjd5JBm4WtKbiaosXIe");
        a("culJrhZrwtqwbmwAG8WmsHlsFduOLDrCzrMSJdAg0DHLX4D82eWnkDxX/OvooW/9OqCjZ9AfVMwJFoIGJ/DROW8HD5E3zfKtsM");
        a("eG58eAg2X5tdD/YP44dH+af42c6VToAb1nFOYhXfYUMlD7ZuEuV3luSDvH/6vFag5th1mjoelSaw009a0DVpG2lW63tNtBz5H2");
        a("eOgIDaHfGfsydtQL+71tJLskeyE/piZnQjmoBsUuJ69BqzqphlBoYGoYttPy1DrspKOps1Ao7LSFGmOcCdBhvbMVbfCkcwE58d");
        a("L5gBbYPd0HaixIL4UKJ9LnkQu4YDD77pm+mPvUzCz0v9HeBLh1tbcJPuU/SAppY/GFDUBOnay4lHVW22/gG+TDmL8e6X6MXr6b");
        a("wnWjgknCaZz5kNYUX9iQlGM8tbT/n3/68xHeVZKEADYEAA==");
    }
}
Output:
Hi! My name is Pascal Slover.

And I try solve puzzle15 with board:
15 14 1 6
9 11 4 12
0 10 7 3
13 8 5 2

Ready! Solution path is : rrruldluuldrurdddluulurrrdlddruldluurddlulurruldrrdd
Moves 52 and Time: 00:00:00.7509766

Bye bye :)

C++

see for an analysis of 20 randomly generated 15 puzzles solved with this solver.

The Solver

// Solve Random 15 Puzzles : Nigel Galloway - October 18th., 2017
class fifteenSolver{
  const int Nr[16]{3,0,0,0,0,1,1,1,1,2,2,2,2,3,3,3}, Nc[16]{3,0,1,2,3,0,1,2,3,0,1,2,3,0,1,2};
  int n{},_n{}, N0[100]{},N3[100]{},N4[100]{};
  unsigned long N2[100]{};
  const bool fY(){
    if (N4[n]<_n) return fN();
    if (N2[n]==0x123456789abcdef0) {std::cout<<"Solution found in "<<n<<" moves :"; for (int g{1};g<=n;++g) std::cout<<(char)N3[g]; std::cout<<std::endl; return true;};
    if (N4[n]==_n) return fN(); else return false;
  }
  const bool                     fN(){
    if (N3[n]!='u' && N0[n]/4<3){fI(); ++n; if (fY()) return true; --n;}
    if (N3[n]!='d' && N0[n]/4>0){fG(); ++n; if (fY()) return true; --n;}
    if (N3[n]!='l' && N0[n]%4<3){fE(); ++n; if (fY()) return true; --n;}
    if (N3[n]!='r' && N0[n]%4>0){fL(); ++n; if (fY()) return true; --n;}
    return false;
  }
  void fI(){
    const int           g = (11-N0[n])*4;
    const unsigned long a = N2[n]&((unsigned long)15<<g);
    N0[n+1]=N0[n]+4; N2[n+1]=N2[n]-a+(a<<16); N3[n+1]='d'; N4[n+1]=N4[n]+(Nr[a>>g]<=N0[n]/4?0:1);
  } 
  void fG(){
    const int           g = (19-N0[n])*4;
    const unsigned long a = N2[n]&((unsigned long)15<<g);
    N0[n+1]=N0[n]-4; N2[n+1]=N2[n]-a+(a>>16); N3[n+1]='u'; N4[n+1]=N4[n]+(Nr[a>>g]>=N0[n]/4?0:1);
  } 
  void fE(){
    const int           g = (14-N0[n])*4;
    const unsigned long a = N2[n]&((unsigned long)15<<g);
    N0[n+1]=N0[n]+1; N2[n+1]=N2[n]-a+(a<<4); N3[n+1]='r'; N4[n+1]=N4[n]+(Nc[a>>g]<=N0[n]%4?0:1);
  } 
  void fL(){
    const int           g = (16-N0[n])*4;
    const unsigned long a = N2[n]&((unsigned long)15<<g);
    N0[n+1]=N0[n]-1; N2[n+1]=N2[n]-a+(a>>4); N3[n+1]='l'; N4[n+1]=N4[n]+(Nc[a>>g]>=N0[n]%4?0:1);
  }
public:
  fifteenSolver(int n, unsigned long g){N0[0]=n; N2[0]=g;}
  void Solve(){for(;not fY();++_n);}
};

The Task

int main (){
  fifteenSolver start(8,0xfe169b4c0a73d852);
  start.Solve();
}
Output:
Solution found in 52 moves: rrrulddluuuldrurdddrullulurrrddldluurddlulurruldrdrd

real    0m0.517s

Extra Credit

int main (){
  fifteenSolver start(0,0x0c9dfbae37254861);
  start.Solve();
}
Output:
Solution found in 80 moves :dddrurdruuulllddrulddrrruuullddruulldddrrurulldrruulldlddrurullddrrruullulddrdrr

real    249m18.464s

Common Lisp

Translation of: Racket

Using an A* search algorithm which is good enough for the first task. I increased SBCL's dynamic memory to 2GB for the code to run smoothly.

;;; Using a priority queue for the A* search
(eval-when (:load-toplevel :compile-toplevel :execute)
  (ql:quickload "pileup"))

;; * The package definition
(defpackage :15-solver
  (:use :common-lisp :pileup)
  (:export "15-puzzle-solver" "*initial-state*" "*goal-state*"))
(in-package :15-solver)

;; * Data types
(defstruct (posn (:constructor posn))
  "A posn is a pair struct containing two integer for the row/col indices."
  (row 0 :type fixnum)
  (col 0 :type fixnum))

(defstruct (state (:constructor state))
  "A state contains a vector and a posn describing the position of the empty slot."
  (matrix '#(1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 0) :type simple-vector)
  (empty-slot (posn :row 3 :col 3) :type posn))
 
(defparameter directions '(up down left right)
  "The possible directions shifting the empty slot.")
 
(defstruct (node (:constructor node))
  "A node contains a state, a reference to the previous node, a g value (actual
costs until this node, and a f value (g value + heuristics)."
  (state (state) :type state)
  (prev nil)
  (cost 0 :type fixnum)
  (f-value 0 :type fixnum))

;; * Some constants
(defparameter *side-size* 4 "The size of the puzzle.")

(defvar *initial-state*
  (state :matrix #(15  14  1  6
                    9  11  4 12
                    0  10  7  3
                   13   8  5  2)
         :empty-slot (posn :row 2 :col 0)))

(defvar *initial-state-2*
  (state :matrix #( 0 12  9 13
                   15 11 10 14
                    3  7  2  5
                    4  8  6  1)
         :empty-slot (posn :row 0 :col 0)))

(defvar *goal-state*
  (state :matrix #( 1  2  3  4
                    5  6  7  8
                    9 10 11 12
                   13 14 15  0)
          :empty-slot (posn :row 3 :col 3)))

;; * The functions

;; ** Accessing the elements of the puzzle
(defun matrix-ref (matrix row col)
  "Matrices are simple vectors, abstracted by following functions."
  (svref matrix (+ (* row *side-size*) col)))

(defun (setf matrix-ref) (val matrix row col)
  (setf (svref matrix (+ (* row *side-size*) col)) val))

;; ** The final predicate
(defun target-state-p (state goal-state)
  "Returns T if STATE is the goal state."
  (equalp state goal-state))

(defun valid-movement-p (direction empty-slot)
  "Returns T if direction is allowed for the current empty slot position."
  (case direction
    (up (< (posn-row empty-slot) (1- *side-size*)))
    (down (> (posn-row empty-slot) 0))
    (left (< (posn-col empty-slot) (1- *side-size*)))
    (right (> (posn-col empty-slot) 0))))

;; ** Pretty print the state
(defun print-state (state)
  "Helper function to pretty-print a state."
  (format t " ====================~%")
  (loop
     with matrix = (state-matrix state)
     for i from 0 below *side-size*
     do
       (loop
          for j from 0 below *side-size*
          do (format t "| ~2,D " (matrix-ref matrix i j)))
       (format t " |~%"))
  (format t " ====================~%"))

;; ** Move the empty slot
(defun move (state direction)
  "Returns a new state after moving STATE's empty-slot in DIRECTION assuming a
  valid direction."
  (let* ((matrix (copy-seq (state-matrix state)))
         (empty-slot (state-empty-slot state))
         (r (posn-row empty-slot))
         (c (posn-col empty-slot))
         (new-empty-slot
          (ccase direction
            (up (setf (matrix-ref matrix r c) (matrix-ref matrix (1+ r) c)
                      (matrix-ref matrix (1+ r) c) 0)
                (posn :row (1+ r) :col c))
            (down (setf (matrix-ref matrix r c) (matrix-ref matrix (1- r) c)
                        (matrix-ref matrix (1- r) c) 0)
                  (posn :row (1- r) :col c))
            (left (setf (matrix-ref matrix r c) (matrix-ref matrix r (1+ c))
                        (matrix-ref matrix r (1+ c)) 0)
                  (posn :row r :col (1+ c)))
            (right (setf (matrix-ref matrix r c) (matrix-ref matrix r (1- c))
                         (matrix-ref matrix r (1- c)) 0)
                   (posn :row r :col (1- c))))))
    (state :matrix matrix :empty-slot new-empty-slot)))

;; ** The heuristics
(defun l1-distance (posn0 posn1)
  "Returns the L1 distance between two positions."
  (+ (abs (- (posn-row posn0) (posn-row posn1)))
     (abs (- (posn-col posn0) (posn-col posn1)))))

(defun element-cost (val current-posn)
  "Returns the L1 distance between the current position and the goal-position
for VAL."
  (if (zerop val)
      (l1-distance current-posn (posn :row 3 :col 3))
      (multiple-value-bind (target-row target-col)
          (floor (1- val) *side-size*)
        (l1-distance current-posn (posn :row target-row :col target-col)))))

(defun distance-to-goal (state)
  "Returns the L1 distance from STATE to the goal state."
  (loop
     with matrix = (state-matrix state)
     with sum = 0
     for i below *side-size*
     do (loop
           for j below *side-size*
           for val = (matrix-ref matrix i j)
           for cost = (element-cost val (posn :row i :col j))
           unless (zerop val)
           do (incf sum cost))
     finally (return sum)))

(defun out-of-order-values (list)
  "Returns the number of values out of order."
  (flet ((count-values (list)
           (loop
              with a = (first list)
              with rest = (rest list)
              for b in rest
              when (> b a)
              count b)))
    (loop
       for candidates = list then (rest candidates)
       while candidates
       summing (count-values candidates) into result
       finally (return (* 2 result)))))

(defun row-conflicts (row state0 state1)
  "Returns the number of conflicts in the given row, i.e. value in the right row
  but in the wrong order. For each conflicted pair add 2 to the value, but a
  maximum of 6 to avoid over-estimation."
  (let* ((goal-row (loop
                      with matrix1 = (state-matrix state1)
                      for j below *side-size*
                      collect (matrix-ref matrix1 row j)))
         (in-goal-row (loop
                         with matrix0 = (state-matrix state0)
                         for j below *side-size*
                         for val = (matrix-ref matrix0 row j)
                         when (member val goal-row)
                         collect val)))
    (min 6 (out-of-order-values
            ;; 0 does not lead to a linear conflict
            (remove 0 (nreverse in-goal-row))))))

(defun col-conflicts (col state0 state1)
  "Returns the number of conflicts in the given column, i.e. value in the right
  row but in the wrong order. For each conflicted pair add 2 to the value, but a
  maximum of 6 to avoid over-estimation."
  (let* ((goal-col (loop
                      with matrix1 = (state-matrix state1)
                      for i below *side-size*
                      collect (matrix-ref matrix1 i col)))
         (in-goal-col (loop
                         with matrix0 = (state-matrix state0)
                         for i below *side-size*
                         for val = (matrix-ref matrix0 i col)
                         when (member val goal-col)
                         collect val)))
    (min 6 (out-of-order-values
            ;; 0 does not lead to a linear conflict
            (remove 0 (nreverse in-goal-col))))))

(defun linear-conflicts (state0 state1)
  "Returns the linear conflicts for state1 with respect to state0."
  (loop
     for i below *side-size*
     for row-conflicts = (row-conflicts i state0 state1)
     for col-conflicts = (col-conflicts i state0 state1)
     summing row-conflicts into all-row-conflicts
     summing col-conflicts into all-col-conflicts
     finally (return (+ all-row-conflicts all-col-conflicts))))

(defun state-heuristics (state)
  "Using the L1 distance and the number of linear conflicts as heuristics."
  (+ (distance-to-goal state)
     (linear-conflicts state *goal-state*)))

;; ** Generate the next possible states. 
(defun next-state-dir-pairs (current-node)
  "Returns a list of pairs containing the next states and the direction for the
  movement of the empty slot."
  (let* ((state (node-state current-node))
         (empty-slot (state-empty-slot state))
         (valid-movements (remove-if-not (lambda (dir) (valid-movement-p dir empty-slot))
                                         directions)))
    (map 'list (lambda (dir) (cons (move state dir) dir)) valid-movements)))

;; ** Searching the shortest paths and reconstructing the movements
(defun reconstruct-movements (leaf-node)
  "Traverse all nodes until the initial state and return a list of symbols
describing the path."
  (labels ((posn-diff (p0 p1)
             ;; Compute a pair describing the last move
             (posn :row (- (posn-row p1) (posn-row p0))
                        :col (- (posn-col p1) (posn-col p0))))
           (find-movement (prev-state state)
             ;; Describe the last movement of the empty slot with R, L, U or D.
             (let* ((prev-empty-slot (state-empty-slot prev-state))
                    (this-empty-slot (state-empty-slot state))
                    (delta (posn-diff prev-empty-slot this-empty-slot)))
               (cond ((equalp delta (posn :row  1 :col  0)) 'u)
                     ((equalp delta (posn :row -1 :col  0)) 'd)
                     ((equalp delta (posn :row  0 :col  1)) 'l)
                     ((equalp delta (posn :row  0 :col -1)) 'r))))
           (iter (node path)
             (if (or (not node) (not (node-prev node)))
                 path
                 (iter (node-prev node)
                       (cons (find-movement (node-state node)
                                            (node-state (node-prev node)))
                             path)))))
    (iter leaf-node '())))

(defun A* (initial-state
           &key (goal-state *goal-state*) (heuristics #'state-heuristics)
             (information 0))
  "An A* search for the shortest path to *GOAL-STATE*"
  (let ((visited (make-hash-table :test #'equalp))) ; All states visited so far
    ;; Some internal helper functions
    (flet ((pick-next-node (queue)
             ;; Get the next node from the queue
             (heap-pop queue))
           (expand-node (node queue)
             ;; Expand the next possible nodes from node and add them to the
             ;; queue if not already visited.
             (loop
                with costs = (node-cost node)
                with successors = (next-state-dir-pairs node)
                for (state . dir) in successors
                for succ-cost = (1+ costs)
                for f-value = (+ succ-cost (funcall heuristics state))
                ;; Check if this state was already looked at
                unless (gethash state visited)
                do
                ;; Insert the next node into the queue
                  (heap-insert
                   (node :state state :prev node :cost succ-cost
                              :f-value f-value)
                   queue))))
      
      ;; The actual A* search
      (loop
         ;; The priority queue
         with queue = (make-heap #'<= :name "queue" :size 1000 :key #'node-f-value)
         with initial-state-cost = (funcall heuristics initial-state)
         initially (heap-insert (node :state initial-state :prev nil :cost 0
                                           :f-value initial-state-cost)
                                queue)
         for counter from 1
         for current-node = (pick-next-node queue)
         for current-state = (node-state current-node)
         ;; Output some information each counter or nothing if information
         ;; equals 0.
         when (and (not (zerop information)) 
                   (zerop (mod counter information))) 
         do (format t "~Dth State, heap size: ~D, current costs: ~D~%"
                    counter (heap-count queue)
                    (node-cost current-node))
           
         ;; If the target is not reached continue
         until (target-state-p current-state goal-state)
         do
         ;; Add the current state to the hash of visited states
           (setf (gethash current-state visited) t)
         ;; Expand the current node and continue
           (expand-node current-node queue)
         finally (return (values (reconstruct-movements current-node) counter))))))

;; ** Pretty print the path
(defun print-path (path)
  "Prints the directions of PATH and its length."
  (format t "~{~A~} ~D moves~%" path (length path)))

;; ** Get some timing information
(defmacro timing (&body forms)
  "Return both how much real time was spend in body and its result"
  (let ((start (gensym))
	(end (gensym))
	(result (gensym)))
    `(let* ((,start (get-internal-real-time))
	    (,result (progn ,@forms))
	    (,end (get-internal-real-time)))
       (values ,result (/ (- ,end ,start) internal-time-units-per-second)))))

;; ** The main function
(defun 15-puzzle-solver (initial-state &key (goal-state *goal-state*))
  "Solves a given and valid 15 puzzle and returns the shortest path to reach the
  goal state."
  (print-state initial-state)
  (multiple-value-bind (result time)
      (timing (multiple-value-bind (path steps)
                      (a* initial-state :goal-state goal-state)
                (print-path path)
                steps))
   (format t "Found the shortest path in ~D steps and ~3,2F seconds~%" result time)) 
  (print-state goal-state))
Output:
15-SOLVER> (15-puzzle-solver *initial-state*)
 ====================
| 15 | 14 |  1 |  6  |
|  9 | 11 |  4 | 12  |
|  0 | 10 |  7 |  3  |
| 13 |  8 |  5 |  2  |
 ====================
RRRULDDLUUULDRURDDDRULLULURRRDDLDLUURDDLULURRULDRDRD 52 moves
Found the shortest path in 1130063 steps and 17.61 seconds
 ====================
|  1 |  2 |  3 |  4  |
|  5 |  6 |  7 |  8  |
|  9 | 10 | 11 | 12  |
| 13 | 14 | 15 |  0  |
 ====================

F#

The Function

// A Naive 15 puzzle solver using no memory. Nigel Galloway: October 6th., 2017
let Nr,Nc = [|3;0;0;0;0;1;1;1;1;2;2;2;2;3;3;3|],[|3;0;1;2;3;0;1;2;3;0;1;2;3;0;1;2|]
type G= |N |I |G |E |L 
type N={i:uint64;g:G list;e:int;l:int}
let fN     n=let g=(11-n.e)*4 in let a=n.i&&&(15UL<<<g)
             {i=n.i-a+(a<<<16);g=N::n.g;e=n.e+4;l=n.l+(if Nr.[int(a>>>g)]<=n.e/4 then 0 else 1)}
let fI     i=let g=(19-i.e)*4 in let a=i.i&&&(15UL<<<g)
             {i=i.i-a+(a>>>16);g=I::i.g;e=i.e-4;l=i.l+(if Nr.[int(a>>>g)]>=i.e/4 then 0 else 1)}
let fG     g=let l=(14-g.e)*4 in let a=g.i&&&(15UL<<<l)
             {i=g.i-a+(a<<<4) ;g=G::g.g;e=g.e+1;l=g.l+(if Nc.[int(a>>>l)]<=g.e%4 then 0 else 1)}
let fE     e=let l=(16-e.e)*4 in let a=e.i&&&(15UL<<<l)
             {i=e.i-a+(a>>>4) ;g=E::e.g;e=e.e-1;l=e.l+(if Nc.[int(a>>>l)]>=e.e%4 then 0 else 1)}
let fL=let l=[|[I;E];[I;G;E];[I;G;E];[I;G];[N;I;E];[N;I;G;E];[N;I;G;E];[N;I;G];[N;I;E];[N;I;G;E];[N;I;G;E];[N;I;G];[N;E];[N;G;E];[N;G;E];[N;G];|]
       (fun n g->List.except [g] l.[n] |> List.map(fun n->match n with N->fI |I->fN |G->fE |E->fG))
let solve n g l=let rec solve n=match n with // n is board, g is pos of 0, l is max depth
                                |n when n.i =0x123456789abcdef0UL->Some(n.g)
                                |n when n.l>l                    ->None
                                |g->let rec fN=function h::t->match solve h with None->fN t |n->n
                                                       |_->None
                                    fN (fL g.e (List.head n.g)|>List.map(fun n->n g))
                solve {i=n;g=[L];e=g;l=0}
  let n = Seq.collect fN n

The Task

let test n g=match [1..15]|>Seq.tryPick(solve n g) with
               Some n->n|>List.rev|>List.iter(fun n->printf "%c" (match n with N->'d'|I->'u'|G->'r'|E->'l'|L->'\u0000'));printfn " (%n moves)" (List.length n)
              |_     ->printfn "No solution found"
test 0xfe169b4c0a73d852UL 8
Output:
rrrulddluuuldrurdddrullulurrrddldluurddlulurruldrdrd (52 moves)

Forth

The idea is taken from C++ or F# version above.

The code was tested with gforth 0.7.3. It required a 64-bit system.

#! /usr/bin/gforth

cell 8 <> [if] s" 64-bit system required" exception throw [then]

\ In the stack comments below,
\ "h" stands for the hole position (0..15),
\ "s" for a 64-bit integer representing a board state,
\ "t" a tile value (0..15, 0 is the hole),
\ "b" for a bit offset of a position within a state,
\ "m" for a masked value (4 bits selected out of a 64-bit state),
\ "w" for a weight of a current path,
\ "d" for a direction constant (0..3)

\ Utility
: 3dup   2 pick 2 pick 2 pick ;
: 4dup   2over 2over ;
: shift   dup 0 > if lshift else negate rshift then ;

hex 123456789abcdef0 decimal constant solution
: row   2 rshift ;   : col   3 and ;

: up-valid?    ( h -- f ) row 0 > ;
: down-valid?  ( h -- f ) row 3 < ;
: left-valid?  ( h -- f ) col 0 > ;
: right-valid? ( h -- f ) col 3 < ;

: up-cost    ( h t -- 0|1 ) 1 - row swap row < 1 and ;
: down-cost  ( h t -- 0|1 ) 1 - row swap row > 1 and ;
: left-cost  ( h t -- 0|1 ) 1 - col swap col < 1 and ;
: right-cost ( h t -- 0|1 ) 1 - col swap col > 1 and ;

\ To iterate over all possible directions, put direction-related functions into arrays:
: ith ( u addr -- w ) swap cells + @ ;
create valid? ' up-valid? , ' left-valid? , ' right-valid? , ' down-valid? , does> ith execute ;
create cost ' up-cost , ' left-cost , ' right-cost , ' down-cost , does> ith execute ;
create step -4 , -1 , 1 , 4 , does> ith ;

\ Advance from a single state to another:
: bits ( h -- b ) 15 swap - 4 * ;
: tile ( s b -- t ) rshift 15 and ;
: new-state ( s h d -- s' ) step dup >r + bits 2dup tile ( s b t ) swap lshift tuck - swap r> 4 * shift + ;
: new-weight ( w s h d -- w' ) >r tuck r@ step + bits tile r> cost + ;
: advance ( w s h d -- w s h w' s' h' ) 4dup new-weight >r  3dup new-state >r  step over + 2r> rot ;

\ Print a solution:
: rollback   2drop drop ;
: .dir ( u -- ) s" d..r.l..u" drop 4 + swap + c@ emit ;
: .dirs ( .. -- ) 0 begin >r 3 pick -1 <> while 3 pick over - .dir rollback r> 1+ repeat r> ;
: win   cr ." solved (read right-to-left!): " .dirs ."  - " . ." moves" bye ;

\ The main recursive function for depth-first search:
create limit 1 ,   : deeper  1 limit +! ;
: u-turn ( .. h2 w1 s1 h1 ) 4 pick 2 pick - ;
: search ( .. h2 w1 s1 h1 )
	over solution = if win then
	2 pick limit @ > if exit then
	4 0 do dup i valid? if i step u-turn <> if i advance recurse rollback then then loop ;

\ Iterative-deepening search:
: solve   1 limit !  begin search deeper again ;

\ -1 0 hex 0c9dfbae37254861 decimal 0 solve    \ uhm.
 -1 0 hex fe169b4c0a73d852 decimal 8 solve     \ the 52 moves case
\ -1 0 hex 123456789afbde0c decimal 14 solve   \ some trivial case, 3 moves
bye
Output:
time ./15_puzzle_solver.fs 
redefined search 
solved (read right-to-left!): ddrrdlurrululddruuldlurddldrrruluuldddrurdluuldlurrr - 52 moves
real	1m14.605s
user	1m5.212s
sys	0m0.048s

Fortran

The Plan

There is a straightforward method for dealing with problems of this sort, the Red Tide. Imagine a maze and pouring red-dyed water into the "entry" - eventually, red water issues forth from the exit, or, back pressure will indicate that there is no such path. In other words, from the starting position find all possible positions that can be reached in one step, then from those positions, all possible positions that can be reached in one step from them, and so on. Eventually, either the stopping position will be reached, or, there will be no more new (still dry) positions to inspect. What is needed is some way of recording whether a position has been marked red or not, and an arrangement for identifying positions that are on the leading edge of the tide as it spreads. Keeping as well some sort of information identifying the path followed by each droplet, so that when a droplet spreads to the end position, its path from the source can be listed.

One could imagine an array FROM whose value for a given position identifies the position from which a step was taken to reach it. The value could be a number identifying that position or be a (sixteen element) list of the placement of the tiles possibly encoded into one number. An index for FROM would span the values zero to fifteen, and there would be sixteen dimensions... Alternatively, an array MOVE would record the move that led to its element's positiion: four possibilities would require just two bits for each element. But 1616 is about 1020 Avogadro's constant is 6·0221415x1023 Oh well.

Since there are only 16! possible board layouts, only about a millionth of the array would be in use (16!/1616 = 0·0000011342267) which is rather a stiff price to pay for a convenient relationship between a board layout and the corresponding location in the array. Actually, calculating a sixteen-dimensional location in an array is not so trivial and earlier compilers would not allow so many dimensions anyway. An alternative calculation might serve. This is the method of "hash tables", whereby, given a key (here, the values of the tiles in the sixteen places of the board), calculate a number, H, by some expedient and use that to index into a table. Entries are added sequentially to the table of positions but the hash number for a position is arbitrary so an array is used: INDEX(H) for the first position stored is one, and for the second position with its value of H, INDEX(H) is two, and so on, Two different positions may well yield the same hash number in a variant of the "birthday paradox", so the value of INDEX(H) is really a pointer to the start of a linked-list of table entries for different positions that all have the same hash number. The length of such chains is expected to be short because the hash number selects within a large INDEX array. Searching should be swift as for a given position, either INDEX(H) will be zero (meaning that the position is unknown), or, there will be one or two entries to probe to see if their position matches.

Because a board position involves sixteen numbers, each in the range of zero to fifteen, it is irresistible to have the board layout described by INTEGER*1 BOARD(16) since eight-bit integers can be defined, though not four-bit integers, alas. Further, an EQUIVALENCE statement can make this storage area the same place that an array INTEGER*4 BORED(4) occupies: no data transfer operations are needed for four 32-bit integers to hold the contenet of sixteen 8-bit integers. Next, the calculations BRD(1) = BORED(1)*16 + BORED(2) and BRD(2) = BORED(3)*16 + BORED(4) will squeeze two four-bit fields into one eight-bit pair, and moreover, do so four pairs at a time in parallel. Thus, array BRD in two 32-bit integers describes a position. A 64-bit integer could be involved instead of two 32-bit integers, but the hash calculation uses BRD(1)*BRD(2) to encourage a good mix. In The Art of Computer Programming, Prof. Knuth advises that the wild hash number be reduced to the range 0:M - 1 by taking the remainder, modulo M, where M is a (large) prime number. The index array then is a fixed size, INDEX(0:M - 1) The output shows these calculations for two entries; notably the ordering of the bytes is peculiar, this being a "little-endian" cpu.

Accordingly, a table entry is defined by TYPE AREC with a NEXT (to link to the next table entry in a chain) and a BRD array to describe the board layout the entry is for. The payload for this problem consists of a PREV which identifies the entry from which this position was reached, and MOVE which identifies the move that had been made to do so.

The initial idea was to work from the given starting position, ascertaining all positions that could be reached in one step, then from each of those the positions reachable by a second step, and so on, until the "solved" state is reached. This is somewhat like the "all possible paths" of Quantum Electrodynamics when considering photon paths. Necessarily, on first contact the linked-list of PREV entries will be a minimum-length path. Loops are precluded by passing over any candidate new position that has already been reached and so already has an entry in the table: it can be reached by a path of the same or lesser length. In some games, loops are not possible, or are truncated by a special rule as in chess, when on the third attainment of a position a draw is declared. Then reading a remark in the Phix solution prompted the realisation that the flow could start from the "solved" position and stop on attainment of the specified position; the linked list via PREV could be reported in reverse order to go from the specified position back to the solved position. Some games are not symmetrical, as in chess where a pawn can only advance, but in this game, a move from A to B is just as allowable as a move from B to A - indeed, when checking the moves from a position, the reverse of the move that led to that position is skipped - it would just lead to a position that is already in the table and so be passed over. Similarly, the table does not record the possible moves from a position (there being two, three or four of them) but only the one move that led to the position. While there might be two, three, or four such moves possible (from different positions), only one move from one position is recorded, the one that got there first.

Starting a Red Tide from the "solved" or ZERO position has the advantage that if the table is retained, a new run for a different start position would take advantage of the previous effort. A table based around moves from one start position would be of little use when given a different start position.

Alas, the Red Tide ran into too much dry sand. A sixteen-dimensional volume has a lot of space in which it can possess a surface. The revised plan was to spread a Red Tide from the ZERO position and at the same time spread a Blue Tide from the specified start position, hoping that they would meet rather sooner. In Numerical Methods that Work (Usually), F. S. Acton remarks upon "Perverse Formulations", such as "... who insists on solving a boundary-value problem in ordinary differential equations via initial-value techniques may get away with it for a while, but ..." One can easily imagine pouring red paint onto the floor in one place, and blue paint in another place: most of the extension of the puddles is in directions that will not meet. With a single puddle, very little of the expansion is towards the target. Details are shown in the results.

A great deal of computer effort could be avoided if the spread could be given good guidance. For instance, a function that for any given position, gives the minimum number of moves needed to reach the ZERO position from it. Thus, from the start position, evaluate the function for each of the positions that can be reached via the available moves (at most, four), and select that one with the smallest value; repeat until at ZERO. Such a function certainly exists, though our ingenuity may not bring it forth. Perhaps some thought may do so. Perhaps not any time soon. But in any case, there is a definite procedure for generating such a function, and it is simple. Analyse all the board positions (they are finite in number) as in the Red Tide process, for each entry retaining the number of moves needed to reach that position from ZERO. Clearly, this is computable by a Turing Machine and so the function is computable, and so exists.

Such a guide function is rather like a "distance" function, so along these lines, when a minimum-step move sequence is found, the distances of each position from ZERO are calculated by various distance functions and the results shown in the output. Notably, function ZDIST calculates an encodement of the board position by referring to the layout of the ZERO position. It relies on the first square of a position having sixteen possible values, then the second square has fifteen and so on down; the number of possible layouts is 16! rather than 1616. Given the list of values of the squares in the ZERO sequence, values that have been taken are marked off (in array AVAIL) and the count of possibilities remaining as each square is identified reduces. The ZDIST number is like an encodement of an integer from its digits, but with a successively-reducing base.

As an experiment, when the Red Tide was poured to completion for a board of three rows and four columns, every position in the stash was presented to ZDIST and the result written out. The filesystem presented difficulties: reading the stash file as a sequential file failed! So, a slog through reading specified record numbers, one after the other, taking half an hour. By contrast, the B6700 filesystem of the 1970s employed a feature called "diagonal I/O" whereby the style of the previous I/O for a file was retained and compared to the style of the new I/O operation: for matching styles, conclusions could be drawn. The first and last few ZDIST values are 0, 105, 1, 328449, 609, 632, 4, 3271809, 3312009, ... 287247191, 446727869, 287039663. That last is entry 23950800 which is 12!/2: every possible attainable position has been tested and stored in the stash (once each), since a parity condition means that half the layout combinations have one parity and half the other, the possible moves being unable to change the parity. Alas, the ZDIST figures do not show anything so simple as odd/even for this. Every value should be unique, but in the past it has suspiciously often been easy to find a proof of a desired situation, so a test: sort the values and check. UltraEdit ran for a long time, then reported that there was insufficient memory to maintain an "undo" list. Very well. And then it wiped the file! Fortunately, the stash was still intact. On restarting in Linux the "sort" utility was available, and after some impressive running of all six cpus at 100% in cyclic bursts, I was looking at a file in a mad order: 0, 1, 10, 100, 1000, 10000, ... Apparently some sort of "word" order, even though the text file used leading spaces so that the numbers were all aligned right. As usual, the "man"ual information was brief and vague, but option "g" for "general number" looked likely, and so it proved. In sorted order: 0, 1, 4, 8, 9, 11, 12, 13, ... 479001589, 479001590, 479001593, 479001594, 479001597, 479001598. And 12! = 479001600. All values were unique.

The ZDIST function would be a candidate for a hash function, as it looks to give a good spray. But it requires rather more computation. It is always possible to calculate a "perfect" hash function, one that not only gives a distinct integer value for every possible key but also produces no gaps, so if there are N keys, there are N hash values in the range 1:N (or 0:N - 1) and the mapping is one to one. There even exist "hyper perfect" hash functions, that possess useful ordering properties: an example is the conversion of dates to a day number, which is one-to-one, without gaps, and ordered by date. However, the calculation of such perfection may be lengthy, so a fast hash is preferable, except for special cases.

In the event, when running, TaskInfo showed that almost all of the time was spent in the filesystem's I/O procedures, no "user time" was visible. The hash calculation indeed was fast, but the I/O was slow. Probably because the disc file was scattered in blocks across the disc device (actually a solid-state drive) and finding the appropriate block took a lot of messing about. In the past, a large disc file would be in one or very few pieces on the actual disc, with a straightforward direct calculation between a record number and a disc's corresponding cylinder, surface, track and sector. These days, the operating system uses "spare" memory to hold the content of recently-used disc blocks, but alas, each index file is 781MB, and the stash files are over 3,000MB. Some systems have many gigabytes of memory, but this one has 4GB.

The Code

The source code started off as a mainline only, but then facilities were added and service routines became helpful. This prompted the use of F90's MODULE facility so that the PARAMETER statement could be used to describe the shape of the board with this information available to each routine without the need for passing the values as parameters or messing with COMMON storage, or repeating the PARAMETER statement in each routine. Otherwise, literal constants such as 4 would appear in various places. These appearances could now be documented by using the appropriate name such as NR and NC rather than just 4 and similar. However, inside FORMAT statements the use of <NR - 1> (and not <NC - 1>) rather than 3 will succeed only if the compiler accepts this usage, and not all do. More complex calculations involving the board size have not been attempted. The PARAMETER facility is useful only for simple calculations, and the compiler typically does not allow the use of many functions, even library functions, in expressions. Considerations such as a 4x4 board having 16 squares is easy, but the consequences of this count fitting into a four-bit binary field are not, thus the equivalences involving BOARD, BORED and BOAR are not general for different board shapes, nor are the column headings adjustable. Similarly, subroutine UNPACK does not attempt to use a loop but employs explicit code.

This approach is also followed in the calculation of the hash code. Not using a loop (for two items only) but, by writing out the product rather than using the compiler's built-in PRODUCT function. A startling difference results:

59:         H = MOD(ABS(PRODUCT(BRD)),APRIME)
004016E3   mov         esi,1
004016E8   mov         ecx,1
004016ED   cmp         ecx,2
004016F0   jg          MAIN$SLIDESOLVE+490h (0040171e)
004016F2   cmp         ecx,1
004016F5   jl          MAIN$SLIDESOLVE+46Eh (004016fc)
004016F7   cmp         ecx,2
004016FA   jle         MAIN$SLIDESOLVE+477h (00401705)
004016FC   xor         eax,eax
004016FE   mov         dword ptr [ebp-54h],eax
00401701   dec         eax
00401702   bound       eax,qword ptr [ebp-54h]
00401705   imul        edx,ecx,4
00401708   mov         edx,dword ptr H (00473714)[edx]
0040170E   imul        edx,esi
00401711   mov         esi,edx
00401713   mov         eax,ecx
00401715   add         eax,1
0040171A   mov         ecx,eax
0040171C   jmp         MAIN$SLIDESOLVE+45Fh (004016ed)
0040171E   mov         eax,esi
00401720   cmp         eax,0
00401725   jge         MAIN$SLIDESOLVE+49Bh (00401729)
00401727   neg         eax
00401729   mov         edx,10549h
0040172E   mov         dword ptr [ebp-54h],edx
00401731   cdq
00401732   idiv        eax,dword ptr [ebp-54h]
00401735   mov         eax,edx
00401737   mov         dword ptr [H (00473714)],eax
60:         write (6,*) H,bored

Whereas by writing out the product,

59:         H = MOD(ABS(BRD(1)*BRD(2)),APRIME)
004016E3   mov         eax,dword ptr [BRD (00473718)]
004016E9   imul        eax,dword ptr [BRD+4 (0047371c)]
004016F0   cmp         eax,0
004016F5   jge         MAIN$SLIDESOLVE+46Bh (004016f9)
004016F7   neg         eax
004016F9   mov         esi,10549h
004016FE   cdq
004016FF   idiv        eax,esi
00401701   mov         eax,edx
00401703   mov         dword ptr [H (00473714)],eax
60:         write (6,*) H,bored

(The source below has the ABS outside the MOD: I don't care what sort of mod is used here, just that negative numbers be as scrambled as positive) In both cases the array indexing is checkable by the compiler at compile time so that there need be no run-time checking on that. Such bound checking may be not as strict as might be expected when EQUIVALENCE tricks are involved. In tests with a variant board size of 3x4, the board position array was declared BOARD(N) where N = 12, but was still equivalenced to BORED(4) and so still allowed room for sixteen elements in BOARD. Subroutine UNPACK was not written to deal with anything other than a 4x4 board and so accessed elements 13, 14, 15, and 16 of BOARD that were outside its declared upper bound of 12, but no run-time objection was made. Similarly with a 3x3 board.

Granted a flexible pre-processor scheme (as in pl/i, say) one could imagine a menu of tricks being selected from according to the board shape specified, but without such a facility, the constants are merely named rather than literal. Any change, such as to a 3x4 board, can be made by adjusting the appropriate PARAMETER and many usages will adjust accordingly. Others will require adjustment by the diligent programmer for good results. In the absence of a pre-processor one could present the various possible code sequences surrounded by tests as in
IF (NR.EQ.4) THEN
 code specialised for NR = 4
ELSE IF (NR.EQ.3) THEN
 code specialised for NR = 3
END IF

and hope that the compiler would carry forward the actual value of NR into the IF-statement's conditional expression, recognise that the result is also constant (for the current compilation with a particular value of NR) and so generate code only for the case that the expression came out as true, without any test to select this being employed in the compiled code. This soon becomes a tangle of combinations, and has not been attempted. And there could be difficulties too. One wonders if, say, with NR = 3, the specialised code for the NR = 4 case could contain a mention of element 4 of an array which is actually of size NR. Would the compiler regard this as an error, given that it will be discarding this code anyway? Even if one used NR rather than a literal such as 4 there could still be problems, such as code calling for a division by (NR - 3).

Implementing the plan had its problems. Since the INDEX array is accessed randomly it should be in memory, but alas if it is large (and now there are two of them) the Compaq Visual Fortran 6.6 F90/95 compiler complains "total image size exceeds max (268435456): image may not run" - but if it is not so large, when many entries are made the hash separation will be heavily overloaded and the chains of entries with equal hash codes will not be short. One could possibly mess about with allocatable arrays as a different storage scheme is used for them, but instead, a disc file for the index array as well as a stash file for the entries. This also would mean that the stash and its index could survive for another run, otherwise if the index data were in memory only, some scheme would be needed to save the index, or to redevelop the index from the stash file on a restart. Both the stash and index files require random access, but the stash file grows sequentially. The index file however has to start full-sized, with all values zero. The obvious ploy of writing zero values to the file as a sequential file works well enough, but on re-opening the index file for random access, there are complaints about accessing a non-existing record. By initialising the file via random access, writing a zero to record one, two, three, etc. sequentially, no such complaint appeared and everything worked. But the initialisation was very slow, taking many minutes. Oddly, writing a zero to the last record without doing anything to intervening records not only worked but did so rapidly. It appeared that the intervening records were not prepared by the I/O subsystem at all, as very little I/O action took place. At a guess, only if a filesystem's allocation block was written to was that block made into a proper file piece with all zero values.

During these developments, a mistype produced an odd result. With F90, many array operations became possible, and accidentally, I typed WRITE (WRK,REC = array) etc but omitted to specify which element of the array was to be used, as in WRITE (WRK,REC = array(1)) etc A positive interpretation of this construction would be to hope that the I/O list (the etc) would be written to multiple records of I/O unit WRK, to array(1), array(2), and so on without re-evaluation of the I/O list. Alas, a file containing 19MB of zeroes resulted. Obviously some mistake. Similarly, one could hope that WRITE (OUT,66) stuff where OUT(1) = 6 (for standard output), and OUT(2) = 10 (a disc file, logging what has been written) would save on repeating WRITE statements, but alas, compatibility with older Fortran requires that such a statement places its output into the storage occupied by array OUT. This might be avoided by a variant form, WRITE (UNIT = OUT,66) stuff to signify that I/O unit numbers are being given, but alas, the compiler doesn't agree.

Source

      SUBROUTINE PROUST(T)	!Remembrance of time passed.
       DOUBLE PRECISION T	!The time, in seconds. Positive only, please.
       DOUBLE PRECISION S	!A copy I can mess with.
       TYPE TIMEWARP		!Now prepare a whole lot of trickery for expressing the wait time.
        INTEGER LIMIT		!The upper limit for the usage.
        INTEGER STEP		!Conversion to the next unit.
        CHARACTER*4 NAME	!A suitable abbreviated name for the accepted unit.
       END TYPE TIMEWARP	!Enough of this.
       INTEGER CLOCKCRACK	!How many different units might I play with?
       PARAMETER (CLOCKCRACK = 5)	!This should so.
       TYPE(TIMEWARP) TIME(CLOCKCRACK)	!One set, please.
       PARAMETER (TIME = (/		!The mention of times lost has multiple registers.
     1  TIMEWARP(99, 60,"secs"),	!Beware 99.5+ rounding up to 100.
     2  TIMEWARP(99, 60,"mins"),	!Likewise with minutes.
     3  TIMEWARP(66, 24,"hrs!"),	!More than a few days might as well be in days.
     4  TIMEWARP(99,365,"days"),	!Too many days, and we will speak of years.
     5  TIMEWARP(99,100,"yrs!")/))	!And the last gasp converts to centuries.
       INTEGER CC		!A stepper for these selections.
       CHARACTER*4 U		!The selected unit.
       INTEGER MSG		!The mouthpiece.
       COMMON/IODEV/ MSG	!Used in common.
        S = T			!A working copy.
        DO CC = 1,CLOCKCRACK	!Now re-assess DT, with a view to announcing a small number.
          IF (S.LE.TIME(CC).LIMIT) THEN	!Too big a number?
            U = TIME(CC).NAME			!No, this unit will suffice.
            GO TO 10				!Make off to use it.
          END IF			!But if the number is too big,
          S = S/TIME(CC).STEP		!Escalate to the next larger unit.
        END DO 			!And see if that will suffice.
        U = "Cys!!"		!In case there are too many years, this is the last gasp.
   10   WRITE (MSG,11) S,U	!Say it.
   11   FORMAT (F7.1,A4,$)	!But don't finish the line.
       END SUBROUTINE PROUST	!A sigh.

       CHARACTER*15 FUNCTION HMS(T)	!Report the time of day.
Careful! Finite precision and binary/decimal/sexagesimal conversion could cause 2:30:00am. to appear as 2:29:60am.
        DOUBLE PRECISION S,T	!Seconds (completed) into the day.
        INTEGER H,M		!More traditional units are to be extracted.
        INTEGER SECONDSINDAY	!A canonical day.
        PARAMETER (SECONDSINDAY = 24*60*60)	!Of nominal seconds.
        CHARACTER*15 TEXT	!A scratchpad.
         H = T			!Truncate into an integer.
         S = T - (H - 1)/SECONDSINDAY*SECONDSINDAY	!Thus allow for midnight = hour 24.
         IF (S.EQ.SECONDSINDAY/2) THEN	!This might happen.
           TEXT = "High Noon!"	!Though the chances are thin.
         ELSE IF (S.EQ.SECONDSINDAY) THEN	!This is synonymous with the start of the next day.
           TEXT = "Midnight!"	!So this presumably won't happen.
         ELSE		!But more likely are miscellaneous values.
           H = S/3600		!Convert seconds into whole hours completed.
           S = S - H*3600	!The remaining time.
           M = S/60		!Seconds into minutes completed.
           S = S - M*60		!Remove them.
           IF (S .GE. 59.9995D0) THEN	!Via format F6.3, will this round up to 60?
             S = 0		!Yes. Curse recurring binary sequences for decimal.
             M = M + 1		!So, up the minute count.
             IF (M.GE.60) THEN	!Is there an overflow here too?
               M = 0		!Yes.
               H = H + 1	!So might appear 24:00:00.000 though it not be Midnight!
             END IF		!So much for twiddling the minutes.
           END IF		!And twiddling the hours.
           IF (H.LT.12) THEN	!A plague on the machine mentality.
             WRITE (TEXT,1) H,M,S,"am."	!Ante-meridian.
    1        FORMAT (I2,":",I2,":",F6.3,A3)	!Thus.
            ELSE		!For the post-meridian, H >= 12.
             IF (H.GT.12) H = H - 12	!Adjust to civil usage. NB! 12 appears.
             WRITE (TEXT,1) H,M,S,"pm."	!Thus. Post-meridian.
           END IF	!So much for those fiddles.
           IF (TEXT(4:4).EQ." ") TEXT(4:4) = "0"	!Now help hint that the
           IF (TEXT(7:7).EQ." ") TEXT(7:7) = "0"	! character string is one entity.
         END IF		!So much for preparation.
         HMS = TEXT	!The result.
       END FUNCTION HMS	!Possible compiler confusion if HMS is invoked in a WRITE statement.

       DOUBLE PRECISION FUNCTION NOWWAS(WOT)	!Ascertain the local time for interval assessment.
Compute with whole day numbers, to avoid day rollover annoyances.
Can't use single precision and expect much discrimination within a day.
C I'd prefer a TIMESTAMP(Local) and a TIMESTAMP(GMT) system function.
C Quite likely, the system separates its data to deliver the parameters, which I then re-glue.
        INTEGER WOT	!What sort of time do I want?
        REAL*8 TIME	!A real good time.
        INTEGER MARK(8)	!The computer's clock time will appear here, but fragmented.
         IF (WOT.LE.0) THEN	!Just the CPU time for this.
           CALL CPU_TIME(TIME)	!Apparently in seconds since starting.
          ELSE			!But normally, I want a time-of-day now.
           CALL DATE_AND_TIME(VALUES = MARK)	!Unpack info that I will repack.
c           WRITE (6,1) MARK
c    1      FORMAT ("The computer clock system reports:",
c     1      /"Year",I5,", Month",I3,", Day",I3,
c     2      /" Minutes from GMT",I5,
c     3      /" Hour",I3,", Minute",I3,",Seconds",I3,".",I3)
           TIME = (MARK(5)*60 + MARK(6))*60 + MARK(7) + MARK(8)/1000D0	!By the millisecond, to seconds.
           IF (WOT.GT.1) TIME = TIME - MARK(4)*60	!Shift back to GMT, which may cross a day boundary.
c          TIME = DAYNUM(MARK(1),MARK(2),MARK(3)) + TIME/SECONDSINDAY	!The fraction of a day is always less than 1 as MARK(5) is declared < 24.
           TIME = MARK(3)*24*60*60 + TIME	!Not bothering with DAYNUM, and converting to use seconds rather than days as the unit.
         END IF			!A simple number, but, multiple trickeries. The GMT shift includes daylight saving's shift...
         NOWWAS = TIME		!Thus is the finger of time found.
       END FUNCTION NOWWAS	!But the Hand of Time has already moved on.

      MODULE SLIDESOLVE		!Collect the details for messing with the game board.
       INTEGER NR,NC,N				!Give names to some sizes.
       PARAMETER (NR = 4, NC = 4, N = NR*NC)	!The shape of the board.
       INTEGER*1 BOARD(N),TARGET(N),ZERO(N)	!Some scratchpads.
       INTEGER BORED(4)				!A re-interpretation of the storage containing the BOARD.
       CHARACTER*(N) BOAR			!Another, since the INDEX function only accepts these.
       EQUIVALENCE (BORED,BOARD,BOAR)		!All together now!
       CHARACTER*1 DIGIT(0:35)			!This will help to translate numbers to characters.
       PARAMETER (DIGIT = (/"0","1","2","3","4","5","6","7","8","9",
     1  "A","B","C","D","E","F","G","H","I","J",	!I don't anticipate going beyond 15.
     2  "K","L","M","N","O","P","Q","R","S","T",	!But, for completeness...
     3  "U","V","W","X","Y","Z"/))			!Add a few more.
      CONTAINS
       SUBROUTINE SHOW(NR,NC,BOARD)	!The layout won't work for NC > 99...
        INTEGER NR,NC		!Number of rows and columns.
        INTEGER*1 BOARD(NC,NR)	!The board is stored transposed, in Furrytran!
        INTEGER R,C		!Steppers.
        INTEGER MSG		!Keep the compiler quiet.
        COMMON/IODEV/ MSG	!I talk to the trees...
         WRITE (MSG,1) (C,C = 1,NC)	!Prepare a heading.
    1    FORMAT ("Row|",9("__",I1,:),90("_",I2,:))	!This should suffice.
         DO R = 1,NR		!Chug down the rows, for each showing a succession of columns.
           WRITE (MSG,2) R,BOARD(1:NC,R)	!Thus, successive elements of storage. Storage style is BOARD(column,row).
    2      FORMAT (I3,"|",99I3)		!Could use parameters, but enough.
         END DO			!Show columns across and rows down, despite the storage order.
       END SUBROUTINE SHOW	!Remember to transpose the array an odd number of times.

       SUBROUTINE UNCRAM(IT,BOARD)	!Recover the board layout..
        INTEGER IT(2)		!Two 32-bit integers hold 16 four-bit fields in a peculiar order.
        INTEGER*1 BOARD(*)	!This is just a simple, orderly sequence of integers.
        INTEGER I,HIT		!Assistants.
         DO I = 0,8,8		!Unpack into the work BOARD.
           HIT = IT(I/8 + 1)		!Grab eight positions, in four bits each..
                                BOARD(I + 5) = IAND(HIT,15)	!The first is position 5.
           HIT = ISHFT(HIT,-4); BOARD(I + 1) = IAND(HIT,15)	!Hex 48372615
           HIT = ISHFT(HIT,-4); BOARD(I + 6) = IAND(HIT,15)	!and C0BFAE9D
           HIT = ISHFT(HIT,-4); BOARD(I + 2) = IAND(HIT,15)	!For BOARD(1) = 1, BOARD(2) = 2,...
           HIT = ISHFT(HIT,-4); BOARD(I + 7) = IAND(HIT,15)	!This computer is (sigh) little-endian.
           HIT = ISHFT(HIT,-4); BOARD(I + 3) = IAND(HIT,15)	!Rather than mess with more loops,
           HIT = ISHFT(HIT,-4); BOARD(I + 8) = IAND(HIT,15)	!Explicit code is less of a brain strain.
           HIT = ISHFT(HIT,-4); BOARD(I + 4) = IAND(HIT,15)	!And it should run swiftly, too...
         END DO				!Only two of them.
       END SUBROUTINE UNCRAM	!A different-sized board would be a problem too.

       INTEGER*8 FUNCTION ZDIST(BOARD)	!Encode the board's positions against the ZERO sequence.
        INTEGER*1 BOARD(N)	!The values of the squares.
        LOGICAL*1 AVAIL(N)	!The numbers will be used one-by-one to produce ZC.
        INTEGER BASE		!This is not a constant, such as ten.
        INTEGER M,IT		!Assistants.
         AVAIL = .TRUE.			!All numbers are available.
         BASE = N			!The first square has all choices.
         ZDIST = 0			!Start the encodement of choices.
         DO M = 1,N			!Step through the board's squares.
           IT = BOARD(M)			!Grab the square's number. It is the index into ZERO.
           IF (IT.EQ.0) IT = N			!But in ZERO, the zero is at the end, damnit.
           AVAIL(IT) = .FALSE.			!This number is now used.
           ZDIST = ZDIST*BASE + COUNT(AVAIL(1:IT - 1))	!The number of available values to skip to reach it.
           BASE = BASE - 1			!Option count for the next time around.
         END DO				!On to the next square.
       END FUNCTION ZDIST	!ZDIST(ZERO) = 0.

       SUBROUTINE REPORT(R,WHICH,MOVE,BRD)	!Since this is invoked in two places.
        INTEGER R		!The record number of the position.
        CHARACTER*(*) WHICH	!In which stash.
        CHARACTER*1 MOVE	!The move code.
        INTEGER BRD(2)		!The crammed board position.
        INTEGER*1 BOARD(N)	!Uncrammed for nicer presentation.
        INTEGER*8 ZC		!Encodes the position in a mixed base.
        INTEGER ZM,ZS		!Alternate forms of distance.
        DOUBLE PRECISION ZE	!This is Euclidean.
        INTEGER MSG		!Being polite about usage,
        COMMON/IODEV/MSG	!Rather than mysterious constants.
         CALL UNCRAM(BRD,BOARD)		!Isolate the details.
         ZM = MAXVAL(ABS(BOARD - ZERO))			!A norm. |(x,y)| = r gives a square shape.
         ZS = SUM(ABS(BOARD - ZERO))			!A norm. |(x,y)| = r gives a diamond shape.
         ZE = SQRT(DFLOAT(SUM((BOARD - ZERO)**2)))	!A norm. |(x,y)| = r gives a circle.
         ZC = ZDIST(BOARD)				!Encodement against ZERO.
         WRITE (MSG,1) R,WHICH,MOVE,DIGIT(BOARD),ZM,ZS,ZE,ZC	!After all that,
    1    FORMAT (I11,A6,A5,1X,"|",<NR - 1>(<NC>A1,"/"),<NC>A1,"|",	!Show the move and the board
     1    2I8,F12.3,I18)					!Plus four assorted distances.
       END SUBROUTINE REPORT	!Just one line is produced.

       SUBROUTINE PURPLE HAZE(BNAME)	!Spreads a red and a blue tide.
        CHARACTER*(*) BNAME		!Base name for the work files.
        CHARACTER*(N) BRAND		!Name part based on the board sequence.
        CHARACTER*(LEN(BNAME) + 1 + N + 4) FNAME	!The full name.
Collect the details for messing with the game board.
        CHARACTER*4 TIDE(2)			!Two tides will spread forth.
        PARAMETER (TIDE = (/" Red","Blue"/))	!With these names.
        INTEGER LZ,LOCZ(2),LOCI(2),ZR,ZC	!Location via row and column.
        EQUIVALENCE(LOCZ(1),ZR),(LOCZ(2),ZC)	!Sometimes separate, sometimes together.
        INTEGER WAY(4),HENCE,W,M,D,WAYS(2,4)	!Direction codes.
        PARAMETER (WAY = (/   +1,  -NC,   -1,  +NC/))	!Directions for the zero square, in one dimension.
        PARAMETER (WAYS = (/0,+1, -1,0, 0,-1, +1,0/))	!The same, but in (row,column) style.
        CHARACTER*1 WNAMEZ(0:4),WNAMEF(0:4)	!Names for the directions.
        PARAMETER (WNAMEZ = (/" ","R","U","L","D"/))	!The zero square's WAYS.
        PARAMETER (WNAMEF = (/" ","L","D","R","U"/))	!The moved square's ways are opposite.
Create two hashed stashes. A stash file and its index file, twice over.
        INTEGER APRIME				!Determines the size of the index.
        PARAMETER (APRIME = 199 999 991)	!Prime 11078917. Prime 6666666 = 116 743 349. Perhaps 1999999973?
        INTEGER HCOUNT(2),NINDEX(2)		!Counts the entries in the stashes and their indices.
        INTEGER P,HIT				!Fingers to entries in the stash.
        INTEGER SLOSH,HNEXT			!Advances from one surge to the next.
        INTEGER IST(2),LST(2),SURGE(2)		!Define the perimeter of a surge.
        INTEGER HEAD,LOOK			!For chasing along a linked-list of records.
        TYPE AREC				!Stores the board position, and helpful stuff.
         INTEGER NEXT					!Links to the next entry that has the same hash value.
         INTEGER PREV					!The entry from which this position was reached.
         INTEGER MOVE					!By moving the zero in this WAY.
         INTEGER BRD(2)					!Squeezed representation of the board position.
        END TYPE AREC				!Greater compaction (especially of MOVE) would require extra crunching.
        INTEGER LREC				!Record length, in INTEGER-size units. I do some counting.
        PARAMETER (LREC = 5)			!For the OPEN statement.
        TYPE(AREC) ASTASH,APROBE		!I need two scratchpads.
        INTEGER NCHECK				!Number of new positions considered.
        INTEGER NLOOK(2),PROBES(2),NP(2),MAXP(2)!Statistics for the primary and secondary searches resulting.
        LOGICAL SURGED(2)			!A SLOSH might not result in a SURGE.
Catch   the red/blue meetings, if any.
        INTEGER MANY,LONG			!They may be many, and, long.
        PARAMETER (MANY = 666,LONG = 66)	!This should do.
        INTEGER NMET,MET(2,MANY)		!Identify the meeting positions, in their own stash.
        INTEGER NTRAIL,TRAIL(LONG)		!Needed to follow the moves.
        INTEGER NS,LS(MANY)			!Count the shove sequences.
        CHARACTER*128 SHOVE(MANY)		!Record them.
Conglomeration of support stuff.
        LOGICAL EXIST				!For testing the presence of a disc file.
        INTEGER I,IT				!Assistants.
        DOUBLE PRECISION T1,T2,E1,E2,NOWWAS	!Time details.
        CHARACTER*15 HMS			!A clock.
        INTEGER MSG,KBD,WRK(2),NDX(2)	!I/O unit numbers.
        COMMON/IODEV/ MSG,KBD,WRK,NDX	!I talk to the trees...
         NS = 0	!No shove sequences have been found.
Concoct some disc files for storage areas, reserving the first record of each as a header.
   10    BOARD = ZERO	!The red tide spreads from "zero".
         DO W = 1,2	!Two work files are required.
           WRITE(MSG,11) TIDE(W)	!Which one this time?
   11      FORMAT (/,"Tide ",A)		!Might as well supply a name.
           DO I = 1,N		!Produce a text sequence for the board layout.
             BRAND(I:I) = DIGIT(BOARD(I))	!One by one...
           END DO		!BRAND = DIGIT(BOARD)
           FNAME = BNAME//"."//BRAND//".dat"	!It contains binary stuff, so what else but data?
           INQUIRE (FILE = FNAME, EXIST = EXIST)	!Perhaps it is lying about.
   20      IF (EXIST) THEN				!Well?
             WRITE (MSG,*) "Restarting from file ",FNAME	!One hopes its content is good.
             OPEN (WRK(W),FILE = FNAME,STATUS = "OLD",ACCESS = "DIRECT",	!Random access is intended.
     1        FORM = "UNFORMATTED",BUFFERED = "YES",RECL = LREC)		!Using record numbers as the key.
             FNAME = BNAME//"."//BRAND//".ndx"		!Now go for the accomplice.
             INQUIRE (FILE = FNAME, EXIST = EXIST)	!That contains the index.
             IF (.NOT.EXIST) THEN			!Well?
               WRITE (MSG,*) " ... except, no file ",FNAME	!Oh dear.
               CLOSE(WRK(W))				!So, no index for the work file. Abandon it.
               GO TO 20					!And thus jump into the ELSE clause below.
             END IF				!Seeing as an explicit GO TO would be regarded as improper...
             READ (WRK(W),REC = 1) HCOUNT(W),SURGE(W),IST(W),LST(W)!Get the header information.
             WRITE (MSG,22) HCOUNT(W),SURGE(W),IST(W),LST(W)	!Reveal.
   22        FORMAT (" Stashed ",I0,". At surge ",I0,		!Perhaps it will be corrupt.
     1        " with the boundary stashed in elements ",I0," to ",I0)	!If so, this might help the reader.
             OPEN (NDX(W),FILE = FNAME,STATUS = "OLD",ACCESS="DIRECT",	!Now for the accomplice.
     1        FORM = "UNFORMATTED",BUFFERED = "YES",RECL = 1)		!One INTEGER per record.
             READ(NDX(W), REC = 1) NINDEX(W)			!This count is maintained, to avoid a mass scan.
             WRITE (MSG,23) NINDEX(W),APRIME			!Exhibit the count.
   23        FORMAT (" Its index uses ",I0," of ",I0," entries.")	!Simple enough.
            ELSE			!But, if there is no stash, create a new one.
             WRITE (MSG,*) "Preparing a stash in file ",FNAME	!Start from scratch.
             OPEN (WRK(W),FILE = FNAME,STATUS="REPLACE",ACCESS="DIRECT",	!I intend non-sequential access...
     1        FORM = "UNFORMATTED",BUFFERED = "YES",RECL = LREC)		!And, not text.
             HCOUNT(W) = 1		!Just one position is known, the final position.
             SURGE(W) = 0		!It has not been clambered away from.
             IST(W) = 1			!The first to inspect at the current level.
             LST(W) = 1			!The last.
             WRITE (WRK(W),REC = 1) HCOUNT(W),SURGE(W),IST(W),LST(W),0	!The header.
             FNAME = BNAME//"."//BRAND//".ndx"	!Now for the associated index file..
             WRITE (MSG,*) "... with an index in file ",FNAME	!Announce before attempting access.
             OPEN (NDX(W),FILE = FNAME,STATUS = "REPLACE",ACCESS=	!Lest there be a mishap.
     1        "DIRECT",FORM = "UNFORMATTED",BUFFERED = "YES",RECL = 1)	!Yep. Just one value per record.
             WRITE (MSG,*) APRIME," zero values for an empty index."	!This may cause a pause.
             NINDEX(W) = 1				!The index will start off holding one used entry.
             WRITE (NDX(W),REC = 1) NINDEX(W)	!Save this count in the header record.
             WRITE (NDX(W),REC = 1 + APRIME) 0	!Zero values will also appear in the gap!
             ASTASH.NEXT = 0		!The first index emtry can never collide with another in an empty index.
             ASTASH.PREV = 0		!And it is created sufficient unto itself.
             ASTASH.MOVE = 0		!Thus, it is not a descendant, but immaculate.
             ASTASH.BRD(1) = BORED(1)*16 + BORED(2)	!Only four bits of the eight supplied are used.
             ASTASH.BRD(2) = BORED(3)*16 + BORED(4)	!So interleave them, pairwise.
             SLOSH = ASTASH.BRD(1)*ASTASH.BRD(2)	!Mash the bits together.
             HIT = ABS(MOD(SLOSH,APRIME)) + 2		!Make a hash. Add one since MOD starts with zero.
             WRITE (NDX(W),REC = HIT) HCOUNT(W)		!Adding another one to dodge the header as well.
             WRITE (MSG,24) BOARD,BORED,ASTASH.BRD,	!Reveal the stages.
     1        SLOSH,SLOSH,SLOSH,APRIME,HIT				!Of the mostly in-place reinterpretations.
   24        FORMAT (<N>Z2," is the board layout in INTEGER*1",/,	!Across the columns and down the rows.
     1        4Z8," is the board layout in INTEGER*4",/,		!Reinterpret as four integers.
     2        2(8X,Z8)," ..interleaved into two INTEGER*4",/,		!Action: Interleaved into two.
     3        Z32," multiplied together in INTEGER*4",/,		!Action: Their product.
     4        I32," as a decimal integer.",/,				!Abandoning hexadecimal.
     5        "ABS(MOD(",I0,",",I0,")) + 2 = ",I0,			!The final step.
     6        " is the record number for the first index entry.")	!The result.
             WRITE (WRK(W),REC = HCOUNT(W) + 1) ASTASH	!Record one is reserved as a header...
           END IF				!Either way, a workfile should be ready now.
           IF (W.EQ.1) BOARD = TARGET	!Thus go for the other work file.
         END DO		!Only two iterations, but a lot of blather.
         SLOSH = MINVAL(SURGE,DIM = 1)	!Find the laggard.

Cast forth a heading for the progress table to follow..
         WRITE (MSG,99)
   99    FORMAT (/,7X,"|",3X,"Tidewrack Boundary Positions  |",
     1    6X,"Positions",5X,"|",7X,"Primary Probes",9X,"Index Use",
     2    4X,"|",5X,"Secondary Probes",3X,"|"," Memory of Time Passed",/,
     3    "Surge",2X,"|",6X,"First",7X,"Last",6X,"Count|",
     4    4X,"Checked Deja vu%|",7X,"Made Max.L  Avg.L|   Used%",
     5    5X,"Load%|",7X,"Made Max.L  Avg.L|",6X,"CPU",8X,"Clock")

Chase along the boundaries of the red and the blue tides, each taking turns as primary and secondary interests.
  100    SLOSH = SLOSH + 1	!Another advance begins.
      WW:DO W = 1,2	!The advance is made in two waves, each with its own statistics.
           M = 3 - W		!Finger the other one.
           NMET = 0		!No meetings have happened yet.
           IF (SURGE(W).GE.SLOSH) CYCLE WW	!Prefer to proceed with matched surges.
           WRITE (MSG,101) SLOSH,TIDE(W),IST(W),LST(W),LST(W)-IST(W)+1	!The boundary to be searched.
  101      FORMAT (I2,1X,A4,"|",3I11,"|",$)				!This line will be continued.
           NCHECK = 0		!No new positions have been prepared.
           NLOOK = 0		!So the stashes have not been searched for any of them.
           PROBES = 0		!And no probes have been made in any such searches.
           MAXP = 0		!So the maximum length of all probe chains is zero so far.
           HNEXT = LST(W) + 1	!This will be where the first new position will be stashed.
           T1 = NOWWAS(0)	!Note the accumulated CPU time at the start of the boundary ride..
           E1 = NOWWAS(2)	!Time of day, in seconds. GMT style (thus not shifted by daylight saving)
        PP:DO P = IST(W),LST(W)	!These are on the edge of the tide. Spreading proceeds.
             READ (WRK(W),REC = P + 1) ASTASH	!Obtain a position, remembering to dodge the header record.
             HENCE = ASTASH.MOVE		!The move (from ASTASH.PREV) that reached this position.
             IF (HENCE.NE.0) HENCE = MOD(HENCE + 1,4) + 1	!The reverse of that direction. Only once zero. Sigh.
             CALL UNCRAM(ASTASH.BRD,BOARD)	!Unpack into the work BOARD.
             LZ = INDEX(BOAR,CHAR(0))	!Find the BOARD square with zero.
             ZR =    (LZ - 1)/NC + 1	!Convert to row and column in LOCZ to facilitate bound checking.
             ZC = MOD(LZ - 1,NC) + 1	!Two divisions, sigh. Add a special /\ syntax? (ZR,ZC) = (LZ - 1)/\NC + 1
Consider all possible moves from position P, If a new position is unknown, add it to the stash.
          DD:DO D = 1,4			!Step through the possible directions in which the zero square might move.
               IF (D.EQ.HENCE) CYCLE DD		!Don't try going back whence this came.
               LOCI = LOCZ + WAYS(1:2,D)	!Finger the destination of the zero square, (row,column) style.
               IF (ANY(LOCI.LE.0)) CYCLE DD	!No wrapping left/right or top/bottom.
               IF (ANY(LOCI.GT.(/NR,NC/))) CYCLE DD	!No .OR. to avoid the possibility of non-shortcut full evaluation.
               NCHECK = NCHECK + 1		!So, here is another position to inspect.
               NP = 0				!No probes of stashes W or M for it have been made.
               IT = WAY(D) + LZ			!Finger the square that is to move to the adjacent zero.
               BOARD(LZ) = BOARD(IT)		!Move that square's content to the square holding the zero.
               BOARD(IT) = 0			!It having departed.
               ASTASH.BRD(1) = BORED(1)*16 + BORED(2)	!Pack the position list
               ASTASH.BRD(2) = BORED(3)*16 + BORED(4)	!Without fussing over adjacency,
               HIT = ABS(MOD(ASTASH.BRD(1)*ASTASH.BRD(2),APRIME)) + 2	!Crunch the hash index.
               READ (NDX(W),REC = HIT) HEAD	!Refer to the index, which fingers the first place to look.
               LOOK = HEAD			!This may be the start of a linked-list.
               IF (LOOK.EQ.0) NINDEX(W) = NINDEX(W) + 1	!Or, a new index entry will be made.
               IF (LOOK.NE.0) NLOOK(1) = NLOOK(1) + 1	!Otherwise, we're looking at a linked-list, hopefully short.
               DO WHILE (LOOK.NE.0)		!Is there a stash entry to look at?
                 NP(1) = NP(1) + 1			!Yes. Count a probe of the W stash.
                 READ (WRK(W),REC = LOOK + 1) APROBE	!Do it. (Dodging the header record)
                 IF (ALL(ASTASH.BRD.EQ.APROBE.BRD)) GO TO 109	!Already seen? Ignore all such as previously dealt with.
                 LOOK = APROBE.NEXT			!Perhaps there follows another entry having the same index.
               END DO				!And eventually, if there was no matching entry,
               HCOUNT(W) = HCOUNT(W) + 1	!A new entry is to be added to stash W, linked from its index.
               IF (HCOUNT(W).LE.0) STOP "HCOUNT overflows!"	!Presuming the usual two's complement style.
               WRITE (NDX(W),REC = HIT) HCOUNT(W)	!The index now fingers the new entry in ASTASH.
               ASTASH.NEXT = HEAD			!Its follower is whatever the index had fingered before.
               ASTASH.PREV = P			!This is the position that led to it.
               ASTASH.MOVE = D			!Via this move.
               WRITE (WRK(W),REC = HCOUNT(W) + 1) ASTASH	!Place the new entry, dodging the header.
Check the other stash for this new position. Perhaps there, a meeting will be found!
               READ (NDX(M),REC = HIT) LOOK	!The other stash uses the same hash function but has its own index.
               IF (LOOK.NE.0) NLOOK(2) = NLOOK(2) + 1	!Perhaps stash M has something to look at.
               DO WHILE(LOOK.NE.0)		!Somewhere along a linked-list.
                 NP(2) = NP(2) + 1			!A thorough look may involve multiple probes.
                 READ(WRK(M),REC = LOOK + 1) APROBE	!Make one.
                 IF (ALL(ASTASH.BRD.EQ.APROBE.BRD)) THEN!A match?
                   IF (NMET.LT.MANY) THEN			!Yes! Hopefully, not too many already.
                     NMET = NMET + 1					!Count another.
                     MET(W,NMET) = HCOUNT(W)				!Save a finger to the new entry.
                     MET(M,NMET) = LOOK					!And to its matching counterparty.
                    ELSE						!But if too numerous for my list,
                     WRITE (MSG,108) TIDE(W),HCOUNT(W),TIDE(M),LOOK	!Announce each.
  108                FORMAT ("Can't save ",A,1X,I0," matching ",A,1X,I0)!Also wrecking my tabular layout.
                   END IF						!So much for recording a match.
                   GO TO 109					!Look no further for the new position; it is found..
                 END IF					!So much for a possible match.
                 LOOK = APROBE.NEXT			!Chase along the linked-list.
               END DO				!Thus checking all those hashing to the same index.
Completed the probe.
  109          MAXP = MAX(MAXP,NP)		!Track the maximum number of probes in any search..
               PROBES = PROBES + NP		!As well as their count.
               BOARD(IT) = BOARD(LZ)		!Finally, undo the move.
               BOARD(LZ) = 0			!To be ready for the next direction.
             END DO DD			!Consider another direction.
           END DO PP		!Advance P to the next spreading possibility.
Completed one perimeter sequence. Cast forth some statistics.
  110      T2 = NOWWAS(0)	!A boundary patrol has been completed.
           E2 = NOWWAS(2)	!And time has passed.
           HIT = HCOUNT(W) - HNEXT + 1	!The number of new positions to work from in the next layer.
           WRITE (MSG,111) NCHECK,100.0*(NCHECK - HIT)/NCHECK,	!Tested, and %already seen
     1      NLOOK(1),MAXP(1),FLOAT(PROBES(1))/MAX(NLOOK(1),1),	!Search statistics.
     2      100.0*NINDEX(W)/APRIME,100.0*HCOUNT(W)/APRIME,	!Index occupancy: used entries, and load.
     3      NLOOK(2),MAXP(2),FLOAT(PROBES(2))/MAX(NLOOK(2),1)	!Other stash's search statistics.
  111      FORMAT (I11,F9.2,"|",I11,I6,F7.3,"|",F8.3,F10.3,"|",	!Attempt to produce regular columns.
     1      I11,I6,F7.3,"|"$)					!To be continued...
           T1 = T2 - T1			!Thus, how much CPU time was used perusing the perimeter.
           E1 = E2 - E1			!Over the elapsed time.
           CALL PROUST(T1)		!Muse over the elapsed CPU time, in seconds.
           CALL PROUST(E1)		!And likewise the elapsed clock time.
           E2 = NOWWAS(1)		!Civil clock, possibly adjusted for daylight saving.
           IF (E1.LE.0) THEN		!The offered timing may be too coarse.
             WRITE (MSG,112) HMS(E2)		!So, just finish the line.
  112        FORMAT (8X,A)			!With a time of day.
            ELSE			!But if some (positive) clock time was measured as elapsing,
             WRITE (MSG,113) T1/E1*100,HMS(E2)	!Offer a ratio as well.
  113        FORMAT (F6.2,"%",1X,A)		!Percentages are usual.
           END IF			!Enough annotation.
Could there be new positions to check? HCOUNT will have been increased if so.
           SURGED(W) = HCOUNT(W).GE.HNEXT	!The boundary has been extended to new positions.
           IF (SURGED(W)) THEN		!But, are there any new positions?
             IST(W) = HNEXT			!Yes. The first new position would have been placed here.
             LST(W) = HCOUNT(W)			!This is where the last position was placed.
             SURGE(W) = SLOSH			!The new surge is ready.
             WRITE (WRK(W),REC = 1) HCOUNT(W),SURGE(W),IST(W),LST(W)	!Update the headers correspondingly..
             WRITE (NDX(W), REC = 1) NINDEX(W)	!Otherwise, a rescan would be needed on a restart.
           ELSE IF (SURGE(W) + 1 .EQ. SLOSH) THEN	!No new positions. First encounter?
             LOOK = LST(W) - IST(W) + 1		!Yes. How many dead ends are there?
             WRITE (MSG,114) LOOK		!Announce.
  114        FORMAT (/,"The boundary has not surged to new positions!",/
     1       "The now-static boundary has ",I0)
             LOOK = LOOK/666 + 1		!If there are many, don't pour forth every one.
             IF (LOOK.GT.1) WRITE (MSG,115) LOOK!Some restraint.
  115        FORMAT (6X,"... Listing step: ",I0)!Rather than rolling forth a horde.
             WRITE (MSG,121)			!Re-use the heading for the REPORT.
             DO P = IST(W),LST(W),LOOK		!Step through the dead ends, possibly sampling every one.
               READ (WRK(W),REC = P + 1) ASTASH		!Grab a position.
               CALL REPORT(P,TIDE(W),WNAMEF(ASTASH.MOVE),ASTASH.BRD)	!Describe it.
             END DO				!On to the next dead end.
           END IF			!Perhaps the universe has been filled.
Could the clouds have touched? If so, two trails have met.
  120   ML:DO P = 1,NMET		!Step through the meeting list.
             IF (NS.LT.MANY) NS = NS + 1!Note another shove sequence.
             LS(NS) = 0			!Details to be determined.
             WRITE (MSG,121)		!Announce, via a heading.
  121         FORMAT (/,5X,"Record Stash Move |Board layout by row|",	!Various details
     1         2X,"Max|d|  Sum|d|   Euclidean   Encoded vs Zero")	!Will be attached.
             NTRAIL = 1			!Every trail starts with its first step.
             TRAIL(1) = MET(2,P)	!This is the blue trail's position that met the red tide..
  122        READ(WRK(2),REC = TRAIL(NTRAIL) + 1) ASTASH	!Obtain details
             IF (ASTASH.PREV.NE.0) THEN	!Had this step arrived from somewhere?
               IF (NTRAIL.GE.LONG) STOP "The trail is too long!"	!Yes.
               NTRAIL = NTRAIL + 1		!Count another step.
               TRAIL(NTRAIL) = ASTASH.PREV	!Finger the preceding step.
               GO TO 122			!And investigate it in turn.
             END IF			!Thus follow the blue trail back to its origin.
  130        DO LOOK = NTRAIL,1,-1	!The end of the blue trail is the position in TARGET, the start position.
               READ(WRK(2),REC = TRAIL(LOOK) + 1) ASTASH	!Grab a position, dodging the header.
               CALL REPORT(TRAIL(LOOK),"Blue",WNAMEF(ASTASH.MOVE),	!Backwards*backwards = forwards.
     1          ASTASH.BRD)						!The board layout is always straightforward...
               IF (LOOK.NE.NTRAIL) THEN		!The start position has no move leading to it.
                 IF (LS(NS).LT.LEN(SHOVE(1))) LS(NS) = LS(NS) + 1	!But count all subsequent ssociated moves.
                 SHOVE(NS)(LS(NS):LS(NS)) = WNAMEF(ASTASH.MOVE)	!Place it.
               END IF				!So much for that move.
             END DO			!On to the next move away from the start position.
  140        HEAD = 0			!Syncopation. Prevent the first position of the red trail from being listed.
             LOOK = MET(1,P)		!It is the same position as the first in the TRAIL, but in the primary stash.
             DO WHILE(LOOK.NE.0)	!The red chain runs back to its starting position, which is the "solution" state..
               READ(WRK(1),REC = LOOK + 1) ASTASH	!Which is in the direction I want to list.
               IF (HEAD.NE.0) THEN			!Except that the moves are one step behind for this list.
                 CALL REPORT(LOOK,"Red",WNAMEZ(HEAD),ASTASH.BRD)	!As this sequence is not being reversed.
                 IF (LS(NS).LT.LEN(SHOVE(1))) LS(NS) = LS(NS) + 1	!This lists the moves in forwards order.
                 SHOVE(NS)(LS(NS):LS(NS)) = WNAMEZ(HEAD)	!But the directions are reversed....
               END IF				!This test avoids listing the "Red" position that is the same as the last "Blue" position.
               HEAD = ASTASH.MOVE		!This is the move that led to this position.
               LOOK = ASTASH.PREV		!From the next position, which will be listed next.
             END DO			!Thus, the listed position was led to by the previous position's move.
  150        DO I = 1,NS - 1		!Perhaps the move sequence has been found already.
               IF (SHOVE(I)(1:LS(I)).EQ.SHOVE(NS)(1:LS(NS))) THEN	!So, compare agains previous shoves.
                 WRITE (MSG,151) I				!It has been seen.
  151            FORMAT (6X,"... same as for sequence ",I0)	!Humm.
                 NS = NS - 1					!Eject the arriviste.
                 GO TO 159					!And carry on.
               END IF				!This shouldn't happen...
             END DO			!On to the next comparison.
             WRITE (MSG,152) LS(NS),SHOVE(NS)(1:LS(NS))	!Show the moves along a line.
  152        FORMAT (I4," moves: ",A)	!Surely plural? One-steps wouldn't be tried?
  159      END DO ML		!Perhaps another pair of snakes have met.
         END DO WW	!Advance W to the other one. M will be swapped correspondingly.

Could there be an end to it all?
         IF (.NOT.ANY(SURGED)) STOP "No progress!"	!Oh dear.
         IF (NMET.LE.0) GO TO 100			!Keep right on to the end of the road...
       END SUBROUTINE PURPLE HAZE	!That was fun!
      END MODULE SLIDESOLVE

      PROGRAM POKE
      USE SLIDESOLVE
      CHARACTER*(19) FNAME		!A base name for some files.
      INTEGER I,R,C			!Some steppers.
      INTEGER MSG,KBD,WRK(2),NDX(2)	!I/O unit numbers.
      COMMON/IODEV/ MSG,KBD,WRK,NDX	!I talk to the trees..
      KBD = 5			!Standard input. (Keyboard)
      MSG = 6			!Standard output.(Display screen)
      WRK = (/10,12/)		!I need two work files,
      NDX = WRK + 1		!Each with its associated index.
      WRITE (FNAME,1) NR,NC	!Now prepare the file name.
    1 FORMAT ("SlideSolveR",I1,"C",I1,".txt")	!Allowing for variation, somewhat.
      WRITE (MSG,2) NR,NC,FNAME			!Announce.
    2 FORMAT ("To play 'slide-square' with ",I0," rows and ",
     1 I0," columns.",/,"An initial layout will be read from file ",
     2 A,/,"The objective is to attain the nice orderly layout"
     3 " as follows:",/)
      FORALL(I = 1:N - 1) ZERO(I) = I	!Regard the final or "solution" state as ZERO.
      ZERO(N) = 0			!The zero sqiuare is at the end, damnit!
      CALL SHOW(NR,NC,ZERO)		!Show the squares in their "solved" arrangement: the "Red" stash.
      OPEN(WRK(1),FILE=FNAME,STATUS="OLD",ACTION="READ")	!For formatted input.
      DO R = 1,NR			!Chug down the rows, reading successive columns across a row..
        READ (WRK(1),*) (TARGET((R - 1)*NC + C), C = 1,NC)	!Into successive storage locations.
      END DO				!Furrytran's storage order is (column,row) for that, alas.
      CLOSE (WRK(1))			!A small input, but much effort follows.
      WRITE (MSG,3)			!Now show the supplied layout.
    3 FORMAT (/,"The starting position:")	!The target, working backwards.
      CALL SHOW(NR,NC,TARGET)		!This will be the starting point for the "Blue" stash.
      IF (ALL(TARGET.EQ.BOARD)) STOP "Huh? They're the same!"	!Surely not.
      WRITE (MSG,4)
    4 FORMAT (/'The plan is to spread a red tide from the "solved" ',
     1 "layout and a blue tide from the specified starting position.",/
     2 "The hope is that these floods will meet at some position,",
     3 " and the blue moves plus the red moves in reverse order,",/
     4 "will be the shortest sequence from the given starting",
     5 " position to the solution.")

      CALL PURPLE HAZE(FNAME(1:14))

      END

The Results

An important feature of the stash file is that its sequential growth makes it easy to keep track of which entries are on the current boundary. Its positions are stored in entry First (IST) to Last (LST) inclusive, and as the new boundary is identified and checked, its accepted positions are placed in the stash following the last entry to become the First:Last span for the next surge. When a candidate new position is checked, it may be that it is already in the stash so it is declared "Already seen" and ignored. This check can be swift, as if the value of INDEX(H) is zero then that hash code has not been seen before and so the position can't be in the stash. As more and more INDEX entries are used, the proportion of zero INDEX values falls, as tracked by the column headed Used%, and different positions may fall upon the same index entry because their hash numbers are the same. Thus the column headed Load% shows the total number of positions stashed divided by the number of index entries available, the value of APRIME. Some index entries will doubtless remain unused even as others are overloaded. It is important that the hash calculation sprays evenly, avoiding clumps.

If an index value is non-zero, then the candidate position's layout must be compared to the layout of each of the positions that have been linked together as having that hash number. Each comparison is a probe of the stash (the disc record must be read) and hopefully, the average number of probes remains a small number, such as one. Perhaps a match will be found early in the chain, but hopefully, most chains are short. Thus the columns headed Max.L and Avg.L report on this. Only after the last linked entry is checked will it be known that the candidate position's layout is not in the stash, and if so, a new entry to hold it is made.

If a position is declared new and added to the primary stash (be it Red or Blue) as a new border element, a secondary search is made of the other stash (respectively, Blue or Red) to seek a match, and the same statistics are presented. No entries are added to the alternate stash: instead a match means that the two tides have met at this position, and so a step sequence is discovered!

The step sequence is shown along with a board layout, followed by various methods of calculating a position's distance from ZERO, the solved state. All are such that Dist(ZERO) = 0, but, they do not show an obvious direction to follow. Some steps along the path to ZERO raise the distance to ZERO.

Specified Problem

To play 'slide-square' with 4 rows and 4 columns.
An initial layout will be read from file SlideSolveR4C4.txt
The objective is to attain the nice orderly layout as follows:

Row|__1__2__3__4
  1|  1  2  3  4
  2|  5  6  7  8
  3|  9 10 11 12
  4| 13 14 15  0

The starting position:
Row|__1__2__3__4
  1| 15 14  1  6
  2|  9 11  4 12
  3|  0 10  7  3
  4| 13  8  5  2

The plan is to spread a red tide from the "solved" layout and a blue tide from the specified starting position.
The hope is that these floods will meet at some position, and the blue moves plus the red moves in reverse order,
will be the shortest sequence from the given starting position to the solution.

Tide  Red
 Preparing a stash in file SlideSolveR4C4.123456789ABCDEF0.dat
 ... with an index in file SlideSolveR4C4.123456789ABCDEF0.ndx
   199999991  zero values for an empty index.
 1 2 3 4 5 6 7 8 9 A B C D E F 0 is the board layout in INTEGER*1
 4030201 8070605 C0B0A09   F0E0D is the board layout in INTEGER*4
        48372615        C0BFAE9D ..interleaved into two INTEGER*4
                        EF5FA0E1 multiplied together in INTEGER*4
                      -278945567 as a decimal integer.
ABS(MOD(-278945567,199999991)) + 2 = 78945578 is the record number for the first index entry.

Tide Blue
 Preparing a stash in file SlideSolveR4C4.FE169B4C0A73D852.dat
 ... with an index in file SlideSolveR4C4.FE169B4C0A73D852.ndx
   199999991  zero values for an empty index.
 F E 1 6 9 B 4 C 0 A 7 3 D 8 5 2 is the board layout in INTEGER*1
 6010E0F C040B09 3070A00 205080D is the board layout in INTEGER*4
        6C14EBF9        3275A80D ..interleaved into two INTEGER*4
                        B2B863A5 multiplied together in INTEGER*4
                     -1296538715 as a decimal integer.
ABS(MOD(-1296538715,199999991)) + 2 = 96538771 is the record number for the first index entry.

       |   Tidewrack Boundary Positions  |      Positions     |       Primary Probes         Index Use    |     Secondary Probes   | Memory of Time Passed
Surge  |      First       Last      Count|    Checked Deja vu%|       Made Max.L  Avg.L|   Used%     Load%|       Made Max.L  Avg.L|      CPU        Clock
 1  Red|          1          1          1|          2     0.00|          0     0  0.000|   0.000     0.000|          0     0  0.000|    0.0secs    0.5secs  2.94%  1:07:03.093am.
 1 Blue|          1          1          1|          3     0.00|          0     0  0.000|   0.000     0.000|          0     0  0.000|    0.0secs    0.5secs  3.22%  1:07:03.578am.
 2  Red|          2          3          2|          4     0.00|          0     0  0.000|   0.000     0.000|          0     0  0.000|    0.0secs    0.0secs  0.00%  1:07:03.593am.
 2 Blue|          2          4          3|          6     0.00|          0     0  0.000|   0.000     0.000|          0     0  0.000|    0.1secs    0.5secs 11.75%  1:07:04.125am.
 3  Red|          4          7          4|         10     0.00|          0     0  0.000|   0.000     0.000|          0     0  0.000|    0.1secs    0.7secs 11.63%  1:07:04.812am.
 3 Blue|          5         10          6|         14     0.00|          0     0  0.000|   0.000     0.000|          0     0  0.000|    0.0secs    0.0secs         1:07:04.812am.
 4  Red|          8         17         10|         24     0.00|          0     0  0.000|   0.000     0.000|          0     0  0.000|    0.0secs    0.0secs  0.00%  1:07:04.875am.
 4 Blue|         11         24         14|         32     0.00|          0     0  0.000|   0.000     0.000|          0     0  0.000|    0.0secs    0.1secs  0.00%  1:07:04.953am.
 5  Red|         18         41         24|         54     0.00|          0     0  0.000|   0.000     0.000|          0     0  0.000|    0.0secs    0.0secs  0.00%  1:07:05.015am.
 5 Blue|         25         56         32|         66     0.00|          0     0  0.000|   0.000     0.000|          0     0  0.000|    0.0secs    0.0secs 50.40%  1:07:05.046am.
 6  Red|         42         95         54|        108     0.93|          1     1  1.000|   0.000     0.000|          0     0  0.000|    0.0secs    0.0secs 33.24%  1:07:05.093am.
 6 Blue|         57        122         66|        136     1.47|          2     1  1.000|   0.000     0.000|          0     0  0.000|    0.0secs    0.1secs  0.00%  1:07:05.171am.
 7  Red|         96        202        107|        215     1.40|          3     1  1.000|   0.000     0.000|          0     0  0.000|    0.0secs    0.1secs  0.00%  1:07:05.265am.
 7 Blue|        123        256        134|        285     1.75|          5     1  1.000|   0.000     0.000|          0     0  0.000|    0.0secs    0.1secs  0.00%  1:07:05.375am.
 8  Red|        203        414        212|        456     2.19|         10     1  1.000|   0.000     0.000|          0     0  0.000|    0.0secs    0.2secs  8.36%  1:07:05.578am.
 8 Blue|        257        536        280|        601     2.66|         16     1  1.000|   0.001     0.001|          0     0  0.000|    0.0secs    0.2secs 20.03%  1:07:05.812am.
 9  Red|        415        860        446|        974     2.87|         28     1  1.000|   0.001     0.001|          0     0  0.000|    0.2secs    0.5secs 37.50%  1:07:06.312am.
 9 Blue|        537       1121        585|       1254     3.19|         41     1  1.000|   0.001     0.001|          0     0  0.000|    0.2secs    0.5secs 34.38%  1:07:06.812am.
10  Red|        861       1806        946|       2032     4.13|         88     1  1.000|   0.002     0.002|          0     0  0.000|    0.3secs    0.9secs 37.28%  1:07:07.734am.
10 Blue|       1122       2335       1214|       2578     4.50|        117     1  1.000|   0.002     0.002|          0     0  0.000|    0.3secs    1.1secs 25.00%  1:07:08.859am.
11  Red|       1807       3754       1948|       4124     4.51|        189     1  1.000|   0.004     0.004|          0     0  0.000|    0.5secs    1.8secs 29.66%  1:07:10.703am.
11 Blue|       2336       4797       2462|       5188     4.66|        249     1  1.000|   0.005     0.005|          0     0  0.000|    0.6secs    2.2secs 27.54%  1:07:12.859am.
12  Red|       3755       7692       3938|       8244     5.29|        440     1  1.000|   0.008     0.008|          0     0  0.000|    1.0secs    3.6secs 28.51%  1:07:16.421am.
12 Blue|       4798       9743       4946|      10442     5.56|        588     1  1.000|   0.010     0.010|          1     1  1.000|    1.6secs    4.2secs 38.20%  1:07:20.593am.
13  Red|       7693      15500       7808|      16470     5.62|        943     1  1.000|   0.016     0.016|          0     0  0.000|    2.2secs    6.5secs 33.65%  1:07:27.093am.
13 Blue|       9744      19604       9861|      20858     6.03|       1285     2  1.001|   0.020     0.020|          4     1  1.000|    2.8secs    7.5secs 37.00%  1:07:34.625am.
14  Red|      15501      31044      15544|      32950     6.46|       2175     2  1.000|   0.031     0.031|          5     1  1.000|    3.8secs   11.2secs 34.12%  1:07:45.843am.
14 Blue|      19605      39204      19600|      41448     6.66|       2813     2  1.001|   0.039     0.039|         14     1  1.000|    4.6secs   12.7secs 36.19%  1:07:58.578am.
15  Red|      31045      61865      30821|      65311     6.84|       4611     2  1.001|   0.061     0.061|         23     1  1.000|    6.8secs   17.2secs 39.62%  1:08:15.734am.
15 Blue|      39205      77892      38688|      81703     6.87|       5803     2  1.000|   0.077     0.077|         47     1  1.000|    7.2secs   18.2secs 39.64%  1:08:33.906am.
16  Red|      61866     122707      60842|     128412     7.33|       9742     2  1.002|   0.121     0.121|         77     2  1.013|   11.9secs   23.2secs 51.21%  1:08:57.093am.
16 Blue|      77893     153978      76086|     160446     7.49|      12421     2  1.001|   0.151     0.151|        172     1  1.000|   13.6secs   23.2secs 58.57%  1:09:20.250am.
17  Red|     122708     241707     119000|     250818     7.56|      20063     3  1.002|   0.236     0.237|        303     1  1.000|   20.5secs   30.5secs 67.33%  1:09:50.765am.
17 Blue|     153979     302413     148435|     312766     7.89|      26220     2  1.002|   0.294     0.295|        678     2  1.001|   24.3secs   32.3secs 75.17%  1:10:23.109am.
18  Red|     241708     473551     231844|     487982     8.33|      43582     3  1.003|   0.458     0.460|       1229     2  1.002|   38.0secs   46.2secs 82.36%  1:11:09.265am.
18 Blue|     302414     590511     288098|     606919     8.56|      56018     3  1.003|   0.570     0.573|       2440     2  1.004|   47.5secs   59.4secs 80.02%  1:12:08.687am.
19  Red|     473552     920893     447342|     942552     8.79|      93152     3  1.005|   0.883     0.890|       4672     2  1.005|   73.1secs   91.3secs 80.07%  1:13:40.046am.
19 Blue|     590512    1145481     554970|    1168265     9.04|     120435     3  1.006|   1.093     1.104|       8999     3  1.009|   90.6secs    1.9mins 81.12%  1:15:31.765am.
20  Red|     920894    1780637     859744|    1809752     9.52|     202081     3  1.007|   1.687     1.709|      17194     2  1.008|    2.4mins    3.0mins 79.31%  1:18:30.265am.
20 Blue|    1145482    2208109    1062628|    2235235     9.77|     262047     3  1.008|   2.080     2.112|      33025     3  1.014|    3.0mins    3.7mins 79.82%  1:22:14.375am.
21  Red|    1780638    3418020    1637383|    3444691    10.06|     451483     4  1.014|   3.183     3.258|      62538     3  1.015|    4.5mins    5.7mins 79.80%  1:27:56.468am.
21 Blue|    2208110    4224923    2016814|    4238343    10.33|     588823     4  1.017|   3.905     4.013|     118609     5  1.024|    5.7mins    7.1mins 80.08%  1:35:03.140am.
22  Red|    3418021    6516290    3098270|    6506982    10.83|    1022051     4  1.023|   5.926     6.159|     219923     4  1.025|    8.7mins   10.7mins 81.12%  1:45:44.328am.
22 Blue|    4224924    8025605    3800682|    7979315    11.11|    1353159     5  1.029|   7.218     7.559|     414055     4  1.039|   10.9mins   13.8mins 79.04%  1:59:31.875am.
23  Red|    6516291   12318701    5802411|   12178635    11.45|    2470087     6  1.048|  10.780    11.551|     765853     5  1.046|   17.3mins   24.1mins 71.78%  2:23:35.828am.
23 Blue|    8025606   15118814    7093209|   14877107    11.76|    3307729     5  1.058|  13.003    14.123|    1401940     6  1.071|   22.4mins   35.3mins 63.46%  2:58:53.375am.
24  Red|   12318702   23102481   10783780|   22603192    12.29|    6014769     6  1.083|  19.074    21.464|    2526552     6  1.084|   36.7mins   65.6mins 55.95%  4:04:29.375am.
24 Blue|   15118815   28246178   13127364|   27507742    12.56|    8148893     7  1.105|  22.682    26.150|    4539588     7  1.124|   49.4mins   96.5mins 51.22%  5:40:59.265am.
25  Red|   23102482   42928799   19826318|   41532426    12.98|   15508438     9  1.166|  32.086    39.535|    8144188     7  1.151|   84.5mins    3.0hrs! 47.06%  8:40:25.296am.
25 Blue|   28246179   52299632   24053454|   50352435    13.30|   21007872     9  1.207|  37.354    47.979|   13961187     8  1.232|    2.0hrs!    4.4hrs! 44.40%  1:07:12.546pm.
26  Red|   42928800   79070945   36142146|   75611538    13.85|   38688179    10  1.300|  50.548    72.103|   24060624     9  1.278|    3.5hrs!    8.3hrs! 42.77%  9:23:03.250pm.
26 Blue|   52299633   95957208   43657576|   91305518    14.15|   51817830    14  1.379|  57.098    87.170|   39429635    10  1.424|    5.3hrs!   12.5hrs! 42.37%  9:53:57.890am.

     Record Stash Move |Board layout by row|  Max|d|  Sum|d|   Euclidean   Encoded vs Zero
          1  Blue      |FE16/9B4C/0A73/D852|      14      86      27.055    19442940853367
          2  Blue    L |FE16/9B4C/A073/D852|      14      88      27.423    19442940844007
          5  Blue    L |FE16/9B4C/A703/D852|      14      88      27.677    19442940842087
         11  Blue    L |FE16/9B4C/A730/D852|      14      88      27.785    19442940841679
         25  Blue    D |FE16/9B40/A73C/D852|      14      80      26.000    19442940922295
         58  Blue    R |FE16/9B04/A73C/D852|      14      80      25.846    19442943220535
        126  Blue    U |FE16/9B34/A70C/D852|      14      80      26.306    19442940271895
        265  Blue    R |FE16/9B34/A07C/D852|      14      80      26.038    19442940274415
        554  Blue    D |FE16/9034/AB7C/D852|      14      72      24.290    19442951159375
       1156  Blue    D |F016/9E34/AB7C/D852|      14      64      21.863    19530129450575
       2409  Blue    R |0F16/9E34/AB7C/D852|      13      62      21.166    20837803818575
       4949  Blue    U |9F16/0E34/AB7C/D852|      13      70      22.804    11597104535375
      10052  Blue    L |9F16/E034/AB7C/D852|      13      72      23.409    11597064618575
      20229  Blue    D |9016/EF34/AB7C/D852|      10      64      20.688    11684242909775
      40459  Blue    L |9106/EF34/AB7C/D852|      10      64      20.736    10544698103375
      80368  Blue    U |9136/EF04/AB7C/D852|      10      64      21.307    10469454209615
     158888  Blue    U |9136/EF74/AB0C/D852|      11      64      22.583    10469452026935
     312029  Blue    U |9136/EF74/AB5C/D802|      15      64      23.452    10469452026423
     609335  Blue    R |9136/EF74/AB5C/D082|      14      64      23.108    10469452026425
    1182001  Blue    D |9136/EF74/A05C/DB82|      10      62      21.119    10469452028615
    2278389  Blue    D |9136/E074/AF5C/DB82|       9      54      18.055    10469455657415
    4359103  Blue    R |9136/0E74/AF5C/DB82|       8      52      17.263    10469531862215
    8279825  Blue    D |0136/9E74/AF5C/DB82|       8      44      15.033    19623012937415
   15596324  Blue    L |1036/9E74/AF5C/DB82|       8      44      15.100     1228393494215
   29135380  Blue    L |1306/9E74/AF5C/DB82|       8      46      15.297      169799958215
   53940860  Blue    L |1360/9E74/AF5C/DB82|       8      48      15.684      111840764615
   98956855  Blue    U |1364/9E70/AF5C/DB82|       8      48      16.673      106528120775
   47181586   Red    R |1364/9E07/AF5C/DB82|       8      48      16.248      106530419015
   25400927   Red    U |1364/9E57/AF0C/DB82|      11      48      17.436      106527470375
   13548279   Red    U |1364/9E57/AF8C/DB02|      15      48      19.183      106527469863
    7168799   Red    L |1364/9E57/AF8C/DB20|      13      44      17.550      106527469862
    3761169   Red    D |1364/9E57/AF80/DB2C|      13      68      24.413      106527469916
    1959965   Red    R |1364/9E57/AF08/DB2C|      13      68      24.083      106527470324
    1013648   Red    U |1364/9E57/AF28/DB0C|      15      68      24.413      106527469693
     521247   Red    R |1364/9E57/AF28/D0BC|      14      68      23.958      106527469696
     266034   Red    D |1364/9E57/A028/DFBC|      12      60      21.307      106527470416
     135063   Red    D |1364/9057/AE28/DFBC|      12      52      18.493      106534727296
      68120   Red    L |1364/9507/AE28/DFBC|      12      52      18.762      106504971136
      34188   Red    U |1364/9527/AE08/DFBC|      12      52      19.183      106501659736
      17049   Red    U |1364/9527/AEB8/DF0C|      15      52      21.354      106501659249
       8443   Red    R |1364/9527/AEB8/D0FC|      14      50      20.640      106501659251
       4119   Red    D |1364/9527/A0B8/DEFC|      12      42      17.720      106501660689
       1986   Red    R |1364/9527/0AB8/DEFC|      12      40      17.146      106501687329
        950   Red    D |1364/0527/9AB8/DEFC|      12      32      14.900      106781074689
        458   Red    L |1364/5027/9AB8/DEFC|      12      32      15.232      106414565889
        222   Red    L |1364/5207/9AB8/DEFC|      12      32      15.362      106381543809
        104   Red    D |1304/5267/9AB8/DEFC|      12      26      13.711      168648485889
         46   Red    R |1034/5267/9AB8/DEFC|      12      24      13.491     1227242021889
         20   Red    U |1234/5067/9AB8/DEFC|      12      24      14.071          36293889
          9   Red    L |1234/5607/9AB8/DEFC|      12      24      14.491           3271809
          4   Red    L |1234/5670/9AB8/DEFC|      12      24      14.967            328449
          2   Red    U |1234/5678/9AB0/DEFC|      12      24      16.971               105
          1   Red    U |1234/5678/9ABC/DEF0|       0       0       0.000                 0
  52 moves: LLLDRURDDRULDLUUURDDRDLLLURUULDRURDDLUURDRDLLDRULLUU

     Record Stash Move |Board layout by row|  Max|d|  Sum|d|   Euclidean   Encoded vs Zero
          1  Blue      |FE16/9B4C/0A73/D852|      14      86      27.055    19442940853367
          2  Blue    L |FE16/9B4C/A073/D852|      14      88      27.423    19442940844007
          5  Blue    L |FE16/9B4C/A703/D852|      14      88      27.677    19442940842087
         11  Blue    L |FE16/9B4C/A730/D852|      14      88      27.785    19442940841679
         25  Blue    D |FE16/9B40/A73C/D852|      14      80      26.000    19442940922295
         58  Blue    R |FE16/9B04/A73C/D852|      14      80      25.846    19442943220535
        126  Blue    U |FE16/9B34/A70C/D852|      14      80      26.306    19442940271895
        266  Blue    U |FE16/9B34/A75C/D802|      15      80      27.055    19442940271383
        558  Blue    R |FE16/9B34/A75C/D082|      14      80      26.758    19442940271385
       1163  Blue    D |FE16/9B34/A05C/D782|      14      80      25.690    19442940274293
       2420  Blue    D |FE16/9034/AB5C/D782|      14      72      23.917    19442951159253
       4967  Blue    D |F016/9E34/AB5C/D782|      14      64      21.448    19530129450453
      10090  Blue    R |0F16/9E34/AB5C/D782|      13      62      20.736    20837803818453
      20306  Blue    U |9F16/0E34/AB5C/D782|      13      70      22.405    11597104535253
      40605  Blue    L |9F16/E034/AB5C/D782|      13      72      23.022    11597064618453
      80644  Blue    D |9016/EF34/AB5C/D782|       9      64      20.248    11684242909653
     159412  Blue    L |9106/EF34/AB5C/D782|       9      64      20.298    10544698103253
     313039  Blue    U |9136/EF04/AB5C/D782|       9      64      20.881    10469454209493
     611272  Blue    U |9136/EF54/AB0C/D782|      11      64      21.817    10469451664053
    1185720  Blue    U |9136/EF54/AB8C/D702|      15      64      23.238    10469451663663
    2285440  Blue    L |9136/EF54/AB8C/D720|      13      60      21.909    10469451663662
    4372401  Blue    D |9136/EF54/AB80/D72C|      13      84      27.713    10469451663716
    8304687  Blue    R |9136/EF54/AB08/D72C|      13      84      27.423    10469451664028
   15642421  Blue    R |9136/EF54/A0B8/D72C|      13      82      27.019    10469451665948
   29220003  Blue    D |9136/E054/AFB8/D72C|      13      74      24.698    10469455294748
   54094941  Blue    R |9136/0E54/AFB8/D72C|      13      72      24.125    10469531499548
   99233939  Blue    D |0136/9E54/AFB8/D72C|      13      64      22.583    19623012574748
   53847416   Red    L |1036/9E54/AFB8/D72C|      13      64      22.627     1228393131548
   29052034   Red    L |1306/9E54/AFB8/D72C|      13      66      22.760      169799595548
   15525093   Red    L |1360/9E54/AFB8/D72C|      13      68      23.022      111840401948
    8231956   Red    U |1364/9E50/AFB8/D72C|      13      68      23.707      106527758108
    4326107   Red    U |1364/9E58/AFB0/D72C|      13      68      25.020      106527510356
    2258614   Red    R |1364/9E58/AF0B/D72C|      13      68      24.576      106527510668
    1169712   Red    U |1364/9E58/AF2B/D70C|      15      68      24.900      106527510037
     602597   Red    R |1364/9E58/AF2B/D07C|      14      68      24.617      106527510040
     308014   Red    D |1364/9E58/A02B/DF7C|      12      60      22.045      106527510760
     156652   Red    D |1364/9058/AE2B/DF7C|      12      52      19.339      106534767640
      79080   Red    L |1364/9508/AE2B/DF7C|      12      52      19.596      106505011480
      39747   Red    U |1364/9528/AE0B/DF7C|      12      52      20.000      106501700080
      19843   Red    U |1364/9528/AE7B/DF0C|      15      52      21.354      106501699449
       9866   Red    R |1364/9528/AE7B/D0FC|      14      50      20.640      106501699451
       4832   Red    D |1364/9528/A07B/DEFC|      12      42      17.720      106501700889
       2335   Red    R |1364/9528/0A7B/DEFC|      12      40      17.146      106501727529
       1114   Red    D |1364/0528/9A7B/DEFC|      12      32      14.900      106781114889
        534   Red    L |1364/5028/9A7B/DEFC|      12      32      15.232      106414606089
        259   Red    L |1364/5208/9A7B/DEFC|      12      32      15.362      106381584009
        124   Red    D |1304/5268/9A7B/DEFC|      12      26      13.711      168648526089
         56   Red    R |1034/5268/9A7B/DEFC|      12      24      13.491     1227242062089
         24   Red    U |1234/5068/9A7B/DEFC|      12      24      14.071          36334089
         10   Red    L |1234/5608/9A7B/DEFC|      12      24      14.491           3312009
          5   Red    U |1234/5678/9A0B/DEFC|      12      24      16.310               609
          2   Red    L |1234/5678/9AB0/DEFC|      12      24      16.971               105
          1   Red    U |1234/5678/9ABC/DEF0|       0       0       0.000                 0
  52 moves: LLLDRUURDDDRULDLUUULDRRDRDLLLUURURDDLUURDRDLLDRULULU

The two paths deviate on the seventh move, from entry 126 Blue either R to 265 Blue or U to 266 Blue. The Euclidean and Encoded distances to ZERO conflict: down and up for the first choice, up and down for the second.

Another Example

Taking the first example from 15_puzzle_solver/20_Random shows that with the red tide expansion in hand, only the new blue tide is poured into the maze:

To play 'slide-square' with 4 rows and 4 columns.
An initial layout will be read from file SlideSolveR4C4.txt
The objective is to attain the nice orderly layout as follows:

Row|__1__2__3__4
  1|  1  2  3  4
  2|  5  6  7  8
  3|  9 10 11 12
  4| 13 14 15  0

The starting position:
Row|__1__2__3__4
  1| 13 10 11  6
  2|  5  3  1  4
  3|  8  0 12  2
  4| 14  7  9 15

The plan is to spread a red tide from the "solved" layout and a blue tide from the specified starting position.
The hope is that these floods will meet at some position, and the blue moves plus the red moves in reverse order,
will be the shortest sequence from the given starting position to the solution.

Tide  Red
 Restarting from file SlideSolveR4C4.123456789ABCDEF0.dat
 Stashed 144206568. At surge 26 with the boundary stashed in elements 79070946 to 144206568
 Its index uses 101095844 of 199999991 entries.

Tide Blue
 Preparing a stash in file SlideSolveR4C4.DAB6531480C2E79F.dat
 ... with an index in file SlideSolveR4C4.DAB6531480C2E79F.ndx
   199999991  zero values for an empty index.
 D A B 6 5 3 1 4 8 0 C 2 E 7 9 F is the board layout in INTEGER*1
 60B0A0D 4010305 20C0008 F09070E is the board layout in INTEGER*4
        64B1A3D5        2FC9078E ..interleaved into two INTEGER*4
                        7340B326 multiplied together in INTEGER*4
                      1933620006 as a decimal integer.
ABS(MOD(1933620006,199999991)) + 2 = 133620089 is the record number for the first index entry.

       |   Tidewrack Boundary Positions  |      Positions     |       Primary Probes         Index Use    |     Secondary Probes   | Memory of Time Passed
Surge  |      First       Last      Count|    Checked Deja vu%|       Made Max.L  Avg.L|   Used%     Load%|       Made Max.L  Avg.L|      CPU        Clock
 1 Blue|          1          1          1|          4     0.00|          0     0  0.000|   0.000     0.000|          2     1  1.000|    0.0secs    0.0secs  0.00%  9:27:19.468am.
 2 Blue|          2          5          4|         10     0.00|          0     0  0.000|   0.000     0.000|          4     2  1.500|    0.0secs    0.5secs  3.33%  9:27:19.937am.
 3 Blue|          6         15         10|         20     0.00|          0     0  0.000|   0.000     0.000|          5     2  1.400|    0.0secs    0.4secs  0.00%  9:27:20.296am.
 4 Blue|         16         35         20|         38     0.00|          0     0  0.000|   0.000     0.000|         16     3  1.250|    0.0secs    0.3secs 11.75%  9:27:20.562am.
 5 Blue|         36         73         38|         80     0.00|          0     0  0.000|   0.000     0.000|         39     4  1.385|    0.0secs    0.1secs  0.00%  9:27:20.625am.
 6 Blue|         74        153         80|        178     2.25|          4     1  1.000|   0.000     0.000|         87     4  1.379|    0.0secs    0.1secs  0.00%  9:27:20.750am.
 7 Blue|        154        327        174|        380     2.11|          8     1  1.000|   0.000     0.000|        186     5  1.500|    0.2secs    0.3secs 52.79%  9:27:21.046am.
 8 Blue|        328        699        372|        790     3.54|         28     1  1.000|   0.001     0.001|        386     5  1.448|    0.2secs    0.4secs 53.51%  9:27:21.484am.
 9 Blue|        700       1461        762|       1590     3.14|         50     1  1.000|   0.002     0.002|        738     6  1.407|    0.3secs    0.9secs 37.50%  9:27:22.375am.
10 Blue|       1462       3001       1540|       3234     5.01|        168     1  1.000|   0.003     0.003|       1542     6  1.396|    0.6secs    2.0secs 29.60%  9:27:24.328am.
11 Blue|       3002       6073       3072|       6512     4.85|        318     1  1.000|   0.006     0.006|       3032     5  1.398|    1.2secs    3.9secs 30.77%  9:27:28.187am.
12 Blue|       6074      12269       6196|      13182     6.27|        841     2  1.001|   0.012     0.012|       6007     6  1.406|    2.5secs    7.5secs 33.96%  9:27:35.687am.
13 Blue|      12270      24625      12356|      26134     6.19|       1648     2  1.001|   0.025     0.025|      12261     6  1.410|    5.0secs   15.6secs 32.23%  9:27:51.296am.
14 Blue|      24626      49141      24516|      51646     6.71|       3581     2  1.000|   0.049     0.049|      23554     6  1.395|    9.2secs   24.8secs 37.14%  9:28:16.078am.
15 Blue|      49142      97320      48179|     101393     6.94|       7301     2  1.000|   0.096     0.096|      46888     7  1.411|   15.8secs   41.9secs 37.71%  9:28:58.031am.
16 Blue|      97321     191676      94356|     198950     7.80|      16489     2  1.002|   0.187     0.188|      89521     7  1.399|   27.3secs   62.8secs 43.45%  9:30:00.781am.
17 Blue|     191677     375108     183432|     386686     8.11|      33604     2  1.003|   0.363     0.365|     177033     8  1.418|   52.7secs    1.9mins 45.48%  9:31:56.734am.
18 Blue|     375109     730438     355330|     748406     8.84|      74407     3  1.005|   0.700     0.706|     333730     7  1.405|   96.8secs    3.7mins 43.38%  9:35:39.812am.
19 Blue|     730439    1412688     682250|    1434890     9.27|     154921     4  1.007|   1.340     1.357|     649155     8  1.416|    3.2mins    7.1mins 44.60%  9:42:44.031am.

     Record Stash Move |Board layout by row|  Max|d|  Sum|d|   Euclidean   Encoded vs Zero
          1  Blue      |DAB6/5314/80C2/E79F|      15      94      29.155    16535302211892
          4  Blue    R |DAB6/5314/08C2/E79F|      15      94      28.879    16535302234212
         12  Blue    D |DAB6/0314/58C2/E79F|      15      94      28.178    16535581621572
         30  Blue    L |DAB6/3014/58C2/E79F|      15      94      28.284    16535251400772
         62  Blue    L |DAB6/3104/58C2/E79F|      15      94      28.320    16535218378692
        125  Blue    D |DA06/31B4/58C2/E79F|      15      86      26.721    16560125373252
        266  Blue    R |D0A6/31B4/58C2/E79F|      15      84      26.344    16971108746052
        568  Blue    R |0DA6/31B4/58C2/E79F|      15      82      25.846    20719775267652
       1203  Blue    U |3DA6/01B4/58C2/E79F|      15      86      26.306     3626483421252
       2480  Blue    L |3DA6/10B4/58C2/E79F|      15      86      26.344     3626080624452
       5044  Blue    D |30A6/1DB4/58C2/E79F|      15      78      24.290     3887608240452
      10136  Blue    L |3A06/1DB4/58C2/E79F|      15      80      24.698     3395673597252
      20466  Blue    L |3A60/1DB4/58C2/E79F|      15      82      24.940     3343462422852
      40817  Blue    U |3A64/1DB0/58C2/E79F|      15      82      25.573     3338668697412
      81229  Blue    U |3A64/1DB2/58C0/E79F|      15      82      25.884     3338668369068
     160022  Blue    R |3A64/1DB2/580C/E79F|      15      80      25.417     3338668369380
     314204  Blue    R |3A64/1DB2/508C/E79F|      15      80      25.100     3338668372500
     612120  Blue    D |3A64/10B2/5D8C/E79F|      15      72      22.935     3338679257460
    1187839  Blue    D |3064/1AB2/5D8C/E79F|      15      64      21.119     3861730860660
    2285373  Blue    R |0364/1AB2/5D8C/E79F|      15      62      20.976    19815358150260
   53888966   Red    U |1364/0AB2/5D8C/E79F|      15      62      21.166      106797401460
   29074972   Red    U |1364/5AB2/0D8C/E79F|      15      62      22.091      106394277060
   15537643   Red    L |1364/5AB2/D08C/E79F|      15      64      22.672      106394263380
    8238754   Red    U |1364/5AB2/D78C/E09F|      15      64      23.875      106394258914
    4329748   Red    L |1364/5AB2/D78C/E90F|      15      64      24.249      106394258911
    2260520   Red    L |1364/5AB2/D78C/E9F0|       6      34      11.747      106394258910
    1170710   Red    D |1364/5AB2/D780/E9FC|      12      58      20.640      106394258989
     603117   Red    R |1364/5AB2/D708/E9FC|      12      58      20.248      106394259493
     308288   Red    D |1364/5A02/D7B8/E9FC|      12      50      17.944      106396078573
     156795   Red    L |1364/5A20/D7B8/E9FC|      12      50      18.055      106393135213
      79148   Red    U |1364/5A28/D7B0/E9FC|      12      50      19.748      106392847909
      39781   Red    R |1364/5A28/D70B/E9FC|      12      50      19.183      106392848317
      19862   Red    R |1364/5A28/D07B/E9FC|      12      50      18.815      106392852037
       9877   Red    U |1364/5A28/D97B/E0FC|      14      50      20.640      106392848411
       4838   Red    R |1364/5A28/D97B/0EFC|      13      48      19.950      106392848421
       2337   Red    D |1364/5A28/097B/DEFC|      12      40      17.146      106392863529
       1115   Red    L |1364/5A28/907B/DEFC|      12      40      17.664      106392836889
        534   Red    D |1364/5028/9A7B/DEFC|      12      32      15.232      106414606089
        259   Red    L |1364/5208/9A7B/DEFC|      12      32      15.362      106381584009
        124   Red    D |1304/5268/9A7B/DEFC|      12      26      13.711      168648526089
         56   Red    R |1034/5268/9A7B/DEFC|      12      24      13.491     1227242062089
         24   Red    U |1234/5068/9A7B/DEFC|      12      24      14.071          36334089
         10   Red    L |1234/5608/9A7B/DEFC|      12      24      14.491           3312009
          5   Red    U |1234/5678/9A0B/DEFC|      12      24      16.310               609
          2   Red    L |1234/5678/9AB0/DEFC|      12      24      16.971               105
          1   Red    U |1234/5678/9ABC/DEF0|       0       0       0.000                 0
  45 moves: RDLLDRRULDLLUURRDDRUULULLDRDLURRURDLDLDRULULU

     Record Stash Move |Board layout by row|  Max|d|  Sum|d|   Euclidean   Encoded vs Zero
          1  Blue      |DAB6/5314/80C2/E79F|      15      94      29.155    16535302211892
          4  Blue    R |DAB6/5314/08C2/E79F|      15      94      28.879    16535302234212
         12  Blue    D |DAB6/0314/58C2/E79F|      15      94      28.178    16535581621572
         30  Blue    L |DAB6/3014/58C2/E79F|      15      94      28.284    16535251400772
         62  Blue    L |DAB6/3104/58C2/E79F|      15      94      28.320    16535218378692
        125  Blue    D |DA06/31B4/58C2/E79F|      15      86      26.721    16560125373252
        266  Blue    R |D0A6/31B4/58C2/E79F|      15      84      26.344    16971108746052
        568  Blue    R |0DA6/31B4/58C2/E79F|      15      82      25.846    20719775267652
       1203  Blue    U |3DA6/01B4/58C2/E79F|      15      86      26.306     3626483421252
       2480  Blue    L |3DA6/10B4/58C2/E79F|      15      86      26.344     3626080624452
       5044  Blue    D |30A6/1DB4/58C2/E79F|      15      78      24.290     3887608240452
      10136  Blue    L |3A06/1DB4/58C2/E79F|      15      80      24.698     3395673597252
      20466  Blue    L |3A60/1DB4/58C2/E79F|      15      82      24.940     3343462422852
      40817  Blue    U |3A64/1DB0/58C2/E79F|      15      82      25.573     3338668697412
      81229  Blue    U |3A64/1DB2/58C0/E79F|      15      82      25.884     3338668369068
     160022  Blue    R |3A64/1DB2/580C/E79F|      15      80      25.417     3338668369380
     314204  Blue    R |3A64/1DB2/508C/E79F|      15      80      25.100     3338668372500
     612122  Blue    U |3A64/1DB2/578C/E09F|      15      80      26.192     3338668368034
    1187843  Blue    L |3A64/1DB2/578C/E90F|      15      80      26.533     3338668368031
    2285379  Blue    L |3A64/1DB2/578C/E9F0|       8      50      15.937     3338668368030
   53890406   Red    D |3A64/1DB2/5780/E9FC|      12      74      23.281     3338668368109
   29075781   Red    R |3A64/1DB2/5708/E9FC|      12      74      22.935     3338668368613
   15538085   Red    D |3A64/1D02/57B8/E9FC|      12      66      20.928     3338669819773
    8239002   Red    L |3A64/1D20/57B8/E9FC|      12      66      21.024     3338666876413
    4329887   Red    U |3A64/1D28/57B0/E9FC|      12      66      22.494     3338666634469
    2260599   Red    R |3A64/1D28/570B/E9FC|      12      66      22.000     3338666634877
    1170753   Red    R |3A64/1D28/507B/E9FC|      12      66      21.679     3338666638597
     603141   Red    D |3A64/1028/5D7B/E9FC|      12      58      19.131     3338677523557
     308302   Red    D |3064/1A28/5D7B/E9FC|      12      50      16.912     3861729126757
     156805   Red    R |0364/1A28/5D7B/E9FC|      12      48      16.733    19815356416357
      79152   Red    U |1364/0A28/5D7B/E9FC|      12      48      16.971      106795667557
      39783   Red    U |1364/5A28/0D7B/E9FC|      12      48      18.111      106392865717
      19862   Red    L |1364/5A28/D07B/E9FC|      12      50      18.815      106392852037
       9877   Red    U |1364/5A28/D97B/E0FC|      14      50      20.640      106392848411
       4838   Red    R |1364/5A28/D97B/0EFC|      13      48      19.950      106392848421
       2337   Red    D |1364/5A28/097B/DEFC|      12      40      17.146      106392863529
       1115   Red    L |1364/5A28/907B/DEFC|      12      40      17.664      106392836889
        534   Red    D |1364/5028/9A7B/DEFC|      12      32      15.232      106414606089
        259   Red    L |1364/5208/9A7B/DEFC|      12      32      15.362      106381584009
        124   Red    D |1304/5268/9A7B/DEFC|      12      26      13.711      168648526089
         56   Red    R |1034/5268/9A7B/DEFC|      12      24      13.491     1227242062089
         24   Red    U |1234/5068/9A7B/DEFC|      12      24      14.071          36334089
         10   Red    L |1234/5608/9A7B/DEFC|      12      24      14.491           3312009
          5   Red    U |1234/5678/9A0B/DEFC|      12      24      16.310               609
          2   Red    L |1234/5678/9AB0/DEFC|      12      24      16.971               105
          1   Red    U |1234/5678/9ABC/DEF0|       0       0       0.000                 0
  45 moves: RDLLDRRULDLLUURRULLDRDLURRDDRUULURDLDLDRULULU

     Record Stash Move |Board layout by row|  Max|d|  Sum|d|   Euclidean   Encoded vs Zero
          1  Blue      |DAB6/5314/80C2/E79F|      15      94      29.155    16535302211892
          4  Blue    R |DAB6/5314/08C2/E79F|      15      94      28.879    16535302234212
         12  Blue    D |DAB6/0314/58C2/E79F|      15      94      28.178    16535581621572
         31  Blue    D |0AB6/D314/58C2/E79F|      15      86      26.268    20458524891972
         65  Blue    L |A0B6/D314/58C2/E79F|      15      88      26.646    13048370139972
        133  Blue    U |A3B6/D014/58C2/E79F|      15      90      27.092    11995513736772
        281  Blue    L |A3B6/D104/58C2/E79F|      15      90      27.129    11995480714692
        599  Blue    D |A306/D1B4/58C2/E79F|      15      82      25.456    12026654646852
       1266  Blue    R |A036/D1B4/58C2/E79F|      15      80      25.338    13004296912452
       2610  Blue    U |A136/D0B4/58C2/E79F|      15      80      25.495    11777091184452
       5305  Blue    R |A136/0DB4/58C2/E79F|      15      78      24.980    11777203677252
      10671  Blue    D |0136/ADB4/58C2/E79F|      15      70      23.324    19623050301252
      21525  Blue    L |1036/ADB4/58C2/E79F|      15      70      23.367     1228430858052
      42928  Blue    L |1306/ADB4/58C2/E79F|      15      72      23.495      169837322052
      85377  Blue    L |1360/ADB4/58C2/E79F|      15      74      23.749      111878128452
     168152  Blue    U |1364/ADB0/58C2/E79F|      15      74      24.413      106565484612
     329996  Blue    U |1364/ADB2/58C0/E79F|      15      74      24.739      106565156268
     642726  Blue    R |1364/ADB2/580C/E79F|      15      72      24.249      106565156580
    1246294  Blue    R |1364/ADB2/508C/E79F|      15      72      23.917      106565159700
    2396768  Blue    D |1364/A0B2/5D8C/E79F|      15      64      21.633      106576044660
   53888966   Red    R |1364/0AB2/5D8C/E79F|      15      62      21.166      106797401460
   29074972   Red    U |1364/5AB2/0D8C/E79F|      15      62      22.091      106394277060
   15537643   Red    L |1364/5AB2/D08C/E79F|      15      64      22.672      106394263380
    8238754   Red    U |1364/5AB2/D78C/E09F|      15      64      23.875      106394258914
    4329748   Red    L |1364/5AB2/D78C/E90F|      15      64      24.249      106394258911
    2260520   Red    L |1364/5AB2/D78C/E9F0|       6      34      11.747      106394258910
    1170710   Red    D |1364/5AB2/D780/E9FC|      12      58      20.640      106394258989
     603117   Red    R |1364/5AB2/D708/E9FC|      12      58      20.248      106394259493
     308288   Red    D |1364/5A02/D7B8/E9FC|      12      50      17.944      106396078573
     156795   Red    L |1364/5A20/D7B8/E9FC|      12      50      18.055      106393135213
      79148   Red    U |1364/5A28/D7B0/E9FC|      12      50      19.748      106392847909
      39781   Red    R |1364/5A28/D70B/E9FC|      12      50      19.183      106392848317
      19862   Red    R |1364/5A28/D07B/E9FC|      12      50      18.815      106392852037
       9877   Red    U |1364/5A28/D97B/E0FC|      14      50      20.640      106392848411
       4838   Red    R |1364/5A28/D97B/0EFC|      13      48      19.950      106392848421
       2337   Red    D |1364/5A28/097B/DEFC|      12      40      17.146      106392863529
       1115   Red    L |1364/5A28/907B/DEFC|      12      40      17.664      106392836889
        534   Red    D |1364/5028/9A7B/DEFC|      12      32      15.232      106414606089
        259   Red    L |1364/5208/9A7B/DEFC|      12      32      15.362      106381584009
        124   Red    D |1304/5268/9A7B/DEFC|      12      26      13.711      168648526089
         56   Red    R |1034/5268/9A7B/DEFC|      12      24      13.491     1227242062089
         24   Red    U |1234/5068/9A7B/DEFC|      12      24      14.071          36334089
         10   Red    L |1234/5608/9A7B/DEFC|      12      24      14.491           3312009
          5   Red    U |1234/5678/9A0B/DEFC|      12      24      16.310               609
          2   Red    L |1234/5678/9AB0/DEFC|      12      24      16.971               105
          1   Red    U |1234/5678/9ABC/DEF0|       0       0       0.000                 0
  45 moves: RDDLULDRURDLLLUURRDRULULLDRDLURRURDLDLDRULULU

     Record Stash Move |Board layout by row|  Max|d|  Sum|d|   Euclidean   Encoded vs Zero
          1  Blue      |DAB6/5314/80C2/E79F|      15      94      29.155    16535302211892
          4  Blue    R |DAB6/5314/08C2/E79F|      15      94      28.879    16535302234212
         12  Blue    D |DAB6/0314/58C2/E79F|      15      94      28.178    16535581621572
         31  Blue    D |0AB6/D314/58C2/E79F|      15      86      26.268    20458524891972
         65  Blue    L |A0B6/D314/58C2/E79F|      15      88      26.646    13048370139972
        133  Blue    U |A3B6/D014/58C2/E79F|      15      90      27.092    11995513736772
        281  Blue    L |A3B6/D104/58C2/E79F|      15      90      27.129    11995480714692
        599  Blue    D |A306/D1B4/58C2/E79F|      15      82      25.456    12026654646852
       1266  Blue    R |A036/D1B4/58C2/E79F|      15      80      25.338    13004296912452
       2610  Blue    U |A136/D0B4/58C2/E79F|      15      80      25.495    11777091184452
       5305  Blue    R |A136/0DB4/58C2/E79F|      15      78      24.980    11777203677252
      10671  Blue    D |0136/ADB4/58C2/E79F|      15      70      23.324    19623050301252
      21525  Blue    L |1036/ADB4/58C2/E79F|      15      70      23.367     1228430858052
      42928  Blue    L |1306/ADB4/58C2/E79F|      15      72      23.495      169837322052
      85377  Blue    L |1360/ADB4/58C2/E79F|      15      74      23.749      111878128452
     168152  Blue    U |1364/ADB0/58C2/E79F|      15      74      24.413      106565484612
     329996  Blue    U |1364/ADB2/58C0/E79F|      15      74      24.739      106565156268
     642726  Blue    R |1364/ADB2/580C/E79F|      15      72      24.249      106565156580
    1246294  Blue    R |1364/ADB2/508C/E79F|      15      72      23.917      106565159700
    2396770  Blue    U |1364/ADB2/578C/E09F|      15      72      25.060      106565155234
   53890081   Red    L |1364/ADB2/578C/E90F|      15      72      25.417      106565155231
   29075603   Red    L |1364/ADB2/578C/E9F0|       7      42      14.000      106565155230
   15537991   Red    D |1364/ADB2/5780/E9FC|      12      66      22.000      106565155309
    8238950   Red    R |1364/ADB2/5708/E9FC|      12      66      21.633      106565155813
    4329857   Red    D |1364/AD02/57B8/E9FC|      12      58      19.494      106566606973
    2260581   Red    L |1364/AD20/57B8/E9FC|      12      58      19.596      106563663613
    1170743   Red    U |1364/AD28/57B0/E9FC|      12      58      21.166      106563421669
     603137   Red    R |1364/AD28/570B/E9FC|      12      58      20.640      106563422077
     308301   Red    R |1364/AD28/507B/E9FC|      12      58      20.298      106563425797
     156804   Red    D |1364/A028/5D7B/E9FC|      12      50      17.550      106574310757
      79152   Red    R |1364/0A28/5D7B/E9FC|      12      48      16.971      106795667557
      39783   Red    U |1364/5A28/0D7B/E9FC|      12      48      18.111      106392865717
      19862   Red    L |1364/5A28/D07B/E9FC|      12      50      18.815      106392852037
       9877   Red    U |1364/5A28/D97B/E0FC|      14      50      20.640      106392848411
       4838   Red    R |1364/5A28/D97B/0EFC|      13      48      19.950      106392848421
       2337   Red    D |1364/5A28/097B/DEFC|      12      40      17.146      106392863529
       1115   Red    L |1364/5A28/907B/DEFC|      12      40      17.664      106392836889
        534   Red    D |1364/5028/9A7B/DEFC|      12      32      15.232      106414606089
        259   Red    L |1364/5208/9A7B/DEFC|      12      32      15.362      106381584009
        124   Red    D |1304/5268/9A7B/DEFC|      12      26      13.711      168648526089
         56   Red    R |1034/5268/9A7B/DEFC|      12      24      13.491     1227242062089
         24   Red    U |1234/5068/9A7B/DEFC|      12      24      14.071          36334089
         10   Red    L |1234/5608/9A7B/DEFC|      12      24      14.491           3312009
          5   Red    U |1234/5678/9A0B/DEFC|      12      24      16.310               609
          2   Red    L |1234/5678/9AB0/DEFC|      12      24      16.971               105
          1   Red    U |1234/5678/9ABC/DEF0|       0       0       0.000                 0
  45 moves: RDDLULDRURDLLLUURRULLDRDLURRDRULURDLDLDRULULU

The first and second move sequences start the same, until position 314204 (the sixteenth move) and then advance D to 612120 or U to 612122. In the first case the Euclidean distance falls while the encoded distance rises but in the second case the Euclidean distance rises while the encoded distance falls. If there might be any guidance to be found in these figures, it is not immediately obvious. But, after an increase, the next step achieves a large reduction, beyond the previous distance. Humm.

Maximum Separation

Just how far away can a position be from ZERO? Alas, 16! is rather large, but lesser board sizes can be perused to completion in a reasonable time. Changing the values in NR and NC is easy, though some other changes are helpful. The hash calculation that is nicely balanced for the 4x4 board doesn't give good spray for a lesser board, but an ad-hoc change to BRD(1)*9 + BRD(2) works well enough. The blue tide is plugged by suppressing its code, and the red tide flows until it can flow no more. For these runs, the output is slightly edited to omit redundant information such as that referring to the blue tide which isn't flowing.

To play 'slide-square' with 3 rows and 4 columns.

Tide  Red
 Preparing a stash in file SlideSolveR3C4.123456789AB0.dat
 ... with an index in file SlideSolveR3C4.123456789AB0.ndx
   199999991  zero values for an empty index.
 1 2 3 4 5 6 7 8 9 A B 0 is the board layout in INTEGER*1
 4030201 8070605   B0A09       0 is the board layout in INTEGER*4
        48372615          B0A090 ..interleaved into two INTEGER*4
                        8AA0F74D combined.
                     -1969162419 as a decimal integer.
ABS(MOD(-1969162419,199999991)) + 2 = 169162502 is the record number for the first index entry.

     | Tidewrack Boundary Positions|      Positions   |     Primary Probes        Index Use  |Memory of Time Passed
Surge|     First      Last    Count|  Checked Deja vu%|     Made Max.L  Avg.L|  Used%   Load%|    CPU      Clock
    1|         1         1        1|        2     0.00|        0     0  0.000|  0.000   0.000|  0.0secs  0.0secs  0.00%  7:57:19.187am.
    2|         2         3        2|        4     0.00|        0     0  0.000|  0.000   0.000|  0.0secs  0.1secs 16.62%  7:57:19.281am.
    3|         4         7        4|        9     0.00|        0     0  0.000|  0.000   0.000|  0.0secs  0.0secs  0.00%  7:57:19.296am.
    4|         8        16        9|       20     0.00|        0     0  0.000|  0.000   0.000|  0.0secs  0.0secs         7:57:19.312am.
    5|        17        36       20|       37     0.00|        0     0  0.000|  0.000   0.000|  0.0secs  0.0secs         7:57:19.312am.
    6|        37        73       37|       64     1.56|        1     1  1.000|  0.000   0.000|  0.0secs  0.2secs  0.00%  7:57:19.468am.
    7|        74       136       63|      125     2.40|        3     1  1.000|  0.000   0.000|  0.0secs  0.0secs  0.00%  7:57:19.515am.
    8|       137       258      122|      241     3.73|        9     1  1.000|  0.000   0.000|  0.0secs  0.1secs  0.00%  7:57:19.609am.
    9|       259       490      232|      451     4.43|       20     1  1.000|  0.000   0.000|  0.0secs  0.1secs  0.00%  7:57:19.671am.
   10|       491       921      431|      827     5.56|       46     1  1.000|  0.001   0.001|  0.0secs  0.1secs  0.00%  7:57:19.796am.
   11|       922      1702      781|     1481     6.01|       89     1  1.000|  0.002   0.002|  0.0secs  0.2secs 15.32%  7:57:20.000am.
   12|      1703      3094     1392|     2671     6.63|      177     1  1.000|  0.003   0.003|  0.0secs  0.4secs  8.33%  7:57:20.375am.
   13|      3095      5588     2494|     4770     6.88|      328     1  1.000|  0.005   0.005|  0.1secs  1.0secs 11.12%  7:57:21.359am.
   14|      5589     10030     4442|     8502     7.62|      652     1  1.000|  0.009   0.009|  0.4secs  1.8secs 21.06%  7:57:23.140am.
   15|     10031     17884     7854|    15135     8.17|     1237     2  1.001|  0.016   0.016|  0.6secs  2.8secs 22.90%  7:57:25.937am.
   16|     17885     31783    13899|    26554     8.81|     2351     1  1.000|  0.028   0.028|  0.9secs  5.0secs 17.65%  7:57:30.984am.
   17|     31784     55998    24215|    46180     9.48|     4409     2  1.000|  0.049   0.049|  1.9secs  7.4secs 26.21%  7:57:38.375am.
   18|     55999     97800    41802|    79668    10.67|     8584     2  1.000|  0.084   0.084|  3.1secs 11.7secs 26.87%  7:57:50.062am.
   19|     97801    168967    71167|   135867    11.76|    16270     3  1.001|  0.144   0.144|  5.1secs 15.7secs 32.40%  7:58:05.734am.
   20|    168968    288855   119888|   228379    13.14|    30689     2  1.001|  0.243   0.244|  9.4secs 21.4secs 43.75%  7:58:27.125am.
   21|    288856    487218   198363|   377240    14.32|    55757     2  1.002|  0.404   0.405| 15.0secs 26.5secs 56.39%  7:58:53.671am.
   22|    487219    810424   323206|   612123    15.74|   100162     2  1.002|  0.660   0.663| 21.7secs 42.4secs 51.16%  7:59:36.093am.
   23|    810425   1326202   515778|   977833    17.06|   176608     3  1.003|  1.060   1.069| 32.7secs 59.2secs 55.29%  8:00:35.296am.
   24|   1326203   2137202   811000|  1533678    18.63|   306896     3  1.005|  1.674   1.693| 56.0secs 91.9secs 60.91%  8:02:07.203am.
   25|   2137203   3385213  1248011|  2358532    20.07|   522581     4  1.008|  2.592   2.635| 85.7secs  2.1mins 67.00%  8:04:15.062am.
   26|   3385214   5270492  1885279|  3554164    21.71|   877235     4  1.012|  3.930   4.026|  2.1mins  3.4mins 60.44%  8:07:40.187am.
   27|   5270493   8052888  2782396|  5239108    23.47|  1452058     5  1.017|  5.824   6.031|  3.1mins  4.4mins 70.93%  8:12:05.062am.
   28|   8052889  12062610  4009722|  7534021    25.39|  2357673     5  1.025|  8.412   8.842|  4.4mins  6.8mins 64.81%  8:18:50.843am.
   29|  12062611  17683964  5621354| 10540894    27.45|  3736777     5  1.037| 11.814  12.666|  5.9mins  8.7mins 67.49%  8:27:35.375am.
   30|  17683965  25331836  7647872| 14308274    29.65|  5757092     5  1.052| 16.090  17.699|  8.1mins 12.1mins 66.43%  8:39:42.640am.
   31|  25331837  35397636 10065800| 18773730    32.03|  8547647     6  1.072| 21.203  24.079| 10.4mins 14.8mins 70.68%  8:54:28.562am.
   32|  35397637  48158049 12760413| 23759506    34.47| 12146155     7  1.097| 27.009  31.864| 13.2mins 19.0mins 69.20%  9:13:31.234am.
   33|  48158050  63728835 15570786| 28870409    37.06| 16437759     8  1.126| 33.226  40.950| 15.7mins 21.8mins 72.16%  9:35:16.421am.
   34|  63728836  81900441 18171606| 33626342    39.63| 20990966     9  1.157| 39.543  51.100| 18.8mins 31.2mins 60.26% 10:06:28.671am.
   35|  81900442 102200317 20299876| 37402396    42.28| 25357818     9  1.193| 45.566  61.894| 21.7mins 43.3mins 50.19% 10:49:46.296am.
   36| 102200318 123787565 21587248| 39666978    44.94| 28674025    11  1.222| 51.062  72.814| 24.1mins 55.6mins 43.32% 11:45:22.359am.
   37| 123787566 145628724 21841159| 39956652    47.68| 30596301    10  1.253| 55.742  83.268| 24.9mins 62.8mins 39.61% 12:48:08.968pm.
   38| 145628725 166535629 20906905| 38122360    50.42| 30382490    10  1.269| 59.612  92.717| 24.8mins 65.3mins 37.94%  1:53:27.953pm.
   39| 166535630 185434986 18899357| 34311262    53.20| 28383116    11  1.286| 62.576 100.747| 22.4mins 61.1mins 36.70%  2:54:31.156pm.
   40| 185434987 201493321 16058335| 29019233    55.99| 24579603    12  1.283| 64.796 107.133| 19.0mins 52.2mins 36.48%  3:46:40.171pm.
   41| 201493322 214265924 12772603| 23016150    58.66| 19948189    10  1.281| 66.330 111.891| 14.8mins 40.7mins 36.41%  4:27:20.140pm.
   42| 214265925 223781141  9515217| 17041788    61.37| 14979073    11  1.262| 67.361 115.182| 10.9mins 29.0mins 37.55%  4:56:23.062pm.
   43| 223781142 230364322  6583181| 11772739    63.96| 10506946    10  1.249| 67.994 117.304|  7.0mins 19.0mins 37.17%  5:15:21.000pm.
   44| 230364323 234607075  4242753|  7532690    66.76|  6789748    11  1.222| 68.366 118.555|  4.3mins 11.3mins 37.93%  5:26:38.500pm.
   45| 234607076 237110948  2503873|  4439978    69.59|  4054719    10  1.206| 68.558 119.231|  2.3mins  6.1mins 37.47%  5:32:47.218pm.
   46| 237110949 238461216  1350268|  2371273    72.87|  2185279    10  1.176| 68.651 119.552| 70.6secs  2.9mins 39.92%  5:35:44.015pm.
   47| 238461217 239104461   643245|  1130010    76.08|  1055623    10  1.160| 68.689 119.687| 29.4secs 74.9secs 39.21%  5:36:58.890pm.
   48| 239104462 239374764   270303|   466424    80.21|   440280    10  1.125| 68.702 119.734| 10.8secs 26.0secs 41.36%  5:37:24.921pm.
   49| 239374765 239467075    92311|   161691    83.23|   154374     8  1.111| 68.705 119.747|  3.4secs  7.6secs 45.08%  5:37:32.546pm.
   50| 239467076 239494191    27116|    44973    88.02|    43422    11  1.071| 68.706 119.750|  0.8secs  1.5secs 56.82%  5:37:34.031pm.
   51| 239494192 239499581     5390|     9553    88.33|     9259     6  1.074| 68.706 119.750|  0.1secs  0.4secs 33.32%  5:37:34.453pm.
   52| 239499582 239500696     1115|     1625    94.71|     1593     5  1.028| 68.706 119.750|  0.0secs  0.0secs104.17%  5:37:34.468pm.
   53| 239500697 239500782       86|      167    89.22|      162     3  1.049| 68.706 119.750|  0.0secs  0.0secs 97.66%  5:37:34.484pm.
   54| 239500783 239500800       18|       18   100.00|       18     1  1.000| 68.706 119.750|  0.0secs  0.0secs         5:37:34.484pm.

The boundary has not surged to new positions!
The now-static boundary has 18

     Record Stash Move | Board layout | Max|d| Sum|d| Euclidean Encoded vs Zero
  239500783   Red    D |0869/B725/43A1|      7     44    14.765       466581807
  239500784   Red    D |0869/B7A1/4325|      9     58    18.601       466582214
  239500785   Red    R |8759/43A2/0B61|      9     48    16.310       302860487
  239500786   Red    R |4325/8761/0BA9|      9     38    15.362       127427903
  239500787   Red    R |0821/B3A5/4769|      9     48    15.811       464885186
  239500788   Red    D |0B21/3765/48A9|      9     38    14.765       475738105
  239500789   Red    R |4321/8B65/07A9|      9     42    15.362       127389739
  239500790   Red    D |0861/B325/47A9|      9     48    15.811       466336825
  239500791   Red    D |0829/B365/47A1|      6     36    12.410       465127617
  239500792   Red    R |0829/B7A5/4361|      7     44    14.765       465130767
  239500793   Red    R |4321/8769/0BA5|      9     30    11.832       127387607
  239500794   Red    R |B821/37A5/0469|     10     58    19.799       424935162
  239500795   Red    R |4361/8725/0BA9|      9     42    15.362       128113223
  239500796   Red    R |4321/B765/08A9|      9     40    15.297       127402699
  239500797   Red    U |8369/4725/0BA1|      9     38    14.283       288340727
  239500798   Red    U |8329/476A/0B51|      9     36    14.213       287247191
  239500799   Red    R |0321/8765/4BA9|      9     30    11.832       446727869
  239500800   Red    R |8321/4765/0BA9|      9     38    15.362       287039663
No progress!

Whereas for four rows and three columns,

To play 'slide-square' with 4 rows and 3 columns.

Tide  Red
 Preparing a stash in file SlideSolveR4C3.123456789AB0.dat
 ... with an index in file SlideSolveR4C3.123456789AB0.ndx
   199999991  zero values for an empty index.
 1 2 3 4 5 6 7 8 9 A B 0 is the board layout in INTEGER*1
 4030201 8070605   B0A09       0 is the board layout in INTEGER*4
        48372615          B0A090 ..interleaved into two INTEGER*4
                        8AA0F74D combined.
                     -1969162419 as a decimal integer.
ABS(MOD(-1969162419,199999991)) + 2 = 169162502 is the record number for the first index entry.

     | Tidewrack Boundary Positions |    Positions     |    Primary Probes       Index Use   |Memory of Time Passed
Surge|     First       Last    Count|  Checked Deja vu%|     Made Max.L Avg.L|  Used%   Load%|    CPU      Clock
    1|         1          1        1|        2     0.00|        0     0 0.000|  0.000   0.000|  0.0secs  0.0secs  0.00%  7:50:20.203pm.
    2|         2          3        2|        4     0.00|        0     0 0.000|  0.000   0.000|  0.0secs  0.1secs 12.50%  7:50:20.328pm.
    3|         4          7        4|        9     0.00|        0     0 0.000|  0.000   0.000|  0.0secs  0.0secs         7:50:20.328pm.
    4|         8         16        9|       20     0.00|        0     0 0.000|  0.000   0.000|  0.0secs  0.0secs  0.00%  7:50:20.343pm.
    5|        17         36       20|       37     0.00|        0     0 0.000|  0.000   0.000|  0.0secs  0.1secs  0.00%  7:50:20.421pm.
    6|        37         73       37|       64     1.56|        1     1 1.000|  0.000   0.000|  0.0secs  0.0secs  0.00%  7:50:20.437pm.
    7|        74        136       63|      125     2.40|        3     1 1.000|  0.000   0.000|  0.0secs  0.2secs  8.31%  7:50:20.625pm.
    8|       137        258      122|      241     3.73|        9     1 1.000|  0.000   0.000|  0.0secs  0.1secs 50.40%  7:50:20.687pm.
    9|       259        490      232|      451     4.43|       20     1 1.000|  0.000   0.000|  0.0secs  0.1secs 49.87%  7:50:20.781pm.
   10|       491        921      431|      827     5.56|       46     1 1.000|  0.001   0.001|  0.1secs  0.2secs 63.59%  7:50:20.953pm.
   11|       922       1702      781|     1481     6.01|       89     1 1.000|  0.002   0.002|  0.1secs  0.3secs 35.06%  7:50:21.265pm.
   12|      1703       3094     1392|     2671     6.63|      177     1 1.000|  0.003   0.003|  0.2secs  0.6secs 41.63%  7:50:21.828pm.
   13|      3095       5588     2494|     4770     6.88|      328     1 1.000|  0.005   0.005|  0.5secs  1.5secs 36.19%  7:50:23.296pm.
   14|      5589      10030     4442|     8502     7.62|      648     1 1.000|  0.009   0.009|  0.8secs  2.2secs 34.50%  7:50:25.515pm.
   15|     10031      17884     7854|    15135     8.17|     1237     1 1.000|  0.016   0.016|  1.3secs  3.9secs 33.60%  7:50:29.421pm.
   16|     17885      31783    13899|    26554     8.81|     2344     1 1.000|  0.028   0.028|  2.2secs  5.9secs 36.77%  7:50:35.328pm.
   17|     31784      55998    24215|    46180     9.48|     4397     1 1.000|  0.049   0.049|  3.1secs  9.5secs 32.73%  7:50:44.781pm.
   18|     55999      97800    41802|    79668    10.67|     8574     2 1.000|  0.084   0.084|  6.0secs 14.5secs 41.25%  7:50:59.328pm.
   19|     97801     168967    71167|   135867    11.76|    16154     2 1.000|  0.144   0.144|  9.3secs 20.5secs 45.46%  7:51:19.812pm.
   20|    168968     288855   119888|   228379    13.14|    30520     2 1.000|  0.243   0.244| 14.7secs 27.9secs 52.72%  7:51:47.671pm.
   21|    288856     487218   198363|   377240    14.32|    55395     2 1.001|  0.404   0.405| 18.5secs 33.0secs 56.18%  7:52:20.656pm.
   22|    487219     810424   323206|   612123    15.74|    99611     2 1.002|  0.660   0.663| 22.4secs 41.7secs 53.62%  7:53:02.359pm.
   23|    810425    1326202   515778|   977833    17.06|   175049     2 1.003|  1.062   1.069| 35.9secs 61.9secs 57.96%  7:54:04.250pm.
   24|   1326203    2137202   811000|  1533678    18.63|   305322     3 1.004|  1.676   1.693| 55.4secs 94.5secs 58.68%  7:55:38.718pm.
   25|   2137203    3385213  1248011|  2358532    20.07|   519257     4 1.007|  2.596   2.635| 87.8secs  2.3mins 63.69%  7:57:56.562pm.
   26|   3385214    5270492  1885279|  3554164    21.71|   872963     3 1.010|  3.936   4.026|  2.2mins  3.4mins 63.19%  8:01:21.109pm.
   27|   5270493    8052888  2782396|  5239108    23.47|  1445680     4 1.016|  5.833   6.031|  3.2mins  4.8mins 66.44%  8:06:08.906pm.
   28|   8052889   12062610  4009722|  7534021    25.39|  2348797     4 1.024|  8.426   8.842|  4.5mins  6.9mins 65.49%  8:13:00.218pm.
   29|  12062611   17683964  5621354| 10540894    27.45|  3734408     5 1.036| 11.829  12.666|  6.4mins  9.1mins 70.67%  8:22:04.328pm.
   30|  17683965   25331836  7647872| 14308274    29.65|  5746564     5 1.051| 16.110  17.699|  8.3mins 12.3mins 67.88%  8:34:21.375pm.
   31|  25331837   35397636 10065800| 18773730    32.03|  8552447     6 1.071| 21.220  24.079| 10.9mins 15.7mins 69.40%  8:50:02.062pm.
   32|  35397637   48158049 12760413| 23759506    34.47| 12145305     7 1.096| 27.027  31.864| 13.8mins 19.5mins 70.96%  9:09:32.671pm.
   33|  48158050   63728835 15570786| 28870409    37.06| 16448462     7 1.125| 33.238  40.950| 16.7mins 23.2mins 71.94%  9:32:46.109pm.
   34|  63728836   81900441 18171606| 33626342    39.63| 21022994     9 1.158| 39.540  51.100| 20.1mins 32.4mins 62.17% 10:05:09.859pm.
   35|  81900442  102200317 20299876| 37402396    42.28| 25358874     8 1.192| 45.562  61.894| 21.9mins 44.7mins 49.02% 10:49:50.296pm.
   36| 102200318  123787565 21587248| 39666978    44.94| 28734429    10 1.224| 51.028  72.814| 25.6mins 56.6mins 45.33% 11:46:25.156pm.
   37| 123787566  145628724 21841159| 39956652    47.68| 30572287    11 1.252| 55.720  83.268| 27.1mins 64.5mins 41.99%  0:50:52.171am.
   38| 145628725  166535629 20906905| 38122360    50.42| 30446707    10 1.273| 59.558  92.717| 26.6mins 66.4mins 40.12%  1:57:16.468am.
   39| 166535630  185434986 18899357| 34311262    53.20| 28341786    10 1.283| 62.543 100.747| 23.7mins 62.2mins 38.16%  2:59:28.250am.
   40| 185434987  201493321 16058335| 29019233    55.99| 24605484    10 1.285| 64.750 107.133| 20.0mins 53.8mins 37.26%  3:53:15.640am.
   41| 201493322  214265924 12772603| 23016150    58.66| 19903099    11 1.276| 66.306 111.891| 15.5mins 41.1mins 37.86%  4:34:19.671am.
   42| 214265925  223781141  9515217| 17041788    61.37| 14979317    10 1.262| 67.337 115.182| 11.3mins 29.2mins 38.48%  5:03:33.937am.
   43| 223781142  230364322  6583181| 11772739    63.96| 10478430    12 1.241| 67.985 117.304|  7.4mins 19.1mins 38.79%  5:22:39.250am.
   44| 230364323  234607075  4242753|  7532690    66.76|  6785656    11 1.220| 68.358 118.555|  4.4mins 11.4mins 38.96%  5:34:04.359am.
   45| 234607076  237110948  2503873|  4439978    69.59|  4041159    10 1.197| 68.558 119.231|  2.4mins  6.2mins 38.76%  5:40:15.031am.
   46| 237110949  238461216  1350268|  2371273    72.87|  2182892    11 1.172| 68.652 119.552| 70.1secs  3.0mins 39.48%  5:43:12.625am.
   47| 238461217  239104461   643245|  1130010    76.08|  1051526    10 1.149| 68.691 119.687| 30.2secs 76.2secs 39.66%  5:44:28.812am.
   48| 239104462  239374764   270303|   466424    80.21|   439307    10 1.118| 68.705 119.734| 11.2secs 26.3secs 42.40%  5:44:55.125am.
   49| 239374765  239467075    92311|   161691    83.23|   153927     9 1.102| 68.708 119.747|  3.6secs  7.9secs 45.63%  5:45:03.000am.
   50| 239467076  239494191    27116|    44973    88.02|    43358     9 1.067| 68.709 119.750|  0.8secs  1.7secs 44.15%  5:45:04.734am.
   51| 239494192  239499581     5390|     9553    88.33|     9231     6 1.062| 68.709 119.750|  0.1secs  0.3secs 52.87%  5:45:05.000am.
   52| 239499582  239500696     1115|     1625    94.71|     1598     4 1.029| 68.709 119.750|  0.0secs  0.0secs100.81%  5:45:05.031am.
   53| 239500697  239500782       86|      167    89.22|      161     3 1.043| 68.709 119.750|  0.0secs  0.0secs         5:45:05.031am.
   54| 239500783  239500800       18|       18   100.00|       18     1 1.000| 68.709 119.750|  0.0secs  0.0secs         5:45:05.031am.

The boundary has not surged to new positions!
The now-static boundary has 18

     Record Stash Move |  Board layout | Max|d| Sum|d| Euclidean Encoded vs Zero
  239500783   Red    D |09A/B78/456/321|      9     52    17.720       471375815
  239500784   Red    L |A90/78B/456/123|      9     60    19.494       391824450
  239500785   Red    L |BA0/789/546/321|     10     56    18.974       435370175
  239500786   Red    D |BA0/789/452/361|     10     56    18.493       435370041
  239500787   Red    R |07A/98B/456/123|      9     56    18.221       464077890
  239500788   Red    R |09A/B87/465/321|      9     52    17.833       471380879
  239500789   Red    R |09A/B87/546/321|      9     52    17.833       471380975
  239500790   Red    R |09A/B87/564/312|     10     54    18.547       471380998
  239500791   Red    L |970/B8A/465/123|      9     60    19.287       344730714
  239500792   Red    L |AB0/789/546/123|      9     60    19.950       395453370
  239500793   Red    L |BA0/879/265/341|     10     56    18.601       435410157
  239500794   Red    R |09A/B78/546/123|      9     56    18.868       471375930
  239500795   Red    L |AB0/789/456/132|      9     58    19.339       395453251
  239500796   Red    R |09A/B78/465/123|      9     56    18.868       471375834
  239500797   Red    D |AB0/798/456/123|      9     60    19.950       395458290
  239500798   Red    L |AB0/789/456/213|     10     60    19.950       395453252
  239500799   Red    L |BA0/789/456/123|     10     60    19.950       435370050
  239500800   Red    R |0BA/789/456/123|      9     56    18.868       478915650
No progress!

Reducing to a 3x3 board encouraged a reduction in the size of the index. The run took about ten seconds.

To play 'slide-square' with 3 rows and 3 columns.

Tide  Red
 Preparing a stash in file SlideSolveR3C3.123456780.dat
 ... with an index in file SlideSolveR3C3.123456780.ndx
      199991  zero values for an empty index.
 1 2 3 4 5 6 7 8 0 is the board layout in INTEGER*1
 4030201 8070605       0       0 is the board layout in INTEGER*4
        48372615               0 ..interleaved into two INTEGER*4
                        89F056BD multiplied together in INTEGER*4
                     -1980737859 as a decimal integer.
ABS(MOD(-1980737859,199991)) + 2 = 26997 is the record number for the first index entry.

     |Tidewrack Boundary Positions|  Positions     |  Primary Probes       Index Use
Surge|   First       Last    Count|Checked Deja vu%|  Made Max.L  Avg.L|  Used%  Load%
    1|       1          1        1|      2     0.00|     0     0  0.000|  0.002  0.002
    2|       2          3        2|      4     0.00|     0     0  0.000|  0.004  0.004
    3|       4          7        4|      8     0.00|     0     0  0.000|  0.008  0.008
    4|       8         15        8|     16     0.00|     0     0  0.000|  0.016  0.016
    5|      16         31       16|     20     0.00|     0     0  0.000|  0.026  0.026
    6|      32         51       20|     40     2.50|     1     1  1.000|  0.045  0.045
    7|      52         90       39|     65     4.62|     3     1  1.000|  0.076  0.076
    8|      91        152       62|    124     6.45|     8     1  1.000|  0.134  0.134
    9|     153        268      116|    164     7.32|    12     1  1.000|  0.210  0.210
   10|     269        420      152|    304     5.92|    18     1  1.000|  0.353  0.353
   11|     421        706      286|    430     7.91|    38     1  1.000|  0.549  0.551
   12|     707       1102      396|    792     5.56|    53     1  1.000|  0.919  0.925
   13|    1103       1850      748|   1114     8.08|    97     2  1.010|  1.427  1.437
   14|    1851       2874     1024|   2048     7.57|   189     1  1.000|  2.357  2.384
   15|    2875       4767     1893|   2799    10.25|   356     2  1.006|  3.578  3.640
   16|    4768       7279     2512|   5024    10.73|   738     2  1.019|  5.721  5.882
   17|    7280      11764     4485|   6599    14.56|  1365     2  1.023|  8.338  8.701
   18|   11765      17402     5638|  11276    15.49|  2734     3  1.042| 12.610 13.466
   19|   17403      26931     9529|  13867    21.55|  4630     3  1.054| 17.228 18.905
   20|   26932      37809    10878|  21756    21.89|  8302     4  1.087| 23.956 27.402
   21|   37810      54802    16993|  24289    29.56| 11793     4  1.102| 30.204 35.958
   22|   54803      71912    17110|  34220    30.01| 18451     4  1.151| 38.089 47.934
   23|   71913      95864    23952|  33912    40.36| 21895     6  1.165| 44.097 58.047
   24|   95865     116088    20224|  40448    40.55| 27617     6  1.205| 50.513 70.071
   25|  116089     140135    24047|  33131    52.98| 25580     7  1.200| 54.289 77.860
   26|  140136     155713    15578|  31156    53.27| 24657     5  1.208| 57.539 85.140
   27|  155714     170273    14560|  19530    67.88| 16842     6  1.157| 58.883 88.277
   28|  170274     176547     6274|  12548    68.84| 10945     5  1.137| 59.684 90.233
   29|  176548     180457     3910|   4926    84.57|  4615     6  1.068| 59.840 90.613
   30|  180458     181217      760|   1520    85.46|  1424     4  1.064| 59.888 90.723
   31|  181218     181438      221|    265    99.25|   264     1  1.000| 59.888 90.724
   32|  181439     181440        2|      4   100.00|     4     1  1.000| 59.888 90.724

The boundary has not surged to new positions!
The now-static boundary has 2

     Record Stash Move |Board  plan| Max|d| Sum|d| Euclidean Encoded vs Zero
     181439   Red    D |647/850/321|      6     32    12.247          220175
     181440   Red    U |867/254/301|      8     32    13.038          311247
No progress!

Thus, for a 3x3 board the greatest separation is thirty-two steps to two positions, while the 3x4 and 4x3 boards both have eighteen positions fifty-two steps away from ZERO.

Go

Translation of: C++
package main

import "fmt"

var (
    Nr = [16]int{3, 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3}
    Nc = [16]int{3, 0, 1, 2, 3, 0, 1, 2, 3, 0, 1, 2, 3, 0, 1, 2}
)

var (
    n, _n      int
    N0, N3, N4 [85]int
    N2         [85]uint64
)

const (
    i = 1
    g = 8
    e = 2
    l = 4
)

func fY() bool {
    if N2[n] == 0x123456789abcdef0 {
        return true
    }
    if N4[n] <= _n {
        return fN()
    }
    return false
}

func fZ(w int) bool {
    if w&i > 0 {
        fI()
        if fY() {
            return true
        }
        n--
    }
    if w&g > 0 {
        fG()
        if fY() {
            return true
        }
        n--
    }
    if w&e > 0 {
        fE()
        if fY() {
            return true
        }
        n--
    }
    if w&l > 0 {
        fL()
        if fY() {
            return true
        }
        n--
    }
    return false
}

func fN() bool {
    switch N0[n] {
    case 0:
        switch N3[n] {
        case 'l':
            return fZ(i)
        case 'u':
            return fZ(e)
        default:
            return fZ(i + e)
        }
    case 3:
        switch N3[n] {
        case 'r':
            return fZ(i)
        case 'u':
            return fZ(l)
        default:
            return fZ(i + l)
        }
    case 1, 2:
        switch N3[n] {
        case 'l':
            return fZ(i + l)
        case 'r':
            return fZ(i + e)
        case 'u':
            return fZ(e + l)
        default:
            return fZ(l + e + i)
        }
    case 12:
        switch N3[n] {
        case 'l':
            return fZ(g)
        case 'd':
            return fZ(e)
        default:
            return fZ(e + g)
        }
    case 15:
        switch N3[n] {
        case 'r':
            return fZ(g)
        case 'd':
            return fZ(l)
        default:
            return fZ(g + l)
        }
    case 13, 14:
        switch N3[n] {
        case 'l':
            return fZ(g + l)
        case 'r':
            return fZ(e + g)
        case 'd':
            return fZ(e + l)
        default:
            return fZ(g + e + l)
        }
    case 4, 8:
        switch N3[n] {
        case 'l':
            return fZ(i + g)
        case 'u':
            return fZ(g + e)
        case 'd':
            return fZ(i + e)
        default:
            return fZ(i + g + e)
        }
    case 7, 11:
        switch N3[n] {
        case 'd':
            return fZ(i + l)
        case 'u':
            return fZ(g + l)
        case 'r':
            return fZ(i + g)
        default:
            return fZ(i + g + l)
        }
    default:
        switch N3[n] {
        case 'd':
            return fZ(i + e + l)
        case 'l':
            return fZ(i + g + l)
        case 'r':
            return fZ(i + g + e)
        case 'u':
            return fZ(g + e + l)
        default:
            return fZ(i + g + e + l)
        }
    }
}

func fI() {
    g := (11 - N0[n]) * 4
    a := N2[n] & uint64(15<<uint(g))
    N0[n+1] = N0[n] + 4
    N2[n+1] = N2[n] - a + (a << 16)
    N3[n+1] = 'd'
    N4[n+1] = N4[n]
    cond := Nr[a>>uint(g)] <= N0[n]/4
    if !cond {
        N4[n+1]++
    }
    n++
}

func fG() {
    g := (19 - N0[n]) * 4
    a := N2[n] & uint64(15<<uint(g))
    N0[n+1] = N0[n] - 4
    N2[n+1] = N2[n] - a + (a >> 16)
    N3[n+1] = 'u'
    N4[n+1] = N4[n]
    cond := Nr[a>>uint(g)] >= N0[n]/4
    if !cond {
        N4[n+1]++
    }
    n++
}

func fE() {
    g := (14 - N0[n]) * 4
    a := N2[n] & uint64(15<<uint(g))
    N0[n+1] = N0[n] + 1
    N2[n+1] = N2[n] - a + (a << 4)
    N3[n+1] = 'r'
    N4[n+1] = N4[n]
    cond := Nc[a>>uint(g)] <= N0[n]%4
    if !cond {
        N4[n+1]++
    }
    n++
}

func fL() {
    g := (16 - N0[n]) * 4
    a := N2[n] & uint64(15<<uint(g))
    N0[n+1] = N0[n] - 1
    N2[n+1] = N2[n] - a + (a >> 4)
    N3[n+1] = 'l'
    N4[n+1] = N4[n]
    cond := Nc[a>>uint(g)] >= N0[n]%4
    if !cond {
        N4[n+1]++
    }
    n++
}

func fifteenSolver(n int, g uint64) {
    N0[0] = n
    N2[0] = g
    N4[0] = 0
}

func solve() {
    if fN() {
        fmt.Print("Solution found in ", n, " moves: ")
        for g := 1; g <= n; g++ {
            fmt.Printf("%c", N3[g])
        }
        fmt.Println()
    } else {
        n = 0
        _n++
        solve()
    }
}

func main() {
    fifteenSolver(8, 0xfe169b4c0a73d852)
    solve()
}
Output:
Solution found in 52 moves: rrrulddluuuldrurdddrullulurrrddldluurddlulurruldrdrd

Java

import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.PriorityQueue;
import java.util.Queue;
import java.util.Set;
import java.util.stream.Collectors;

public final class Puzzle15Solver {
    
    public static void main(String[] aArgs) {
        List<Integer> start = List.of( 15, 14, 1, 6, 9, 11, 4, 12, 0, 10, 7, 3, 13, 8, 5, 2 );
        final int zeroIndex = 8;
        Puzzle initial = new Puzzle(start, new ArrayList<String>(), zeroIndex, 0);
        openSet.add(initial);
        System.out.println("Solving the 15 puzzle:");
        initial.display();        
        
        while ( solution == null ) {
            search();
        }

        System.out.println(solution.moves.stream().collect(Collectors.joining("")));
        System.out.println("Number of steps: " + solution.moves.size());
        System.out.println("Number of puzzle states checked: " + closedSet.size());
    }
    
    private static void search() {
		Puzzle current = openSet.poll();
	    closedSet.add(current);
	    final int zeroIndex = current.zeroIndex;
	    final int row = zeroIndex / 4;
        final int column = zeroIndex % 4;
	    
		if ( column > 0 ) {
			Puzzle nextPuzzle = current.clone();
			nextPuzzle.makeMove(Move.LEFT);
	    }
	    if ( column < 3 ) {
	    	Puzzle nextPuzzle = current.clone();
	    	nextPuzzle.makeMove(Move.RIGHT);
	    }
	    if ( row > 0 ) {
	    	Puzzle nextPuzzle = current.clone();
	    	nextPuzzle.makeMove(Move.UP);
	    }
	    if ( row < 3 ) {
	    	Puzzle nextPuzzle = current.clone();
	    	nextPuzzle.makeMove(Move.DOWN);
	    }
	}

    private enum Move {		
		LEFT("L", -1), RIGHT("R", +1), UP("U", -4), DOWN("D", +4);		
		
		private Move(String aSymbol, int aStep) {
			symbol = aSymbol;
			step = aStep;
		}
		
		private String symbol;
		private Integer step;		
	}
    
    private static class Puzzle {    

        public Puzzle(List<Integer> aTiles, List<String> aMoves, int aZeroIndex, int aSearchDepth) {
        	tiles = aTiles;
            moves = aMoves;        
            zeroIndex = aZeroIndex;
            searchDepth = aSearchDepth;             
        }
        
        public void makeMove(Move aMove) {    		
    		Integer temp = tiles.get(zeroIndex + aMove.step);
    		tiles.set(zeroIndex + aMove.step, 0);
    		tiles.set(zeroIndex, temp);
    		
    		zeroIndex += aMove.step;
    		moves.add(aMove.symbol);
    		
    		if ( ! closedSet.contains(this) ) {
                openSet.add(this);
                if ( tiles.equals(Puzzle.GOAL) ) {
                    solution = this;
                }
            }
    	}

        public long heuristic() {
        	int distance = 0;
        	for ( int i = 0; i < tiles.size(); i++ ) {
        		final int tile = tiles.get(i);
            	if ( tile > 0 ) { 
            		distance += Math.abs( ( i / 4 ) - ( tile - 1 ) / 4 ) + Math.abs( ( i % 4 ) - ( tile - 1 ) % 4 );
            	}
            }
            return distance + searchDepth;
        }  
        
        public Puzzle clone() {
            return new Puzzle(new ArrayList<Integer>(tiles), new ArrayList<String>(moves), zeroIndex, searchDepth + 1);
        }

        public void display() {
        	 for ( int i = 0; i < tiles.size(); i++ ) {
                 System.out.print(String.format("%s%2d%s",
                 	( i % 4 == 0 ) ? "[" : "", tiles.get(i), ( i % 4 == 3 ) ? "]\n" : " "));
             }
             System.out.println();
        }   

        @Override
        public boolean equals(Object aObject) {
        	return switch(aObject) {
        		case Puzzle puzzle -> tiles.equals(puzzle.tiles);
        		case Object object -> false;
        	};
        }
        
        @Override
        public int hashCode() {
            int hash = 3;
            hash = 23 * hash + tiles.hashCode();
            hash = 23 * hash + zeroIndex;
            return hash;
        }
        
        private List<Integer> tiles;
        private List<String> moves;
        private int zeroIndex;
        private int searchDepth;        
        
        private static final List<Integer> GOAL = List.of( 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 0 );
        
    }

    private static Queue<Puzzle> openSet =
    	new PriorityQueue<Puzzle>( (one, two) -> Long.compare(one.heuristic(), two.heuristic()) ); 
    private static Set<Puzzle> closedSet = new HashSet<Puzzle>();
    private static Puzzle solution;
   
}
Output:
Solving the 15 puzzle:
[15 14  1  6]
[ 9 11  4 12]
[ 0 10  7  3]
[13  8  5  2]

RRRULDLUULDRURDDDLUULURRRDLDDRULDLUURDDLULURRULDRRDD
Number of steps: 52
Number of puzzle states checked: 2276369

Julia

Translation of: C++
const Nr = [3, 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3]
const Nc = [3, 0, 1, 2, 3, 0, 1, 2, 3, 0, 1, 2, 3, 0, 1, 2]

const N0 = zeros(Int, 85)
const N2 = zeros(UInt64, 85)
const N3 = zeros(UInt8, 85)
const N4 = zeros(Int, 85)
const i = 1
const g = 8
const ee = 2
const l = 4
const _n = Vector{Int32}([0])

function fY(n::Int)
    if N2[n + 1] == UInt64(0x123456789abcdef0)
        return true, n
    end
    if N4[n + 1] <= _n[1]
        return fN(n)
    end
    false, n
end

function fZ(w, n)
    if w & i > 0
        n = fI(n)
        (y, n) = fY(n)
        if y return (true, n) end
        n -= 1
    end
    if w & g > 0
        n = fG(n)
        (y, n) = fY(n)
        if y return (true, n) end
        n -= 1
    end
    if w & ee > 0
        n = fE(n)
        (y, n) = fY(n)
        if y return (true, n) end
        n -= 1
    end
    if w & l > 0
        n = fL(n)
        (y, n) = fY(n)
        if y return (true, n) end
        n -= 1
    end
    false, n
end

function fN(n::Int)
    x = N0[n + 1]
    y = UInt8(N3[n + 1])
    if x == 0
        if y == UInt8('l')
            return fZ(i, n)
        elseif y == UInt8('u')
            return fZ(ee, n)
        else
            return fZ(i + ee, n)
        end
    elseif x == 3
        if y == UInt8('r')
            return fZ(i, n)
        elseif y == UInt8('u')
            return fZ(l, n)
        else
            return fZ(i + l, n)
        end
    elseif x == 1 || x == 2
        if y == UInt8('l')
            return fZ(i + l, n)
        elseif y == UInt8('r')
            return fZ(i + ee, n)
        elseif y == UInt8('u')
            return fZ(ee + l, n)
        else
            return fZ(l + ee + i, n)
        end
    elseif x == 12
        if y == UInt8('l')
            return fZ(g, n)
        elseif y == UInt8('d')
            return fZ(ee, n)
        else
            return fZ(ee + g, n)
        end
    elseif x == 15
        if y == UInt8('r')
            return fZ(g, n)
        elseif y == UInt8('d')
            return fZ(l, n)
        else
            return fZ(g + l, n)
        end
    elseif x == 13 || x == 14
        if y == UInt8('l')
            return fZ(g + l, n)
        elseif y == UInt8('r')
            return fZ(ee + g, n)
        elseif y == UInt8('d')
            return fZ(ee + l, n)
        else
            return fZ(g + ee + l, n)
        end
    elseif x == 4 || x == 8
        if y == UInt8('l')
            return fZ(i + g, n)
        elseif y == UInt8('u')
            return fZ(g + ee, n)
        elseif y == UInt8('d')
            return fZ(i + ee, n)
        else
            return fZ(i + g + ee, n)
        end
    elseif x == 7 || x == 11
        if y == UInt8('d')
            return fZ(i + l, n)
        elseif y == UInt8('u')
            return fZ(g + l, n)
        elseif y == UInt8('r')
                return fZ(i + g, n)
        else
            return fZ(i + g + l, n)
        end
    else
        if y == UInt8('d')
            return fZ(i + ee + l, n)
        elseif y == UInt8('l')
                return fZ(i + g + l, n)
        elseif y == UInt8('r')
            return fZ(i + g + ee, n)
        elseif y == UInt8('u')
            return fZ(g + ee + l, n)
        else
            return fZ(i + g + ee + l, n)
        end
    end
end

function fI(n)
    gg = (11 - N0[n + 1]) * 4
    a = N2[n + 1] & (UInt64(0xf) << UInt(gg))
    N0[n + 2] = N0[n + 1] + 4
    N2[n + 2] = N2[n + 1] - a + (a << 16)
    N3[n + 2] = UInt8('d')
    N4[n + 2] = N4[n + 1]
    cond = Nr[(a >> gg) + 1] <= div(N0[n + 1], 4)
    if !cond
        N4[n + 2] += 1
    end
    n += 1
    n
end

function fG(n)
    gg = (19 - N0[n + 1]) * 4
    a = N2[n + 1] & (UInt64(0xf) << UInt(gg))
    N0[n + 2] = N0[n + 1] - 4
    N2[n + 2] = N2[n + 1] - a + (a >> 16)
    N3[n + 2] = UInt8('u')
    N4[n + 2] = N4[n + 1]
    cond = Nr[(a >> gg) + 1] >= div(N0[n + 1], 4)
    if !cond
        N4[n + 2] += 1
    end
    n += 1
    n
end

function fE(n)
    gg = (14 - N0[n + 1]) * 4
    a = N2[n + 1] & (UInt64(0xf) << UInt(gg))
    N0[n + 2] = N0[n + 1] + 1
    N2[n + 2] = N2[n + 1] - a + (a << 4)
    N3[n + 2] = UInt8('r')
    N4[n + 2] = N4[n + 1]
    cond = Nc[(a >> gg) + 1] <= N0[n + 1] % 4
    if !cond
        N4[n + 2] += 1
    end
    n += 1
    n
end

function fL(n)
    gg = (16 - N0[n + 1]) * 4
    a = N2[n + 1] & (UInt64(0xf) << UInt(gg))
    N0[n + 2] = N0[n + 1] - 1
    N2[n + 2] = N2[n + 1] - a + (a >> 4)
    N3[n + 2] = UInt8('l')
    N4[n + 2] = N4[n + 1]
    cond = Nc[(a >> gg) + 1] >= N0[n + 1] % 4
    if !cond
        N4[n + 2] += 1
    end
    n += 1
    n
end

function solve(n)
    ans, n = fN(n)
    if ans
        println("Solution found in $n moves: ")
        for ch in N3[2:n+1] print(Char(ch)) end; println()
    else
        println("next iteration, _n[1] will be $(_n[1] + 1)...")
        n = 0; _n[1] += 1; solve(n)
    end
end

run() = (N0[1] = 8; _n[1] = 1; N2[1] = 0xfe169b4c0a73d852; solve(0))
run()
Output:

next iteration, _n[1] will be 2...
next iteration, _n[1] will be 3...
next iteration, _n[1] will be 4...
next iteration, _n[1] will be 5...
next iteration, _n[1] will be 6...
next iteration, _n[1] will be 7...
next iteration, _n[1] will be 8...
Solution found in 52 moves:
rrrulddluuuldrurdddrullulurrrddldluurddlulurruldrdrd

Lua

Original

#!/usr/bin/lua
--[[
Solve 3X3 sliding tile puzzle using Astar on Lua. Requires 3mb and
millions of recursions.
by RMM  2020-may-1
]]--

local SOLUTION_LIMIT, MAX_F_VALUE = 100,100
local NR, NC, RCSIZE  = 4,4,4*4
local Up, Down, Right, Left = 'u','d','r','l'
local G_cols = {}  -- goal columns
local G_rows = {} -- goal rows
local C_cols = {} -- current cols
local C_rows = {} -- current rows
local Goal = {}
local Tiles = {}		-- the puzzle
local Solution = {}		-- final path is instance of desc
local desc  = {}		-- descending path
desc[0] = 0 -- override Lua default "1" index

-- @brief create C compatible array for Lua
local function Amake( list )
   array = {}
   for i=0, #list do
      array[i] = list[i+1] -- simulate "C" array in Lua
   end
   return array
end

G_cols= Amake({0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0, } )
G_rows= Amake({0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0, } )
C_cols= Amake({0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0, } )
C_rows= Amake({0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0, } )
Tiles = Amake({ 15,14,1,6,  9,11,4,12,  0,10,7,3, 13,8,5,2,} )
Goal  = Amake({1,2,3,4, 5,6,7,8, 9,10,11,12, 13,14,15,0,} )

local m1 = {recursions=0, found=0, threshold=0, times=0, steps=0,}
-- ---------------------------------------------------------------
-- return 1D array index given 2D row,column
local function rcidx( row, col )
   return (row * NR + col)
end

-- @brief recursive search
-- @return f_score
local function search( depth, x_row, x_col, h_score)
   local move, go_back_move, last_move_by_current
   local f_score, ix, min, temp, hscore2, idx1
   local N_minus_one = NR - 1;
   local rc1 = {row=0,col=0}
   local rc2 = {row=0,col=0}
   local gtmp = {row=0,col=0}

   f_score = depth + h_score;
   if f_score > m1.threshold then  return f_score end
   if h_score == 0 then do
      m1.found = 1
      Solution = table.concat(desc)
      return f_score  end end
   
   m1.recursions = m1.recursions+1
   m1.times = m1.times + 1
   if m1.times > 200000 then do
	 print("Recursions ",m1.recursions)
	 m1.times = 0
   end  end
   
   min = 999999
   last_move_by_current = desc[depth];
   rc1.row = x_row;
   rc1.col = x_col;
   for ix = 0,3 do
      if ix==0 then do
	    move = Up;
	    go_back_move = Down;
	    rc2.row = x_row - 1;
	    rc2.col = x_col;
		   end
      elseif ix==1 then do
	    move = Down;
	    go_back_move = Up;
	    rc2.row = x_row + 1;
	    rc2.col = x_col;
		       end
      elseif ix==2 then do
	    move = Left;
	    go_back_move = Right;
	    rc2.row = x_row;
	    rc2.col = x_col - 1;
		       end
      elseif ix==3 then do
	    move = Right;
	    go_back_move = Left;
	    rc2.row = x_row;
	    rc2.col = x_col + 1;
			end end
      if move==Up and x_row==0 then goto next end
      if move==Down and x_row==N_minus_one then goto next end
      if move==Left and x_col==0 then goto next end
      if move==Right and x_col==N_minus_one then goto next end
      if last_move_by_current==go_back_move then goto next end
      hscore2 = h_score
      idx1 = Tiles[rcidx(rc2.row,rc2.col)]
      gtmp.row = G_rows[idx1]
      gtmp.col = G_cols[idx1]
      local h_adj=0
      if go_back_move==Up then do
	    if gtmp.row < C_rows[idx1] then
	       h_adj = -1 else h_adj = 1 end end
      end
      if go_back_move==Down then do
	    if gtmp.row > C_rows[idx1] then
	       h_adj = -1 else h_adj = 1 end end
      end
      if go_back_move==Left then do
	    if gtmp.col < C_cols[idx1] then
	       h_adj = -1 else h_adj = 1 end end
      end
      if go_back_move==Right then do
	    if gtmp.col > C_cols[idx1] then
	       h_adj = -1 else h_adj = 1 end end
      end
      hscore2 = hscore2 + h_adj
      C_rows[0] = rc2.row;
      C_cols[0] = rc2.col;
      C_rows[idx1] = rc1.row;
      C_cols[idx1] = rc1.col; 
      Tiles[rcidx(rc1.row,rc1.col)] = idx1;
      Tiles[rcidx(rc2.row,rc2.col)] = 0;
      desc[depth+1] = move
      desc[depth+2] = '\0'

      temp = search(depth+1, rc2.row, rc2.col, hscore2); -- descend
      -- regress
      Tiles[rcidx(rc1.row,rc1.col)] = 0
      Tiles[rcidx(rc2.row,rc2.col)] = idx1
      desc[depth+1] = '\0'
      C_rows[0] = rc1.row;
      C_cols[0] = rc1.col;
      C_rows[idx1] = rc2.row;
      C_cols[idx1] = rc2.col;
      if m1.found == 1 then return temp end
      if temp < min then  min = temp end
      ::next::  -- Lua does not have "continue;" so uses goto
   end  -- end for
   m1.found = 0;
   -- return the minimum f_score greater than m1.threshold
   return min;
end

-- @brief  Run solver using A-star algorithm
-- @param Tiles .. 3X3 sliding tile puzzle
local function solve(Tiles)
    local temp, i,j
    local x_row, x_col, h_score = 0,0,0

    m1.found = 0;
    for i=0,(NR-1) do
       for j=0, (NC-1) do
	  G_rows[ Goal[rcidx(i,j)] ] = i;
	  G_cols[ Goal[rcidx(i,j)] ] = j;
	  C_rows[ Tiles[rcidx(i,j)] ] = i;
	  C_cols[ Tiles[rcidx(i,j)] ] = j;
       end
    end
    
    for i=0,(NR-1) do
       for j=0, (NC-1) do
	  if Tiles[rcidx(i,j)] == 0 then do
		x_row = i;
		x_col = j;
		break;
					 end
	  end
       end
    end

   desc[0] = '$'
   desc[1] = '\0'

   h_score = 0;   -- Manhattan/Taxicab heuristic
   for  i = 1,(RCSIZE-1) do
      h_score = h_score + (math.abs(C_rows[i] - G_rows[i]) +
		    math.abs(C_cols[i] - G_cols[i]))
   end
   m1.threshold = h_score
   print("solve -> search")   
   while true do
      temp = search( 0, x_row, x_col, h_score);
      if m1.found == 1 then do
	    print("Gound solution of length",#Solution-1)
	    print(Solution)
	    break;
      end end
      
      if temp > MAX_F_VALUE then do
	    print("Maximum f value reached! terminating! \n");
	    break;	 end
      end
      m1.threshold = temp;
   end -- while
   return 0
end

-- show sliding tile puzzle rows and columns
local function print_tiles( pt)
   local i,j,num
    for i=0,(NR-1) do
       for j=0, (NC-1) do
	  num = pt[rcidx(i,j)]
	  io.write(string.format("%02d ", num))
       end
       print("")
    end
    print("")
end

-- main(void) {
print("Solve sliding 15 tile puzzle");
m1.recursions = 0
m1.times=0
print_tiles(Tiles);
print_tiles(Goal)
solve(Tiles);
Output:

FOUND SOLUTION of length 52 rrrulddluuuldrurdddrullulurrrddldluurddlulurruldrdrd

Alternate (with extra credit)

----------
-- SOLVER
----------

local table_concat = table.concat -- local alias

local function Solver(root, h, successors)
  local pathlist, pathhash, iters
  -- it is required that "h(node)" returns:
  --   0 when "is_goal(node)==true"
  --   >0 when "is_goal(node)==false"
  --   (because it allows for some simplification herein)
  local FOUND = 0 -- ie: "is_goal(node)==true"
  local NOT_FOUND = 1e9 -- some number larger than largest possible f

  local function hash(node)
    return table_concat(node,",")
  end

  local function search(g, bound)
    iters = iters + 1
    --if ((iters % 1000000) == 0) then print("iterations:", iters) end
    local node = pathlist[#pathlist]
    local h = h(node)
    local f = g + h
    if (f > bound) then return f end
    if (h == FOUND) then return FOUND end
    local min = NOT_FOUND
    for succ, cost in successors(node) do
      local succhash = hash(succ)
      if (not pathhash[succhash]) then
        pathlist[#pathlist+1], pathhash[succhash] = succ, true
        local t = search(g+cost, bound)
        if (t == FOUND) then return FOUND end
        if (t < min) then min = t end
        pathlist[#pathlist], pathhash[succhash] = nil, nil
      end
    end
    return min
  end

  return {
    solve = function()
      pathlist = { root }
      pathhash = { [hash(root)] = true }
      iters = 0
      local bound = h(root)
      while true do
        bound = search(0, bound)
        if (bound == FOUND) then return bound, iters, pathlist end
        if (bound == NOT_FOUND) then return bound, iters, nil end
      end
    end
  }
end

------------------
-- DOMAIN SUPPORT
------------------

local i2c = { [0]=0, 1,2,3,4, 1,2,3,4, 1,2,3,4, 1,2,3,4 } -- convert index to column
local i2r = { [0]=0, 1,1,1,1, 2,2,2,2, 3,3,3,3, 4,4,4,4 } -- convert index to row
local R, U, L, D = 1, -4, -1, 4 -- move indexing values
local movenames = { -- move names
  [0]="", [R]="r", [U]="u", [L]="l", [D]="d"
}
local succmoves = { -- successor directions
  {R,D}, {R,L,D}, {R,L,D}, {L,D},
  {R,U,D}, {R,U,L,D}, {R,U,L,D}, {U,L,D},
  {R,U,D}, {R,U,L,D}, {R,U,L,D}, {U,L,D},
  {R,U}, {R,U,L}, {R,U,L}, {U,L}
}
local manhdists = { -- manhattan distances
  { [0]=0, 0, 1, 2, 3, 1, 2, 3, 4, 2, 3, 4, 5, 3, 4, 5, 6 },
  { [0]=0, 1, 0, 1, 2, 2, 1, 2, 3, 3, 2, 3, 4, 4, 3, 4, 5 },
  { [0]=0, 2, 1, 0, 1, 3, 2, 1, 2, 4, 3, 2, 3, 5, 4, 3, 4 },
  { [0]=0, 3, 2, 1, 0, 4, 3, 2, 1, 5, 4, 3, 2, 6, 5, 4, 3 },
  { [0]=0, 1, 2, 3, 4, 0, 1, 2, 3, 1, 2, 3, 4, 2, 3, 4, 5 },
  { [0]=0, 2, 1, 2, 3, 1, 0, 1, 2, 2, 1, 2, 3, 3, 2, 3, 4 },
  { [0]=0, 3, 2, 1, 2, 2, 1, 0, 1, 3, 2, 1, 2, 4, 3, 2, 3 },
  { [0]=0, 4, 3, 2, 1, 3, 2, 1, 0, 4, 3, 2, 1, 5, 4, 3, 2 },
  { [0]=0, 2, 3, 4, 5, 1, 2, 3, 4, 0, 1, 2, 3, 1, 2, 3, 4 },
  { [0]=0, 3, 2, 3, 4, 2, 1, 2, 3, 1, 0, 1, 2, 2, 1, 2, 3 },
  { [0]=0, 4, 3, 2, 3, 3, 2, 1, 2, 2, 1, 0, 1, 3, 2, 1, 2 },
  { [0]=0, 5, 4, 3, 2, 4, 3, 2, 1, 3, 2, 1, 0, 4, 3, 2, 1 },
  { [0]=0, 3, 4, 5, 6, 2, 3, 4, 5, 1, 2, 3, 4, 0, 1, 2, 3 },
  { [0]=0, 4, 3, 4, 5, 3, 2, 3, 4, 2, 1, 2, 3, 1, 0, 1, 2 },
  { [0]=0, 5, 4, 3, 4, 4, 3, 2, 3, 3, 2, 1, 2, 2, 1, 0, 1 },
  { [0]=0, 6, 5, 4, 3, 5, 4, 3, 2, 4, 3, 2, 1, 3, 2, 1, 0 },
}

--- create a state from a pattern, optionally applying a move
local function state(patt, move)
  local node = {}
  for k,v in pairs(patt) do node[k] = v end
  if (move) then
    local e = node.e
    local ep = e + move
    node[e], node[ep] = node[ep], 0
    node.e, node.m = ep, move
  end
  return node
end

--- iterator for successors of node
local function successors(node)
  local moves = succmoves[node.e]
  local i, n = 0, #moves
  return function()
    i = i + 1
    if (i <= n) then
      return state(node, moves[i]), 1
    end
  end
end

--- hueristic estimate of travel cost from node to goal
local function h(node)
  local sum, ijx, jix, t = 0, 1, 1
  for i = 1, 4 do
    local colmax, rowmax = 0, 0
    for j = 1, 4 do
      t = node[ijx]
      sum = sum + manhdists[ijx][t] -- manhattan
      if (i2r[t] == i) then -- row conflicts
        if (t > rowmax) then rowmax=t else sum=sum+2 end
      end
      t = node[jix]
      if (i2c[t] == i) then -- col conflicts
        if (t > colmax) then colmax=t else sum=sum+2 end
      end
      ijx, jix = ijx+1, jix+4
    end
    jix = jix - 15
  end
  return sum
end

------------------
-- PRINT SUPPORT:
------------------

local function printnode(node)
  print("+--+--+--+--+")
  for i = 0, 12, 4 do
    print( string.format("|%2d|%2d|%2d|%2d|", node[i+1], node[i+2], node[i+3], node[i+4]) )
    print("+--+--+--+--+")
  end
end

local function printpath(path)
  -- note that #path is 1 longer than solution due to root node at path[1]
  -- concatenated result will be correct length since movenames[root.m]==""
  local t = {}
  for i, node in ipairs(path) do
    t[i] = movenames[node.m]
  end
  local pathstr = table_concat(t)
  print("SOLUTION: " .. pathstr .. " (length: " .. #pathstr .. ")")
end

---------
-- TASKS:
---------

-- goal is implied by h(), never actually used (see solver's notes)
-- local goal = state({ 1,2,3,4, 5,6,7,8, 9,10,11,12, 13,14,15,0, e=16, m=0 })

do
  print("PRIMARY TASK (OPTIMALLY)")
  local sclock = os.clock()
  local root = state({ 15,14,1,6, 9,11,4,12, 0,10,7,3, 13,8,5,2, e=9, m=0 })
  printnode(root)
  local solver = Solver(root, h, successors)
  local bound, iters, path = solver:solve()
  printpath(path)
  printnode(path[#path])
  print("ITERATIONS: " .. iters)
  print("ELAPSED: " .. (os.clock()-sclock) .. "s")
end

print()

do
  print("EXTRA CREDIT TASK (APPROXIMATELY, NON-OPTIMALLY)")
  -- only primary task specifies "fewest possible moves"
  -- extra credit task only specifies "solve", so..
  local sclock = os.clock()
  local root = state({ 0,12,9,13, 15,11,10,14, 3,7,2,5, 4,8,6,1, e=1, m=0 })
  printnode(root)
  local function hec(node)
    -- overweighting h makes it not admissible,
    -- causing solver to favor g when minimizing,
    -- leading to non-optimal (but much easier to find!) solutions
    return h(node)*1.5
  end
  local solver = Solver(root, hec, successors)
  local bound, iters, path = solver:solve()
  printpath(path) --> 86, optimal solution is known to be 80
  printnode(path[#path])
  print("ITERATIONS: " .. iters)
  print("ELAPSED: " .. (os.clock()-sclock) .. "s")
end
Output:
PRIMARY TASK (OPTIMALLY)
+--+--+--+--+
|15|14| 1| 6|
+--+--+--+--+
| 9|11| 4|12|
+--+--+--+--+
| 0|10| 7| 3|
+--+--+--+--+
|13| 8| 5| 2|
+--+--+--+--+
SOLUTION: rrruldluuldrurdddluulurrrdlddruldluurddlulurruldrrdd (length: 52)
+--+--+--+--+
| 1| 2| 3| 4|
+--+--+--+--+
| 5| 6| 7| 8|
+--+--+--+--+
| 9|10|11|12|
+--+--+--+--+
|13|14|15| 0|
+--+--+--+--+
ITERATIONS: 4963696
ELAPSED: 34.279s

EXTRA CREDIT TASK (APPROXIMATELY, NON-OPTIMALLY)
+--+--+--+--+
| 0|12| 9|13|
+--+--+--+--+
|15|11|10|14|
+--+--+--+--+
| 3| 7| 2| 5|
+--+--+--+--+
| 4| 8| 6| 1|
+--+--+--+--+
SOLUTION: rrdldruldluruldrdrrululdrddlluurrddlluurrdrdllurrdlluruurddluurddluulddrdluluruldddrrr (length: 86)
+--+--+--+--+
| 1| 2| 3| 4|
+--+--+--+--+
| 5| 6| 7| 8|
+--+--+--+--+
| 9|10|11|12|
+--+--+--+--+
|13|14|15| 0|
+--+--+--+--+
ITERATIONS: 1325248
ELAPSED: 9.101s

Nim

Translation of: C++

The solver

# 15 puzzle.

import strformat
import times

const
  Nr = [3, 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3]
  Nc = [3, 0, 1, 2, 3, 0, 1, 2, 3, 0, 1, 2, 3, 0, 1, 2]

type

  Solver = object
    n: int
    np: int
    n0: array[100, int]
    n2: array[100, uint64]
    n3: array[100, char]
    n4: array[100, int]

  Value = range[0..15]

# Forward definition.
proc fN(s: var Solver): bool

#---------------------------------------------------------------------------------------------------

proc fI(s: var Solver) =

  let n = s.n
  let g = (11 - s.n0[n]) * 4
  let a = s.n2[n] and uint(15 shl g)
  s.n0[n + 1] = s.n0[n] + 4
  s.n2[n + 1] = s.n2[n] - a + a shl 16
  s.n3[n + 1] = 'd'
  s.n4[n + 1] = s.n4[n] + ord(Nr[a shr g] > s.n0[n] div 4)

#---------------------------------------------------------------------------------------------------

proc fG(s: var Solver) =

  let n = s.n
  let g = (19 - s.n0[n]) * 4
  let a = s.n2[n] and uint(15 shl g)
  s.n0[n + 1] = s.n0[n] - 4
  s.n2[n + 1] = s.n2[n] - a + a shr 16
  s.n3[n + 1] = 'u'
  s.n4[n + 1] = s.n4[n] + ord(Nr[a shr g] < s.n0[n] div 4)

#---------------------------------------------------------------------------------------------------

proc fE(s: var Solver) =

  let n = s.n
  let g = (14 - s.n0[n]) * 4
  let a = s.n2[n] and uint(15 shl g)
  s.n0[n + 1] = s.n0[n] + 1
  s.n2[n + 1] = s.n2[n] - a + a shl 4
  s.n3[n + 1] = 'r'
  s.n4[n + 1] = s.n4[n] + ord(Nc[a shr g] > s.n0[n] mod 4)

#---------------------------------------------------------------------------------------------------

proc fL(s: var Solver) =

  let n = s.n
  let g = (16 - s.n0[n]) * 4
  let a = s.n2[n] and uint(15 shl g)
  s.n0[n + 1] = s.n0[n] - 1
  s.n2[n + 1] = s.n2[n] - a + a shr 4
  s.n3[n + 1] = 'l'
  s.n4[n + 1] = s.n4[n] + ord(Nc[a shr g] < s.n0[n] mod 4)

#---------------------------------------------------------------------------------------------------

proc fY(s: var Solver): bool =

  if s.n2[s.n] == 0x123456789abcdef0'u:
    return true
  if s.n4[s.n] <= s.np:
    return s.fN()

#---------------------------------------------------------------------------------------------------

proc fN(s: var Solver): bool =

  let n = s.n
  if s.n3[n] != 'u' and s.n0[n] div 4 < 3:
    s.fI
    inc s.n
    if s.fY(): return true
    dec s.n
  if s.n3[n] != 'd' and s.n0[n] div 4 > 0:
    s.fG()
    inc s.n
    if s.fY(): return true
    dec s.n
  if s.n3[n] != 'l' and s.n0[n] mod 4 < 3:
    s.fE()
    inc s.n
    if s.fY(): return true
    dec s.n
  if s.n3[n] != 'r' and s.n0[n] mod 4 > 0:
    s.fL()
    inc s.n
    if s.fY(): return true
    dec s.n

#---------------------------------------------------------------------------------------------------

proc initSolver(values: array[16, Value]): Solver {.noInit.} =

  result.n = 0
  result.np = 0
  result.n0[0] = values.find(0)
  result.n2[0] = (var tmp = 0'u; for val in values: tmp = tmp shl 4 or uint(val); tmp)
  result.n4[0] = 0

#---------------------------------------------------------------------------------------------------

proc run(s: var Solver) =

  while not s.fY():
    inc s.np
  stdout.write(fmt"Solution found with {s.n} moves: ")
  for g in 1..s.n:
    stdout.write(s.n3[g])
  stdout.write(".\n")

#---------------------------------------------------------------------------------------------------

proc toString(d: Duration): string =
  # Custom representation of a duration.
  const Plural: array[bool, string] = ["", "s"]
  var ms = d.inMilliseconds
  for (label, d) in {"hour": 3_600_000, "minute": 60_000, "second": 1_000, "millisecond": 1}:
    let val = ms div d
    if val > 0:
      result.add($val & ' ' & label & Plural[val > 1])
      ms = ms mod d
      if ms > 0: result.add(' ')

The task

let start = getTime()
var solver = initSolver([Value 15, 14,  1,  6,
                                9, 11,  4, 12,
                                0, 10,  7,  3,
                               13,  8,  5,  2])
solver.run()
echo fmt"Execution time: {(getTime() - start).toString}."
Output:
Solution found with 52 moves: rrrulddluuuldrurdddrullulurrrddldluurddlulurruldrdrd.
Execution time: 504 milliseconds.

Extra credit

let start = getTime()
var solver = initSolver([Value  0, 12,  9, 13,
                               15, 11, 10, 14,
                                3,  7,  2,  5,
                                4,  8,  6,  1])
solver.run()
echo fmt"Execution time: {(getTime() - start).toString}."
Output:
Solution found with 80 moves: dddrurdruuulllddrulddrrruuullddruulldddrrurulldrruulldlddrurullddrrruullulddrdrr.
Execution time: 3 hours 44 minutes 47 seconds 2 milliseconds.

Pascal

The Solver

unit FifteenSolverT;
\\ Solve 15 Puzzle. Nigel Galloway; February 1st., 2019.
interface
type TN=record n:UInt64; i,g,e,l:shortint; end;
type TG=record found:boolean; path:array[0..99] of TN; end;
function solve15(const board : UInt64; const bPos:shortint; const d:shortint; const ng:shortint):TG;
const endPos:UInt64=$123456789abcdef0;
implementation
const N:array[0..15] of shortint=(3,0,0,0,0,1,1,1,1,2,2,2,2,3,3,3);
const I:array[0..15] of shortint=(3,0,1,2,3,0,1,2,3,0,1,2,3,0,1,2);
const G:array[0..15] of shortint=(5,13,13,9,7,15,15,11,7,15,15,11,6,14,14,10);
const E:array[0..15] of shortint=(0,1,2,2,3,3,3,3,4,4,4,4,4,4,4,4);
const L:array[0..4 ] of shortint=(0,11,19,14,16);
function solve15(const board:UInt64; const bPos:shortint; const d:shortint; const ng:shortint):TG;
var path:TG; P:^TN; Q:^TN; _g:shortint; _n:UInt64;
begin P:=@path.path; P^.n:=board; P^.i:=0; P^.g:=0; P^.e:=ng; P^.l:=bPos;
  while true do begin
    if P<@path.path then begin path.found:=false; exit(path); end;
    if P^.n=endPos  then begin path.found:=true; exit(path); end;
    if (P^.e=0) or (P^.i>d) then begin P-=1; continue; end else begin Q:=P+1; Q^.g:=E[P^.e]; end;
    Q^.i:=P^.i; _g:=(L[Q^.g]-P^.l)*4; _n:=P^.n and (UInt64($F)<<_g);
    case Q^.g of
      1:begin Q^.l:=P^.l+4; Q^.e:=G[Q^.l]-2; P^.e-=1; Q^.n:=P^.n-_n+(_n<<16); if N[_n>>_g]>=(Q^.l div 4) then Q^.i+=1; end;
      2:begin Q^.l:=P^.l-4; Q^.e:=G[Q^.l]-1; P^.e-=2; Q^.n:=P^.n-_n+(_n>>16); if N[_n>>_g]<=(Q^.l div 4) then Q^.i+=1; end;
      3:begin Q^.l:=P^.l+1; Q^.e:=G[Q^.l]-8; P^.e-=4; Q^.n:=P^.n-_n+(_n<< 4); if I[_n>>_g]>=(Q^.l mod 4) then Q^.i+=1; end;
      4:begin Q^.l:=P^.l-1; Q^.e:=G[Q^.l]-4; P^.e-=8; Q^.n:=P^.n-_n+(_n>> 4); if I[_n>>_g]<=(Q^.l mod 4) then Q^.i+=1; end;
    end;
    P+=1;
  end;
end;
end.
// Threaded use of 15 solver Unit. Nigel Galloway; February 1st., 2019.
program testFifteenSolver;
uses {$IFDEF UNIX}cthreads,{$ENDIF}sysutils,strutils,FifteenSolverT;
var Tz:array[0..5] of TThreadID; Tr:array[0..5] of TG; Tc:array[0..5] of shortint; Tw:array[0..5] of shortint;
const N:array[0..4 ] of string=('','d','u','r','l');
const G:array[0..15] of string=('41','841','841','81','421','8421','8421','821','421','8421','8421','821','42','842','842','82');
var ip:string; x,y:UInt64; P,Q:^TN; bPos,v,w,z,f:shortint; time1, time2: TDateTime; c:char;
function T(a:pointer):ptrint;
begin
  Tr[uint32(a)]:=solve15(x,bPos,Tw[uint32(a)],Tc[uint32(a)]);
  if Tr[uint32(a)].found then f:=uint32(a);
  T:=0;
end;
begin
  ReadLn(ip);
  bPos:=Npos('0',ip,1)-1; w:=0; z:=0; f:=-1;
  y:=(UInt64(Hex2Dec(ip[9..17]))<<32)>>32; x:=UInt64(Hex2Dec(ip[1..8]))<<32+y;
  time1:=Now;
  for w:=0 to $7f do begin
    for c in G[bpos] do begin v:=z mod 6; Tc[v]:=integer(c)-48; Tw[v]:=w;
      Tz[v]:=BeginThread(@T,pointer(v));
      z+=1; if z>5 then waitforthreadterminate(Tz[z mod 6],$7fff);
    end;
    if f>=0 then break;
  end;
  for bpos:=0 to 5 do if Tw[bpos]>=Tw[f] then killthread(Tz[bpos]) else waitforthreadterminate(Tz[bpos],$7fff);
  time2:=Now; WriteLn('Solution(s) found in ' +  FormatDateTime('hh.mm.ss.zzz', time2-time1) + ' seconds');
  for bpos:=0 to 5 do if Tr[bpos].found then begin
    P:=@Tr[bpos].path; repeat Q:=P; Write(N[Q^.g]); P+=1; until Q^.n=endpos; WriteLn();
  end;
end.

The Task

Output:
fe169b4c0a73d852
Solution(s) found in 00.00.00.423 seconds
rrruldluuldrurdddluulurrrdlddruldluurddlulurruldrrdd

Extra Credit

Output:
0c9dfbae37254861
Solution(s) found in 12.58.46.194 seconds
rrrdldlluurrddluurrdlurdddllluurrdrdlluluurrdddluurddlluururdrddlluurrullldrrrdd
drrrdlluurrdddlluururddlurddlulldrrulluurrdrdllluurrdrdllldruururdddlluluurrrddd

Picat

import planner.

main =>
    init(InitS),
    goal(GoalS),
    best_plan((InitS,GoalS),Plan),
    println(Plan).

init(InitS) =>
    M = {{15, 14, 1,  6},
         {9 , 11, 4, 12},
         {0,  10, 7,  3},
         {13,  8, 5,  2}},
    InitS = [(R,C) : T in 0..15, pos(M,T,R,C)].

goal(GoalS) =>
    M = {{1,  2,  3,  4},
         {5,  6,  7,  8},
         {9, 10, 11, 12},
         {13,14, 15,  0}},
    GoalS = [(R,C) : T in 0..15, pos(M,T,R,C)].         

pos(M,T,R,C) =>
    N = len(M),
    between(1,N,R),
    between(1,N,C),
    M[R,C] == T,!.
    
final((S,GoalS)) => S == GoalS.

action((S,GoalS),NextS,Action,Cost) =>
    S = [P0|Tiles],
    P0 = (R0,C0),
    Cost = 1,
    (R1 = R0-1, R1 >= 1, C1 = C0, Action = u;
     R1 = R0+1, R1 =< 4, C1 = C0, Action = d;
     R1 = R0, C1 = C0-1, C1 >= 1, Action = l;
     R1 = R0, C1 = C0+1, C1 =< 4, Action = r),
    P1 = (R1,C1),
    slide(P0,P1,Tiles,Tiles1),
    S1 = [P1|Tiles1],
    NextS = (S1,GoalS).

% slide the tile at P1 to the empty square at P0
slide(P0,P1,[P1|Tiles],Tiles1) =>
    Tiles1 = [P0|Tiles].
slide(P0,P1,[Tile|Tiles],Tiles1) =>
    Tiles1=[Tile|Tiles1R],
    slide(P0,P1,Tiles,Tiles1R).

% called by the planner
heuristic((S,GoalS)) = Dist =>
    S = [_|Tiles],
    GoalS = [_|FTiles],
    Dist = sum([abs(R-FR)+abs(C-FC) : 
                {(R,C),(FR,FC)} in zip(Tiles,FTiles)]).
Output:
rrrulddluuuldrurdddrullulurrrddldluurddlulurruldrdrd  

Perl

Translation of: Raku
use strict;
no warnings;

use enum qw(False True);
use constant Nr => <3 0 0 0 0 1 1 1 1 2 2 2 2 3 3 3>;
use constant Nc => <3 0 1 2 3 0 1 2 3 0 1 2 3 0 1 2>;

my ($n, $m) = (0, 0);
my(@N0, @N2, @N3, @N4);

sub fY {
   printf "Solution found in $n moves: %s\n", join('', @N3) and exit if $N2[$n] == 0x123456789abcdef0;
   $N4[$n] <= $m ? fN() : False;
}

sub fN {
   sub common { ++$n; return True if fY(); --$n }
   if ($N3[$n] ne 'u' and int($N0[$n] / 4) < 3) { fI(); common() }
   if ($N3[$n] ne 'd' and int($N0[$n] / 4) > 0) { fG(); common() }
   if ($N3[$n] ne 'l' and    ($N0[$n] % 4) < 3) { fE(); common() }
   if ($N3[$n] ne 'r' and    ($N0[$n] % 4) > 0) { fL(); common() }
   return False;
}

sub fI {
   my $g = (11-$N0[$n])*4;
   my $a = $N2[$n] & (15 << $g);
   $N0[$n+1] = $N0[$n]+4;
   $N2[$n+1] = $N2[$n]-$a+($a<<16);
   $N4[$n+1] = $N4[$n]+((Nr)[$a>>$g] <= int($N0[$n] / 4) ? 0 : 1);
   $N3[$n+1] = 'd';
}

sub fG {
   my $g = (19-$N0[$n])*4;
   my $a = $N2[$n] & (15 << $g);
   $N0[$n+1] = $N0[$n]-4;
   $N2[$n+1] = $N2[$n]-$a+($a>>16);
   $N4[$n+1] = $N4[$n]+((Nr)[$a>>$g] >= int($N0[$n] / 4) ? 0 : 1);
   $N3[$n+1] = 'u';
}

sub fE {
   my $g = (14-$N0[$n])*4;
   my $a = $N2[$n] & (15 << $g);
   $N0[$n+1] = $N0[$n]+1;
   $N2[$n+1] = $N2[$n]-$a+($a<<4);
   $N4[$n+1] = $N4[$n]+((Nc)[$a>>$g] <= $N0[$n]%4 ? 0 : 1);
   $N3[$n+1] = 'r';
}

sub fL {
   my $g = (16-$N0[$n])*4;
   my $a = $N2[$n] & (15 << $g);
   $N0[$n+1] = $N0[$n]-1;
   $N2[$n+1] = $N2[$n]-$a+($a>>4);
   $N4[$n+1] = $N4[$n]+((Nc)[$a>>$g] >= $N0[$n]%4 ? 0 : 1);
   $N3[$n+1] = 'l';
}

($N0[0], $N2[0]) = (8, 0xfe169b4c0a73d852); # initial state
while () { fY() or ++$m }
Output:
Solution found in 52 moves: rrrulddluuuldrurdddrullulurrrddldluurddlulurruldrdrd

Phix

-- demo\rosetta\Solve15puzzle.exw
constant STM = 0    -- single-tile metrics.
constant MTM = 0    -- multi-tile metrics.
if STM and MTM then ?9/0 end if -- both prohibited
--  0       0   -- fastest, but non-optimal
--  1       0   -- optimal in STM
--  0       1   -- optimal in MTM (slowest by far)
 
--Note: The fast method uses an inadmissible heuristic - see "not STM" in iddfs().
--      It explores mtm-style using the higher stm heuristic and may therefore 
--      fail badly in some cases.
 
constant SIZE = 4
 
constant goal = { 1, 2, 3, 4,
                  5, 6, 7, 8,
                  9,10,11,12,
                 13,14,15, 0}
 
--
-- multi-tile-metric walking distance heuristic lookup (mmwd).
-- ==========================================================
-- Uses patterns of counts of tiles in/from row/col, eg the solved state 
--  (ie goal above) could be represented by the following:
--      {{4,0,0,0},
--       {0,4,0,0},
--       {0,0,4,0},
--       {0,0,0,3}}
--  ie row/col 1 contains 4 tiles from col/row 1, etc. In this case
--  both are identical, but you can count row/col or col/row, and then
--  add them together. There are up to 24964 possible patterns. The
--  blank space is not counted. Note that a vertical move cannot change
--  a vertical pattern, ditto horizontal, and basic symmetry means that
--  row/col and col/row patterns will match (at least, that is, if they 
--  are calculated sympathetically), halving the setup cost.
-- The data is just the number of moves made before this pattern was
--  first encountered, in a breadth-first search, backwards from the
--  goal state, until all patterns have been enumerated.
-- (The same ideas/vars are now also used for stm metrics when MTM=0)
--
sequence wdkey              -- one such 4x4 pattern
constant mmwd = new_dict()  -- lookup table, data is walking distance.
 
 
--
-- We use two to-do lists: todo is the current list, and everything
-- of walkingdistance+1 ends up on tdnx. Once todo is exhausted, we
-- swap the dictionary-ids, so tdnx automatically becomes empty.
-- Key is an mmwd pattern as above, and data is {distance,space_idx}.
--
integer todo = new_dict()
integer tdnx = new_dict()
 
--
 
enum UP = 1, DOWN = -1
 
procedure explore(integer space_idx, walking_distance, direction)
--
--  Given a space index, explore all the possible moves in direction,
--  setting the distance and extending the tdnx table.
--
integer tile_idx = space_idx+direction
    for group=1 to SIZE do
        if wdkey[tile_idx][group] then
            -- ie: check row tile_idx for tiles belonging to rows 1..4
            -- Swap one of those tiles with the space
            wdkey[tile_idx][group] -= 1
            wdkey[space_idx][group] += 1
 
            if getd_index(wdkey,mmwd)=0 then
                -- save the walking distance value
                setd(wdkey,walking_distance+1,mmwd)
                -- and add to the todo next list:
                if getd_index(wdkey,tdnx)!=0 then ?9/0 end if
                setd(wdkey,{walking_distance+1,tile_idx},tdnx)
            end if
 
if MTM then
            if tile_idx>1 and tile_idx<SIZE then
                -- mtm: same direction means same distance:
                explore(tile_idx, walking_distance, direction)
            end if
end if
 
            -- Revert the swap so we can look at the next candidate.
            wdkey[tile_idx][group] += 1
            wdkey[space_idx][group] -= 1
        end if
    end for
end procedure
 
procedure generate_mmwd()
-- Perform a breadth-first search begining with the solved puzzle state 
--  and exploring from there until no more new patterns emerge.
integer walking_distance = 0, space = 4
 
    wdkey = {{4,0,0,0}, -- \
             {0,4,0,0}, --  } 4 tiles in correct row positions
             {0,0,4,0}, -- /
             {0,0,0,3}} --    3 tiles in correct row position
    setd(wdkey,walking_distance,mmwd)
    while 1 do
        if space<4 then explore(space, walking_distance, UP)    end if
        if space>1 then explore(space, walking_distance, DOWN) end if
        if dict_size(todo)=0 then
            if dict_size(tdnx)=0 then exit end if
            {todo,tdnx} = {tdnx,todo}
        end if
        wdkey = getd_partial_key(0,todo)
        {walking_distance,space} = getd(wdkey,todo)
        deld(wdkey,todo)
    end while
end procedure
 
function walking_distance(sequence puzzle)
sequence rkey = repeat(repeat(0,SIZE),SIZE),
         ckey = repeat(repeat(0,SIZE),SIZE)
    integer k = 1
    for i=1 to SIZE do  -- rows
        for j=1 to SIZE do  -- columns
            integer tile = puzzle[k]
            if tile!=0 then
                integer row = floor((tile-1)/4)+1,
                        col = mod(tile-1,4)+1
                rkey[i][row] += 1
                ckey[j][col] += 1
            end if
            k += 1
        end for
    end for
    if getd_index(rkey,mmwd)=0
    or getd_index(ckey,mmwd)=0 then
        ?9/0 -- sanity check
    end if
    integer rwd = getd(rkey,mmwd),
            cwd = getd(ckey,mmwd)
    return rwd+cwd
end function
 
sequence puzzle
string res = ""
atom t0 = time(),
     t1 = time()+1
atom tries = 0
 
constant ok = {{0,1,1,1,0,1,1,1,0,1,1,1,0,1,1,1},   -- left
               {0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1},   -- up
               {1,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0},   -- down
               {1,1,1,0,1,1,1,0,1,1,1,0,1,1,1,0}}   -- right
function iddfs(integer step, lim, space, prevmv)
    if time()>t1 then
        printf(1,"working... (depth=%d, tries=%d, time=%3ds)\r",{lim,tries,time()-t0})
        t1 = time()+1
    end if
    tries += 1
    integer d = iff(step==lim?0:walking_distance(puzzle))
    if d=0 then
 
        return (puzzle==goal)
 
    elsif step+d<=lim then
 
        for mv=1 to 4 do -- l/u/d/r
            if prevmv!=(5-mv) -- not l after r or vice versa, ditto u/d
            and ok[mv][space] then
                integer nspace = space+{-1,-4,+4,+1}[mv]
                integer tile = puzzle[nspace]
                if puzzle[space]!=0 then ?9/0 end if    -- sanity check     
                puzzle[space] = tile
                puzzle[nspace] = 0
                if iddfs(step+iff(MTM or not STM?(prevmv!=mv):1),lim,nspace,mv) then
                    res &= "ludr"[mv]
                    return true
                end if
                puzzle[nspace] = tile
                puzzle[space] = 0
            end if
        end for
    end if
    return false
end function
 
function pack(string s)
integer n = length(s), n0 = n
    for i=1 to 4 do
        integer ch = "lrud"[i], k
        while 1 do
            k = match(repeat(ch,3),s)
            if k=0 then exit end if
            s[k+1..k+2] = "3"
            n -= 2
        end while
        while 1 do
            k = match(repeat(ch,2),s)
            if k=0 then exit end if
            s[k+1] = '2'
            n -= 1
        end while
    end for
    return {n,iff(MTM?sprintf("%d",n):sprintf("%d(%d)",{n,n0})),s}
end function
 
procedure apply_moves(string moves, integer space)
integer move, ch, nspace
    puzzle[space] = 0
    for i=1 to length(moves) do
        ch = moves[i]
        if ch>'3' then
            move = find(ch,"ulrd")
        end if
        -- (hint: "r" -> the 'r' does 1
        --        "r2" -> the 'r' does 1, the '2' does 1
        --        "r3" -> the 'r' does 1, the '3' does 2!)
        for j=1 to 1+(ch='3') do
            nspace = space+{-4,-1,+1,4}[move]
            puzzle[space] = puzzle[nspace]
            space = nspace
            puzzle[nspace] = 0
        end for
    end for
end procedure
 
function solvable(sequence board)
integer n = length(board)
sequence positions = repeat(0,n)
    -- prepare the mapping from each tile to its position
    board[find(0,board)] = n
    for i=1 to n do
        positions[board[i]] = i
    end for
 
    -- check whether this is an even or odd state
    integer row = floor((positions[16]-1)/4),
            col = (positions[16]-1)-row*4
    bool even_state = (positions[16]==16) or (mod(row,2)==mod(col,2))
 
    -- count the even cycles
    integer even_count = 0
    sequence visited = repeat(false,16)
    for i=1 to n do
        if not visited[i] then
            -- a new cycle starts at i. Count its length..
            integer cycle_length = 0,
                    next_tile = i
            while not visited[next_tile] do
                cycle_length +=1
                visited[next_tile] = true
                next_tile = positions[next_tile]
            end while
            even_count += (mod(cycle_length,2)==0)
        end if
    end for
    return even_state == (mod(even_count,2)==0)
end function
 
procedure main()
 
    puzzle = {15,14, 1, 6, 
               9,11, 4,12,
               0,10, 7, 3, 
              13, 8, 5, 2}
 
    if not solvable(puzzle) then
        ?puzzle
        printf(1,"puzzle is not solveable\n")
    else
 
        generate_mmwd()
 
        sequence original = puzzle
        integer space = find(0,puzzle)
 
        for lim=walking_distance(puzzle) to iff(MTM?43:80) do
            if iddfs(0, lim, space, '-') then exit end if
        end for
 
        {integer n, string ns, string ans} = pack(reverse(res))
 
        printf(1,"\n\noriginal:")
        ?original
        atom t = time()-t0
        printf(1,"\n%soptimal solution of %s moves found in %s: %s\n\nresult: ",
                 {iff(MTM?"mtm-":iff(STM?"stm-":"non-")),ns,elapsed(t),ans})
        puzzle = original
        apply_moves(ans,space)
        ?puzzle
    end if
end procedure
main()
Output:
original:{15,14,1,6,9,11,4,12,0,10,7,3,13,8,5,2}
non-optimal solution of 35(60) moves found in 2.42s: u2r2d3ru2ld2ru3ld3l2u3r2d2l2dru2ldru2rd3lulur3dl2ur2d2
stm-optimal solution of 38(52) moves found in 1 minute and 54s: r3uldlu2ldrurd3lu2lur3dld2ruldlu2rd2lulur2uldr2d2
mtm-optimal solution of 31(60) moves found in 2 hours, 38 minutes and 28s: u2r2d3ru2ld2ru3ld3l2u3r2d2l2dru3rd3l2u2r3dl3dru2r2d2

simpler, better

The basic algorithm (credit Nigel Galloway) is just

let all moves which do not increase the manhattan cost be regarded as "free".
   -- (if you can solve in mc, clearly it is an optimal solution)
let n=0
while (not solveable_with_at_most_n_non_free_moves(n)) n++
   -- (clearly optimal by exhaustively disproving all lesser n)
-- demo/rosetta/Solve15puzzle_simple.exw
enum left, down, up, right  -- (nb 5-move flips it, as in down===5-up, etc)
 
constant valid_moves = {{ 0, 0, 5, 2}, { 1, 0, 6, 3}, { 2, 0, 7, 4}, { 3, 0, 8, 0},
                        { 0, 1, 9, 6}, { 5, 2,10, 7}, { 6, 3,11, 8}, { 7, 4,12, 0},
                        { 0, 5,13,10}, { 9, 6,14,11}, {10, 7,15,12}, {11, 8,16, 0},
                        { 0, 9, 0,14}, {13,10, 0,15}, {14,11, 0,16}, {15,12, 0, 0}}
 
constant zero_cost = {{0b000000000000000,0b000000000000000,0b000000000001111,0b001000100010001},
                      {0b110111011101110,0b000000000000000,0b000000000001111,0b011001100110011},
                      {0b100110011001100,0b000000000000000,0b000000000001111,0b111011101110111},
                      {0b000100010001000,0b000000000000000,0b000000000001111,0b000000000000000},
                      {0b000000000000000,0b111111111110000,0b000000011111111,0b001000100010001},
                      {0b110111011101110,0b111111111110000,0b000000011111111,0b011001100110011},
                      {0b100110011001100,0b111111111110000,0b000000011111111,0b111011101110111},
                      {0b000100010001000,0b111111111110000,0b000000011111111,0b000000000000000},
                      {0b000000000000000,0b111111100000000,0b000111111111111,0b001000100010001},
                      {0b110111011101110,0b111111100000000,0b000111111111111,0b011001100110011},
                      {0b100110011001100,0b111111100000000,0b000111111111111,0b111011101110111},
                      {0b000100010001000,0b111111100000000,0b000111111111111,0b000000000000000},
                      {0b000000000000000,0b111000000000000,0b000000000000000,0b001000100010001},
                      {0b110111011101110,0b111000000000000,0b000000000000000,0b011001100110011},
                      {0b100110011001100,0b111000000000000,0b000000000000000,0b111011101110111},
                      {0b000100010001000,0b111000000000000,0b000000000000000,0b000000000000000}}
 
constant masks      = {0b000000000000001,0b000000000000010,0b000000000000100,0b000000000001000,
                       0b000000000010000,0b000000000100000,0b000000001000000,0b000000010000000,
                       0b000000100000000,0b000001000000000,0b000010000000000,0b000100000000000,
                       0b001000000000000,0b010000000000000,0b100000000000000}
 
-- Or, if you prefer to build those with code (but I really wanted to show the above bitmasks):
--/*
sequence valid_moves = repeat(repeat(0,4),16),
         zero_cost   = repeat(repeat(0,4),16)
constant masks = sq_power(2,tagset(14,0))
for square=1 to 16 do
    integer s_row = floor((square+3)/4),
            s_col = remainder((square-1),4)+1
    for move=left to right do -- (via up/down)
        if (move=left  and s_col>1)
        or (move=down  and s_row>1)
        or (move=up    and s_row<4)
        or (move=right and s_col<4) then
            integer origin = square+{-1,-4,+4,+1}[move],
                    o_row = floor((origin+3)/4),
                    o_col = remainder((origin-1),4)+1
            valid_moves[square][move] = origin
            for piece=1 to 15 do -- (aka target)
                integer t_row = floor((piece+3)/4),
                        t_col = remainder((piece-1),4)+1,
                        p_md = abs(t_row-o_row)+abs(t_col-o_col),
                        n_md = abs(t_row-s_row)+abs(t_col-s_col)
                if n_md<=p_md then
                    zero_cost[square][move] += masks[piece]
                end if
            end for
        end if
    end for
end for
--pp(valid_moves,{pp_IntFmt,"%2d",pp_Maxlen,70})
--pp(zero_cost,{pp_IntFmt,"%015b"})
--pp(masks,{pp_IntFmt,"%015b",pp_IntCh,false})
--*/
if up or down then end if -- (suppress unused warnings, since the above commented out)
 
string moves = ""
sequence board = {15,14, 1, 6, 
                   9,11, 4,12,
                   0,10, 7, 3, 
                  13, 8, 5, 2}
integer space = 9
 
constant goal = { 1, 2, 3, 4,
                  5, 6, 7, 8,
                  9,10,11,12,
                 13,14,15, 0}
 
atom t1 = time()+1
 
function solve(integer nfree, space, mdx=1, skip_move=0)
--
-- nfree is the number of non-free moves we can yet make
-- space is the location of the space (duh), [1..16] 
-- mdx is just the move index for building the solution
-- skip_move significantly narrows search space (1000 or 
--  more times faster, believe it or not, simply by not 
--  allowing the immediate undoing of the last move)
--
    for move=left to right do -- (via up/down)
        integer new_space = valid_moves[space][move]
        if move!=skip_move and new_space then
            integer piece = board[new_space],
                    zcsmv = zero_cost[space][move],
                    maskp = masks[piece],
                    zcost = (and_bits(zcsmv,maskp)=0) -- (0==free, 1==not)
            if nfree>=zcost then
                if mdx>length(moves) then moves &= '?' end if
--              moves[mdx] = "ludr"[move]
                moves[mdx] = "ludrLUDR"[move+zcost*4]
                board[space] = piece
                board[new_space] = 0
                if time()>t1 then
                    printf(1,"%s\r",{moves})
                    t1 = time()+1
                end if
                if space=piece and board=goal then
                    moves = moves[1..mdx] -- (trim)
                    return true
                end if
                if solve(nfree-zcost,new_space,mdx+1,5-move) then
                    return true
                end if
                board[new_space] = piece
                board[space] = 0
            end if
        end if
    end for
    return false
end function
 
pp(board,{pp_IntFmt,"%2d",pp_Maxlen,17})
 
atom t0 = time()
integer n = 0
while not solve(n,space) do n += 1 end while
printf(1,"solution of %d moves found in %s: %s\n",
         {length(moves),elapsed(time()-t0),moves})
Output:

uppercase indicates the non-free moves (in manhattan cost terms).

{15,14, 1, 6,
  9,11, 4,12,
  0,10, 7, 3,
 13, 8, 5, 2}
solution of 52 moves found in 8.5s: RRruldluulDRurdddlUulurRrdLddrUldluurddlulurruldrrdd

Extrapolating from 0.5s/4hrs of C++, as-is this would probably take at least 3 days to solve the extra credit...
You could probably make solve() iterative rather than recursive, and then go all full-on-inline-assembly on it...

PowerBASIC

Works with PowerBASIC 6 Console Compiler

Translation of: Go
' The program solve fe169b4c0a73d852 in 4-5 seconds (on Intel Core i7-3770 3.40 GHz, 16 GB RAM, Windows 10 Pro).
' Test not completed with 0c9dfbae37254861 (still running after about 4 hours).
' Most of initialization is done in procedure fifteenSolver(), so it's possible to call it many times from the main function.
' No need to pass to fifteenSolver() the initial position of 0; the procedure determines it.
' Program includes procedure to create new configurations (by shuffling the correct final configuration).
' Program also includes a simple (text only) optional visualization of solving moves; the second (optional) parameter of fifteenSolver() is the pause between each move (in milliseconds).
'
' PowerBASIC compilers (both PBCC and PBWin) are 32 bits and are not that efficient when dealing with 64 bit integers (quad, only signed);
' this is not a problem for additions and subtractions, but left/right shift are quite slow;
' to speed up execution, left/right shift are done by inline X86 assembler (the PB statement is commented).

#compiler pbcc
#dim all

global Nr() as long
global Nc() as long
global n as long
global nn as long ' variable name _n not allowed
global N0() as long
global N3() as long
global N4() as long
global N2() as quad

%Ki=1
%Ke=2
%Kl=4
%Kg=8

%l=108 ' l
%r=114 ' r
%u=117 ' u
%d=100 ' d

function fY() as long
   if N2(n) = &h123456789abcdef0 then
      function=1
      exit function
   end if
   if N4(n) <= nn then
      function = fN()
      exit function
   end if
   function = 0
end function

function fZ(byval w as long) as long
   if (w and %Ki) > 0 then
      call fI()
      if fY() then
         function = 1
         exit function
      end if
      decr n
   end if
   if (w and %Kg) > 0 then
      call fG()
      if fY() then
         function = 1
         exit function
      end if
      decr n
   end if
   if (w and %Ke) > 0 then
      call fE()
      if fY() then
         function = 1
         exit function
      end if
      decr n
   end if
   if (w and %Kl) > 0 then
      call fL()
      if fY() then
         function = 1
         exit function
      end if
      decr n
   end if
   function = 0
end function

function fN() as long
   select case N0(n)
      case 0
         select case N3(n)
            case %l
               function = fZ(%Ki)
               exit function
            case %u
               function = fZ(%Ke)
               exit function
            case else
               function = fZ(%Ki + %Ke)
               exit function
         end select
      case 3
         select case N3(n)
            case %r
               function = fZ(%Ki)
               exit function
            case %u
               function = fZ(%Kl)
               exit function
            case else
               function = fZ(%Ki + %Kl)
               exit function
         end select
      case 1, 2
         select case N3(n)
            case %l
               function = fZ(%Ki + %Kl)
               exit function
            case %r
               function = fZ(%Ki + %Ke)
               exit function
            case %u
               function = fZ(%Ke + %Kl)
               exit function
            case else
               function = fZ(%Kl + %Ke + %Ki)
               exit function
         end select
      case 12
         select case N3(n)
            case %l
               function = fZ(%Kg)
               exit function
            case %d
               function = fZ(%Ke)
               exit function
            case else
               function = fZ(%Ke + %Kg)
               exit function
         end select
      case 15
         select case N3(n)
            case %r
               function = fZ(%Kg)
               exit function
            case %d
               function = fZ(%Kl)
               exit function
            case else
               function = fZ(%Kg + %Kl)
               exit function
         end select
      case 13, 14
         select case N3(n)
            case %l
               function = fZ(%Kg + %Kl)
               exit function
            case %r
               function = fZ(%Ke + %Kg)
               exit function
            case %d
               function = fZ(%Ke + %Kl)
               exit function
            case else
               function = fZ(%Kg + %Ke + %Kl)
               exit function
         end select
      case 4, 8
         select case N3(n)
            case %l
               function = fZ(%Ki + %Kg)
               exit function
            case %u
               function = fZ(%Kg + %Ke)
               exit function
            case %d
               function = fZ(%Ki + %Ke)
               exit function
            case else
               function = fZ(%Ki + %Kg + %Ke)
               exit function
         end select
      case 7, 11
         select case N3(n)
            case %d
               function = fZ(%Ki + %Kl)
               exit function
            case %u
               function = fZ(%Kg + %Kl)
               exit function
            case %r
               function = fZ(%Ki + %Kg)
               exit function
            case else
               function = fZ(%Ki + %Kg + %Kl)
               exit function
         end select
      case else
         select case N3(n)
            case %d
               function = fZ(%Ki + %Ke + %Kl)
               exit function
            case %l
               function = fZ(%Ki + %Kg + %Kl)
               exit function
            case %r
               function = fZ(%Ki + %Kg + %Ke)
               exit function
            case %u
               function = fZ(%Kg + %Ke + %Kl)
               exit function
            case else
               function = fZ(%Ki + %Kg + %Ke + %Kl)
               exit function
         end select
   end select
end function

sub fI()
   local g as long
   local a as quad
   g = (11 - N0(n)) * 4
   a = (N2(n) and lshift(15,g))
   N0(n + 1) = N0(n) + 4
   N2(n + 1) = N2(n) - a + lshift(a, 16)
   N3(n + 1) = %d
   N4(n + 1) = N4(n)
   if isfalse(Nr(rshift(a,g)) <= N0(n)\4) then
      incr N4(n + 1)
   end if
   incr n
end sub

sub fG()
   local g as long
   local a as quad
   g = (19 - N0(n)) * 4
   a = (N2(n) and lshift(15,g))
   N0(n + 1) = N0(n) - 4
   N2(n + 1) = N2(n) - a + rshift(a, 16)
   N3(n + 1) = %u
   N4(n + 1) = N4(n)
   if isfalse(Nr(rshift(a,g)) >= N0(n)\4) then
      incr N4(n + 1)
   end if
   incr n
end sub

sub fE()
   local g as long
   local a as quad
   g = (14 - N0(n)) * 4
   a = (N2(n) and lshift(15,g))
   N0(n + 1) = N0(n) + 1
   N2(n + 1) = N2(n) - a + lshift(a,4)
   N3(n + 1) = %r
   N4(n + 1) = N4(n)
   if isfalse(Nc(rshift(a,g)) <= (N0(n) mod 4)) then
      incr N4(n + 1)
   end if
   incr n
end sub

sub fL()
   local g as long
   local a as quad
   g = (16 - N0(n)) * 4
   a = (N2(n) and lshift(15,g))
   N0(n + 1) = N0(n) - 1
   N2(n + 1) = N2(n) - a + rshift(a,4)
   N3(n + 1) = %l
   N4(n + 1) = N4(n)
   if isfalse(Nc(rshift(a,g)) >= (N0(n) mod 4)) then
      incr N4(n + 1)
   end if
   incr n
end sub

function lshift(byval v as quad, byval s as dword) as quad
   'shift left v, s
   ' inline assembler shift is much faster
   !mov edx,v[4]
   !mov eax,v
   !mov ecx,s
   !shld edx,eax,cl
   !shl eax,cl
   !test ecx,32
   !jz skip
   !mov edx,eax
   !xor eax,eax
   skip:
   !mov v[4],edx
   !mov v,eax
   function = v
end function

function rshift(byval v as quad, byval s as dword) as quad
   'shift right v, s
   ' inline assembler shift is much faster
   !mov edx,v[4]
   !mov eax,v
   !mov ecx,s
   !shrd eax,edx,cl
   !shr edx,cl
   !test ecx,32
   !jz skip
   !mov eax,edx
   !xor edx,edx
   skip:
   !mov v[4],edx
   !mov v,eax
   function = v
end function

sub fifteenSolver(byval g as quad, optional byval p as long)
   local h as string
   local j as long
   local s as string
   local t as single
   t=timer
   redim Nr(0 to 15)
   redim Nc(0 to 15)
   redim N0(0 to 99)
   redim N3(0 to 100)
   redim N4(0 to 99)
   redim N2(0 to 99)
   array assign Nr()=3, 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3
   array assign Nc()=3, 0, 1, 2, 3, 0, 1, 2, 3, 0, 1, 2, 3, 0, 1, 2
   n = 0
   nn = 0
   h = hex$(g, 16)
   cls
   print "Puzzle: ";lcase$(h)
   call ShowConfiguration(h, 2)
   print
   print
   N0(0) = instr(h, "0") - 1
   N2(0) = g
   call solve()
   print using$("Solution found in #### moves: ", n);
   for j = 1 to n
      s = s + chr$(N3(j))
   next
   print s
   print "Time = ";format$(timer-t, "#####.########");" seconds"
   if p then
      call showMoves(h, s, n, p)
   end if
end sub

sub solve()
   if fN() then
      exit sub
   else
      n = 0
      incr nn
      call solve()
   end if
end sub

function createPuzzle(byval j as long) as quad
   local q as quad
   local h as string
   local z as long
   local d as long
   local r as long
   local u as long
   randomize timer
   q=&h123456789abcdef0
   h = hex$(q, 16)
   u = 0
   while j > 0 ' number of moves to do
      do
         d = rnd(1, 4) ' -1 +1 -4 +4
      loop while d = u
      u = -d
      r = rnd(1, 3) ' repetitions
      while r
         z = instr(h, "0")
         select case d
            case 1 ' -1
               if (z mod 4) <> 1 then
                  mid$(h, z, 1) = mid$(h, z - 1, 1)
                  mid$(h, z - 1, 1) = "0"
                  decr j
               end if
            case 2 ' +1
               if (z mod 4) <> 0 then
                  mid$(h, z , 1) = mid$(h, z + 1, 1)
                  mid$(h, z + 1, 1) = "0"
                  decr j
               end if
            case 3 ' -4
               if z >= 5 then
                  mid$(h, z, 1) = mid$(h, z - 4, 1)
                  mid$(h, z - 4, 1) = "0"
                  decr j
               end if
            case 4 ' +4
               if z <= 12 then
                  mid$(h, z , 1) = mid$(h, z + 4, 1)
                  mid$(h, z + 4, 1) = "0"
                  decr j
               end if
         end select
         decr r
      wend
   wend
   function = val("&h"+h)
end function

sub shoWMoves(byval h as string, byval s as string, byval m as long, byval p as long)
   local j as long
   local z as long
   local d as long
   cursor off
   call ShowConfiguration(h, 12)
   for j = 1 to m
      d = asc(mid$(s, j, 1))
      z = instr(h, "0")
      select case d
         case %l
            if (z mod 4) <> 1 then
               mid$(h, z, 1) = mid$(h, z - 1, 1)
               mid$(h, z - 1, 1) = "0"
            end if
         case %r
            if (z mod 4) <> 0 then
               mid$(h, z , 1) = mid$(h, z + 1, 1)
               mid$(h, z + 1, 1) = "0"
            end if
         case %u
            if z >= 5 then
               mid$(h, z, 1) = mid$(h, z - 4, 1)
               mid$(h, z - 4, 1) = "0"
            end if
         case %d
            if z <= 12 then
               mid$(h, z , 1) = mid$(h, z + 4, 1)
               mid$(h, z + 4, 1) = "0"
            end if
      end select
      call ShowConfiguration(h, 12)
      sleep p
   next
   locate 20,1
   print "Press any key ..."
   cursor on
   waitkey$
   cls
end sub

sub ShowConfiguration(byval h as string, i as long)
   local r as long
   local c as long
   local x as string
   for r = 1 to 4
      for c = 1 to 4
         locate r + i, c + c - 1
         x = mid$(h, r * 4 - 4 + c, 1)
         if x = "0" then
            x = " "
         end if
         print x;
      next
   next
end sub

function PBMain() as long
   call fifteenSolver(&hfe169b4c0a73d852, 1000)
  'call fifteenSolver(createPuzzle(100), 1000)
  'call fifteenSolver(&h0c9dfbae37254861, 1000)
end function

Python

Iterative Depth A*

From https://codegolf.stackexchange.com/questions/6884/solve-the-15-puzzle-the-tile-sliding-puzzle

Solution titled "PyPy, 195 moves, ~12 seconds computation"

Modified to run this task's 52 move problem.

import random


class IDAStar:
    def __init__(self, h, neighbours):
        """ Iterative-deepening A* search.

        h(n) is the heuristic that gives the cost between node n and the goal node. It must be admissable, meaning that h(n) MUST NEVER OVERSTIMATE the true cost. Underestimating is fine.

        neighbours(n) is an iterable giving a pair (cost, node, descr) for each node neighbouring n
        IN ASCENDING ORDER OF COST. descr is not used in the computation but can be used to
        efficiently store information about the path edges (e.g. up/left/right/down for grids).
        """

        self.h = h
        self.neighbours = neighbours
        self.FOUND = object()


    def solve(self, root, is_goal, max_cost=None):
        """ Returns the shortest path between the root and a given goal, as well as the total cost.
        If the cost exceeds a given max_cost, the function returns None. If you do not give a
        maximum cost the solver will never return for unsolvable instances."""

        self.is_goal = is_goal
        self.path = [root]
        self.is_in_path = {root}
        self.path_descrs = []
        self.nodes_evaluated = 0

        bound = self.h(root)

        while True:
            t = self._search(0, bound)
            if t is self.FOUND: return self.path, self.path_descrs, bound, self.nodes_evaluated
            if t is None: return None
            bound = t

    def _search(self, g, bound):
        self.nodes_evaluated += 1

        node = self.path[-1]
        f = g + self.h(node)
        if f > bound: return f
        if self.is_goal(node): return self.FOUND

        m = None # Lower bound on cost.
        for cost, n, descr in self.neighbours(node):
            if n in self.is_in_path: continue

            self.path.append(n)
            self.is_in_path.add(n)
            self.path_descrs.append(descr)
            t = self._search(g + cost, bound)

            if t == self.FOUND: return self.FOUND
            if m is None or (t is not None and t < m): m = t

            self.path.pop()
            self.path_descrs.pop()
            self.is_in_path.remove(n)

        return m


def slide_solved_state(n):
    return tuple(i % (n*n) for i in range(1, n*n+1))

def slide_randomize(p, neighbours):
    for _ in range(len(p) ** 2):
        _, p, _ = random.choice(list(neighbours(p)))
    return p

def slide_neighbours(n):
    movelist = []
    for gap in range(n*n):
        x, y = gap % n, gap // n
        moves = []
        if x > 0: moves.append(-1)    # Move the gap left.
        if x < n-1: moves.append(+1)  # Move the gap right.
        if y > 0: moves.append(-n)    # Move the gap up.
        if y < n-1: moves.append(+n)  # Move the gap down.
        movelist.append(moves)

    def neighbours(p):
        gap = p.index(0)
        l = list(p)

        for m in movelist[gap]:
            l[gap] = l[gap + m]
            l[gap + m] = 0
            yield (1, tuple(l), (l[gap], m))
            l[gap + m] = l[gap]
            l[gap] = 0

    return neighbours

def slide_print(p):
    n = int(round(len(p) ** 0.5))
    l = len(str(n*n))
    for i in range(0, len(p), n):
        print(" ".join("{:>{}}".format(x, l) for x in p[i:i+n]))

def encode_cfg(cfg, n):
    r = 0
    b = n.bit_length()
    for i in range(len(cfg)):
        r |= cfg[i] << (b*i)
    return r


def gen_wd_table(n):
    goal = [[0] * i + [n] + [0] * (n - 1 - i) for i in range(n)]
    goal[-1][-1] = n - 1
    goal = tuple(sum(goal, []))

    table = {}
    to_visit = [(goal, 0, n-1)]
    while to_visit:
        cfg, cost, e = to_visit.pop(0)
        enccfg = encode_cfg(cfg, n)
        if enccfg in table: continue
        table[enccfg] = cost

        for d in [-1, 1]:
            if 0 <= e + d < n:
                for c in range(n):
                    if cfg[n*(e+d) + c] > 0:
                        ncfg = list(cfg)
                        ncfg[n*(e+d) + c] -= 1
                        ncfg[n*e + c] += 1
                        to_visit.append((tuple(ncfg), cost + 1, e+d))

    return table

def slide_wd(n, goal):
    wd = gen_wd_table(n)
    goals = {i : goal.index(i) for i in goal}
    b = n.bit_length()

    def h(p):
        ht = 0 # Walking distance between rows.
        vt = 0 # Walking distance between columns.
        d = 0
        for i, c in enumerate(p):
            if c == 0: continue
            g = goals[c]
            xi, yi = i % n, i // n
            xg, yg = g % n, g // n
            ht += 1 << (b*(n*yi+yg))
            vt += 1 << (b*(n*xi+xg))

            if yg == yi:
                for k in range(i + 1, i - i%n + n): # Until end of row.
                    if p[k] and goals[p[k]] // n == yi and goals[p[k]] < g:
                        d += 2

            if xg == xi:
                for k in range(i + n, n * n, n): # Until end of column.
                    if p[k] and goals[p[k]] % n == xi and goals[p[k]] < g:
                        d += 2

        d += wd[ht] + wd[vt]

        return d
    return h




if __name__ == "__main__":
    solved_state = slide_solved_state(4)
    neighbours = slide_neighbours(4)
    is_goal = lambda p: p == solved_state

    tests = [
        (15, 14, 1, 6, 9, 11, 4, 12, 0, 10, 7, 3, 13, 8, 5, 2),
    ]

    slide_solver = IDAStar(slide_wd(4, solved_state), neighbours)

    for p in tests:
        path, moves, cost, num_eval = slide_solver.solve(p, is_goal, 80)
        slide_print(p)
        print(", ".join({-1: "Left", 1: "Right", -4: "Up", 4: "Down"}[move[1]] for move in moves))
        print(cost, num_eval)

Output - this solution of the problem for this task is the same as the second solution:

rrruldluuldrurdddluulurrrdlddruldluurddlulurruldrrdd

Profiling with standard Python 3.7 took 30 seconds.

15 14  1  6
 9 11  4 12
 0 10  7  3
13  8  5  2
Right, Right, Right, Up, Left, Down, Left, Up, Up, Left, Down, Right, Up, Right, Down, Down, Down, Left, Up, Up, Left, Up, Right, Right, Right, Down, Left, Down, Down, Right, Up, Left, Down, Left, Up, Up, Right, Down, Down, Left, Up, Left, Up, Right, Right, Up, Left, Down, Right, Right, Down, Down
52 872794

A* with good heuristic

File - astar.py
"""

Python example for this Rosetta Code task:

http://rosettacode.org/wiki/15_puzzle_solver

Using A* Algorithm from Wikkipedia:

https://en.wikipedia.org/wiki/A*_search_algorithm

Need to use heuristic that guarantees a shortest path
solution.

"""

import heapq
import copy

# Hopefully this is larger than any fscore or gscore

integer_infinity = 1000000000

class Position(object):
    """Position class represents one position of a 15 puzzle"""

    def __init__(self, tiles):
        """
        Takes a tuple of tuples representing the tiles on a 4x4 puzzle board
        numbering 1-15 with 0 representing an empty square. For example:
        
        (( 1,  2,  3,  4),
         ( 5,  6,  7,  8),
         ( 9, 10, 11, 12),
         (13, 14, 15,  0))
         
        Converts list of lists representation into tuple of tuples.
        """
        if type(tiles) == type(list()):
            t = tiles
            self.tiles = ((t[0][0], t[0][1], t[0][2], t[0][3]),
                          (t[1][0], t[1][1], t[1][2], t[1][3]),        
                          (t[2][0], t[2][1], t[2][2], t[2][3]),        
                          (t[3][0], t[3][1], t[3][2], t[3][3]))
        else:
            self.tiles = tiles
            
        # fields for A* algorithm
        
        self.fscore = integer_infinity
        self.gscore = integer_infinity
        
        self.cameFrom = None
                                    
    def copy_tiles(self):
        """ returns list of lists version """
        t = self.tiles
        
        return [[t[0][0], t[0][1], t[0][2], t[0][3]],
                [t[1][0], t[1][1], t[1][2], t[1][3]],        
                [t[2][0], t[2][1], t[2][2], t[2][3]],        
                [t[3][0], t[3][1], t[3][2], t[3][3]]]        

        
    def neighbors(self):
        """
        returns a list of neighbors
        returns a list position objects with their
        directiontomoveto set to the direction that the
        empty square moved.
        
        tiles is 4x4 tuple of tuples with
        0,0 as top left.
    
        tiles[y][x]

        """
        
        # find 0 - blank square
        
        x0 = None
        y0 = None
        
        for i in range(4):
            for j in range(4):
                if self.tiles[i][j] == 0:
                    y0 = i
                    x0 = j

        if x0 == None or y0 == None:
            return []
            
        neighbor_list = []
            
        # move 0 to the right
        if x0 < 3:
            new_tiles = self.copy_tiles()
            temp = new_tiles[y0][x0+1]
            new_tiles[y0][x0+1] = 0
            new_tiles[y0][x0] = temp
            new_pos = new_position(new_tiles)
            neighbor_list.append(new_pos)
        # move 0 to the left
        if x0 > 0:
            new_tiles = self.copy_tiles()
            temp = new_tiles[y0][x0-1]
            new_tiles[y0][x0-1] = 0
            new_tiles[y0][x0] = temp
            new_pos = new_position(new_tiles)
            neighbor_list.append(new_pos)
        # move 0 up
        if y0 > 0:
            new_tiles = self.copy_tiles()
            temp = new_tiles[y0-1][x0]
            new_tiles[y0-1][x0] = 0
            new_tiles[y0][x0] = temp
            new_pos = new_position(new_tiles)
            neighbor_list.append(new_pos)
        # move 0 down
        if y0 < 3:
            new_tiles = self.copy_tiles()
            temp = new_tiles[y0+1][x0]
            new_tiles[y0+1][x0] = 0
            new_tiles[y0][x0] = temp
            new_pos = new_position(new_tiles)
            neighbor_list.append(new_pos)
            
        return neighbor_list
        
    def __repr__(self):
        # printable version of self
        
        return str(self.tiles[0])+'\n'+str(self.tiles[1])+'\n'+str(self.tiles[2])+'\n'+str(self.tiles[3])+'\n'

# takes tuple of tuples tiles as key, Position object for that tiles as value

all_positions = dict()

def new_position(tiles):
    """ returns a new position or looks up existing one """
    global all_positions
    if type(tiles) == type(list()):
        t = tiles
        tuptiles =   ((t[0][0], t[0][1], t[0][2], t[0][3]),
                      (t[1][0], t[1][1], t[1][2], t[1][3]),        
                      (t[2][0], t[2][1], t[2][2], t[2][3]),        
                      (t[3][0], t[3][1], t[3][2], t[3][3]))
    else:
        tuptiles = tiles
        
    if tuptiles in all_positions:
        return 	all_positions[tuptiles]
    else:
        new_pos = Position(tiles)
        all_positions[tuptiles] = new_pos
        return new_pos
                
def reconstruct_path(current):
    """ 
    Uses the cameFrom members to follow the chain of moves backwards
    and then reverses the list to get the path in the correct order.
    """
    total_path = [current]

    while current.cameFrom != None:
        current = current.cameFrom
        total_path.append(current)
        
    total_path.reverse()
    
    return total_path
        
class PriorityQueue(object):
    """
    Priority queue using heapq.
    elements of queue are (fscore,tiles) for each position.
    If element is removed from queue and fscore doesn't match
    then that element is discarded.
    """

    def __init__(self, object_list):
        """ 
        Save a list in a heapq.
        Assume that each object only appears once
        in the list.
        """
        self.queue_length = 0
        self.qheap = []
        for e in object_list:
            self.qheap.append((e.fscore,e.tiles))
            self.queue_length += 1
        heapq.heapify(self.qheap)
        
    def push(self, new_object):
        """ save object in heapq """
        heapq.heappush(self.qheap,(new_object.fscore,new_object.tiles))
        self.queue_length += 1
        
    def pop(self):
        """ remove object from heap and return """
        if self.queue_length < 1:
            return None
        fscore, tiles = heapq.heappop(self.qheap)
        self.queue_length -= 1
        global all_positions
        pos = all_positions[tiles]
        if pos.fscore == fscore:
            return pos
        else:
            return self.pop()
                
    def __repr__(self):
        # printable version of self
        strrep = ""
        for e in self.qheap:
          fscore, tiles = e
          strrep += str(fscore)+":"+str(tiles)+"\n"
        
        return strrep
        
conflict_table = None

def build_conflict_table():
    global conflict_table
    conflict_table = dict()
    
    # assumes goal tuple has up to 
    # for the given pattern it the start position
    # how much to add for linear conflicts
    # 2 per conflict - max of 6
    
    # goal tuple is ('g0', 'g1', 'g2', 'g3')
    
    conflict_table[('g0', 'g1', 'g2', 'g3')] = 0
    conflict_table[('g0', 'g1', 'g2', 'x')] = 0
    conflict_table[('g0', 'g1', 'g3', 'g2')] = 2
    conflict_table[('g0', 'g1', 'g3', 'x')] = 0
    conflict_table[('g0', 'g1', 'x', 'g2')] = 0
    conflict_table[('g0', 'g1', 'x', 'g3')] = 0
    conflict_table[('g0', 'g1', 'x', 'x')] = 0
    conflict_table[('g0', 'g2', 'g1', 'g3')] = 2
    conflict_table[('g0', 'g2', 'g1', 'x')] = 2
    conflict_table[('g0', 'g2', 'g3', 'g1')] = 4
    conflict_table[('g0', 'g2', 'g3', 'x')] = 0
    conflict_table[('g0', 'g2', 'x', 'g1')] = 2
    conflict_table[('g0', 'g2', 'x', 'g3')] = 0
    conflict_table[('g0', 'g2', 'x', 'x')] = 0
    conflict_table[('g0', 'g3', 'g1', 'g2')] = 4 
    conflict_table[('g0', 'g3', 'g1', 'x')] = 2
    conflict_table[('g0', 'g3', 'g2', 'g1')] = 4
    conflict_table[('g0', 'g3', 'g2', 'x')] = 2
    conflict_table[('g0', 'g3', 'x', 'g1')] = 2
    conflict_table[('g0', 'g3', 'x', 'g2')] = 2
    conflict_table[('g0', 'g3', 'x', 'x')] = 0
    conflict_table[('g0', 'x', 'g1', 'g2')] = 0
    conflict_table[('g0', 'x', 'g1', 'g3')] = 0
    conflict_table[('g0', 'x', 'g1', 'x')] = 0
    conflict_table[('g0', 'x', 'g2', 'g1')] = 2
    conflict_table[('g0', 'x', 'g2', 'g3')] = 0
    conflict_table[('g0', 'x', 'g2', 'x')] = 0
    conflict_table[('g0', 'x', 'g3', 'g1')] = 2
    conflict_table[('g0', 'x', 'g3', 'g2')] = 2
    conflict_table[('g0', 'x', 'g3', 'x')] = 0
    conflict_table[('g0', 'x', 'x', 'g1')] = 0
    conflict_table[('g0', 'x', 'x', 'g2')] = 0
    conflict_table[('g0', 'x', 'x', 'g3')] = 0
    conflict_table[('g1', 'g0', 'g2', 'g3')] = 2
    conflict_table[('g1', 'g0', 'g2', 'x')] = 2
    conflict_table[('g1', 'g0', 'g3', 'g2')] = 4 
    conflict_table[('g1', 'g0', 'g3', 'x')] = 2
    conflict_table[('g1', 'g0', 'x', 'g2')] = 2
    conflict_table[('g1', 'g0', 'x', 'g3')] = 2
    conflict_table[('g1', 'g0', 'x', 'x')] = 2
    conflict_table[('g1', 'g2', 'g0', 'g3')] = 4 
    conflict_table[('g1', 'g2', 'g0', 'x')] = 4
    conflict_table[('g1', 'g2', 'g3', 'g0')] = 6 
    conflict_table[('g1', 'g2', 'g3', 'x')] = 0
    conflict_table[('g1', 'g2', 'x', 'g0')] = 4
    conflict_table[('g1', 'g2', 'x', 'g3')] = 0
    conflict_table[('g1', 'g2', 'x', 'x')] = 0
    conflict_table[('g1', 'g3', 'g0', 'g2')] = 4 
    conflict_table[('g1', 'g3', 'g0', 'x')] = 4
    conflict_table[('g1', 'g3', 'g2', 'g0')] = 6 
    conflict_table[('g1', 'g3', 'g2', 'x')] = 0
    conflict_table[('g1', 'g3', 'x', 'g0')] = 4
    conflict_table[('g1', 'g3', 'x', 'g2')] = 2
    conflict_table[('g1', 'g3', 'x', 'x')] = 0
    conflict_table[('g1', 'x', 'g0', 'g2')] = 2
    conflict_table[('g1', 'x', 'g0', 'g3')] = 2
    conflict_table[('g1', 'x', 'g0', 'x')] = 2
    conflict_table[('g1', 'x', 'g2', 'g0')] = 4
    conflict_table[('g1', 'x', 'g2', 'g3')] = 0
    conflict_table[('g1', 'x', 'g2', 'x')] = 0
    conflict_table[('g1', 'x', 'g3', 'g0')] = 4
    conflict_table[('g1', 'x', 'g3', 'g2')] = 2
    conflict_table[('g1', 'x', 'g3', 'x')] = 0
    conflict_table[('g1', 'x', 'x', 'g0')] = 2
    conflict_table[('g1', 'x', 'x', 'g2')] = 0
    conflict_table[('g1', 'x', 'x', 'g3')] = 0
    conflict_table[('g2', 'g0', 'g1', 'g3')] = 4
    conflict_table[('g2', 'g0', 'g1', 'x')] = 4
    conflict_table[('g2', 'g0', 'g3', 'g1')] = 4
    conflict_table[('g2', 'g0', 'g3', 'x')] = 2
    conflict_table[('g2', 'g0', 'x', 'g1')] = 4
    conflict_table[('g2', 'g0', 'x', 'g3')] = 2
    conflict_table[('g2', 'g0', 'x', 'x')] = 2
    conflict_table[('g2', 'g1', 'g0', 'g3')] = 4
    conflict_table[('g2', 'g1', 'g0', 'x')] = 4
    conflict_table[('g2', 'g1', 'g3', 'g0')] = 6
    conflict_table[('g2', 'g1', 'g3', 'x')] = 2
    conflict_table[('g2', 'g1', 'x', 'g0')] = 4
    conflict_table[('g2', 'g1', 'x', 'g3')] = 2
    conflict_table[('g2', 'g1', 'x', 'x')] = 2
    conflict_table[('g2', 'g3', 'g0', 'g1')] = 4
    conflict_table[('g2', 'g3', 'g0', 'x')] = 4
    conflict_table[('g2', 'g3', 'g1', 'g0')] = 6
    conflict_table[('g2', 'g3', 'g1', 'x')] = 4
    conflict_table[('g2', 'g3', 'x', 'g0')] = 4
    conflict_table[('g2', 'g3', 'x', 'g1')] = 4
    conflict_table[('g2', 'g3', 'x', 'x')] = 0
    conflict_table[('g2', 'x', 'g0', 'g1')] = 4
    conflict_table[('g2', 'x', 'g0', 'g3')] = 2
    conflict_table[('g2', 'x', 'g0', 'x')] = 2
    conflict_table[('g2', 'x', 'g1', 'g0')] = 4
    conflict_table[('g2', 'x', 'g1', 'g3')] = 2
    conflict_table[('g2', 'x', 'g1', 'x')] = 2
    conflict_table[('g2', 'x', 'g3', 'g0')] = 4
    conflict_table[('g2', 'x', 'g3', 'g1')] = 4
    conflict_table[('g2', 'x', 'g3', 'x')] = 0
    conflict_table[('g2', 'x', 'x', 'g0')] = 2
    conflict_table[('g2', 'x', 'x', 'g1')] = 2
    conflict_table[('g2', 'x', 'x', 'g3')] = 0
    conflict_table[('g3', 'g0', 'g1', 'g2')] = 6
    conflict_table[('g3', 'g0', 'g1', 'x')] = 4
    conflict_table[('g3', 'g0', 'g2', 'g1')] = 6
    conflict_table[('g3', 'g0', 'g2', 'x')] = 4
    conflict_table[('g3', 'g0', 'x', 'g1')] = 4
    conflict_table[('g3', 'g0', 'x', 'g2')] = 4
    conflict_table[('g3', 'g0', 'x', 'x')] = 2
    conflict_table[('g3', 'g1', 'g0', 'g2')] = 6
    conflict_table[('g3', 'g1', 'g0', 'x')] = 4
    conflict_table[('g3', 'g1', 'g2', 'g0')] = 6
    conflict_table[('g3', 'g1', 'g2', 'x')] = 4
    conflict_table[('g3', 'g1', 'x', 'g0')] = 4
    conflict_table[('g3', 'g1', 'x', 'g2')] = 4
    conflict_table[('g3', 'g1', 'x', 'x')] = 2
    conflict_table[('g3', 'g2', 'g0', 'g1')] = 6
    conflict_table[('g3', 'g2', 'g0', 'x')] = 4
    conflict_table[('g3', 'g2', 'g1', 'g0')] = 6
    conflict_table[('g3', 'g2', 'g1', 'x')] = 4
    conflict_table[('g3', 'g2', 'x', 'g0')] = 4
    conflict_table[('g3', 'g2', 'x', 'g1')] = 4
    conflict_table[('g3', 'g2', 'x', 'x')] = 2
    conflict_table[('g3', 'x', 'g0', 'g1')] = 4
    conflict_table[('g3', 'x', 'g0', 'g2')] = 4
    conflict_table[('g3', 'x', 'g0', 'x')] = 2
    conflict_table[('g3', 'x', 'g1', 'g0')] = 4
    conflict_table[('g3', 'x', 'g1', 'g2')] = 4
    conflict_table[('g3', 'x', 'g1', 'x')] = 2
    conflict_table[('g3', 'x', 'g2', 'g0')] = 4
    conflict_table[('g3', 'x', 'g2', 'g1')] = 4
    conflict_table[('g3', 'x', 'g2', 'x')] = 2
    conflict_table[('g3', 'x', 'x', 'g0')] = 2
    conflict_table[('g3', 'x', 'x', 'g1')] = 2
    conflict_table[('g3', 'x', 'x', 'g2')] = 2
    conflict_table[('x', 'g0', 'g1', 'g2')] = 0
    conflict_table[('x', 'g0', 'g1', 'g3')] = 0
    conflict_table[('x', 'g0', 'g1', 'x')] = 0
    conflict_table[('x', 'g0', 'g2', 'g1')] = 2
    conflict_table[('x', 'g0', 'g2', 'g3')] = 0
    conflict_table[('x', 'g0', 'g2', 'x')] = 0
    conflict_table[('x', 'g0', 'g3', 'g1')] = 2
    conflict_table[('x', 'g0', 'g3', 'g2')] = 2
    conflict_table[('x', 'g0', 'g3', 'x')] = 0
    conflict_table[('x', 'g0', 'x', 'g1')] = 0
    conflict_table[('x', 'g0', 'x', 'g2')] = 0
    conflict_table[('x', 'g0', 'x', 'g3')] = 0
    conflict_table[('x', 'g1', 'g0', 'g2')] = 2
    conflict_table[('x', 'g1', 'g0', 'g3')] = 2
    conflict_table[('x', 'g1', 'g0', 'x')] = 2
    conflict_table[('x', 'g1', 'g2', 'g0')] = 4
    conflict_table[('x', 'g1', 'g2', 'g3')] = 0
    conflict_table[('x', 'g1', 'g2', 'x')] = 0
    conflict_table[('x', 'g1', 'g3', 'g0')] = 4
    conflict_table[('x', 'g1', 'g3', 'g2')] = 2
    conflict_table[('x', 'g1', 'g3', 'x')] = 0
    conflict_table[('x', 'g1', 'x', 'g0')] = 2
    conflict_table[('x', 'g1', 'x', 'g2')] = 0
    conflict_table[('x', 'g1', 'x', 'g3')] = 0
    conflict_table[('x', 'g2', 'g0', 'g1')] = 4
    conflict_table[('x', 'g2', 'g0', 'g3')] = 2
    conflict_table[('x', 'g2', 'g0', 'x')] = 2
    conflict_table[('x', 'g2', 'g1', 'g0')] = 4
    conflict_table[('x', 'g2', 'g1', 'g3')] = 2
    conflict_table[('x', 'g2', 'g1', 'x')] = 2
    conflict_table[('x', 'g2', 'g3', 'g0')] = 4
    conflict_table[('x', 'g2', 'g3', 'g1')] = 4
    conflict_table[('x', 'g2', 'g3', 'x')] = 0
    conflict_table[('x', 'g2', 'x', 'g0')] = 2
    conflict_table[('x', 'g2', 'x', 'g1')] = 2
    conflict_table[('x', 'g2', 'x', 'g3')] = 0
    conflict_table[('x', 'g3', 'g0', 'g1')] = 4
    conflict_table[('x', 'g3', 'g0', 'g2')] = 4
    conflict_table[('x', 'g3', 'g0', 'x')] = 2
    conflict_table[('x', 'g3', 'g1', 'g0')] = 4
    conflict_table[('x', 'g3', 'g1', 'g2')] = 4
    conflict_table[('x', 'g3', 'g1', 'x')] = 2
    conflict_table[('x', 'g3', 'g2', 'g0')] = 4
    conflict_table[('x', 'g3', 'g2', 'g1')] = 4
    conflict_table[('x', 'g3', 'g2', 'x')] = 2
    conflict_table[('x', 'g3', 'x', 'g0')] = 2
    conflict_table[('x', 'g3', 'x', 'g1')] = 2
    conflict_table[('x', 'g3', 'x', 'g2')] = 2
    conflict_table[('x', 'x', 'g0', 'g1')] = 0
    conflict_table[('x', 'x', 'g0', 'g2')] = 0
    conflict_table[('x', 'x', 'g0', 'g3')] = 0
    conflict_table[('x', 'x', 'g1', 'g0')] = 2
    conflict_table[('x', 'x', 'g1', 'g2')] = 0
    conflict_table[('x', 'x', 'g1', 'g3')] = 0
    conflict_table[('x', 'x', 'g2', 'g0')] = 2
    conflict_table[('x', 'x', 'g2', 'g1')] = 2
    conflict_table[('x', 'x', 'g2', 'g3')] = 0
    conflict_table[('x', 'x', 'g3', 'g0')] = 2
    conflict_table[('x', 'x', 'g3', 'g1')] = 2
    conflict_table[('x', 'x', 'g3', 'g2')] = 2
        
def linear_conflicts(start_list,goal_list):
    """
    calculates number of moves to add to the estimate of
    the moves to get from start to goal based on the number
    of conflicts on a given row or column. start_list
    represents the current location and goal_list represnts
    the final goal.
    """
    
    # Find which of the tiles in start_list have their goals on this line
    # build a pattern to use in a lookup table of this form:
    # g0, g1, g3, g3 fill in x where there is no goal for this line
    
    # all 'x' until we file a tile whose goal is in this line
    
    goal_pattern = ['x', 'x', 'x', 'x']
    
    for g in range(4):
        for s in range(4):
            start_tile_num = start_list[s]
            if start_tile_num == goal_list[g] and start_tile_num != 0:
                goal_pattern[s] = 'g' + str(g) # i.e. g0
                                
    global conflict_table
    
    tup_goal_pattern = tuple(goal_pattern)
    
    if tup_goal_pattern in conflict_table:
        return conflict_table[tuple(goal_pattern)]
    else:
        return 0
    
class lcmap(dict):
    """ 
    Lets you return 0 if you look for an object that
    is not in the dictionary. 
    """
    def __missing__(self, key):
        return 0

def listconflicts(goal_list):
    """ 
    list all possible start lists that will have at least
    one linear conflict.
    
    Possible goal tile configurations
    
    g g g g
    g g g x
    g g x g
    g x g g
    x g g g
    g g x x
    g x g x
    g x x g
    x g g x
    x g x g
    x x g g
        
    """
    
    all_tiles = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]
    
    non_goal_tiles = []
    
    for t in all_tiles:
        if t not in goal_list:
            non_goal_tiles.append(t) 
            
    combinations = lcmap()

    # g g g g
    
    for i in goal_list:
        tile_list2 = goal_list[:]
        tile_list2.remove(i)
        for j in tile_list2:
            tile_list3 = tile_list2[:]
            tile_list3.remove(j)
            for k in tile_list3:
                tile_list4 = tile_list3[:]
                tile_list4.remove(k)
                for l in tile_list4:
                    start_list = (i, j, k, l)
                    conflictadd = linear_conflicts(start_list,goal_list)
                    if conflictadd > 0:
                        combinations[start_list]=conflictadd    
    
    # g g g x
    
    for i in goal_list:
        tile_list2 = goal_list[:]
        tile_list2.remove(i)
        for j in tile_list2:
            tile_list3 = tile_list2[:]
            tile_list3.remove(j)
            for k in tile_list3:
                for l in non_goal_tiles:
                    start_list = (i, j, k, l)
                    conflictadd = linear_conflicts(start_list,goal_list)
                    if conflictadd > 0:
                        combinations[start_list]=conflictadd  

    # g g x g
    
    for i in goal_list:
        tile_list2 = goal_list[:]
        tile_list2.remove(i)
        for j in tile_list2:
            tile_list3 = tile_list2[:]
            tile_list3.remove(j)
            for k in non_goal_tiles:
                for l in tile_list3:
                    start_list = (i, j, k, l)
                    conflictadd = linear_conflicts(start_list,goal_list)
                    if conflictadd > 0:
                        combinations[start_list]=conflictadd
    # g x g g
    
    for i in goal_list:
        tile_list2 = goal_list[:]
        tile_list2.remove(i)
        for j in non_goal_tiles:
            for k in tile_list2:
                tile_list3 = tile_list2[:]
                tile_list3.remove(k)
                for l in tile_list3:
                    start_list = (i, j, k, l)
                    conflictadd = linear_conflicts(start_list,goal_list)
                    if conflictadd > 0:
                        combinations[start_list]=conflictadd

    # x g g g
    
    for i in non_goal_tiles:
        for j in goal_list:
            tile_list2 = goal_list[:]
            tile_list2.remove(j)
            for k in tile_list2:
                tile_list3 = tile_list2[:]
                tile_list3.remove(k)
                for l in tile_list3:
                    start_list = (i, j, k, l)
                    conflictadd = linear_conflicts(start_list,goal_list)
                    if conflictadd > 0:
                        combinations[start_list]=conflictadd

    # g g x x

    for i in goal_list:
        tile_list2 = goal_list[:]
        tile_list2.remove(i)
        for j in tile_list2:
            tile_list3 = tile_list2[:]
            tile_list3.remove(j)
            for k in non_goal_tiles:
                tile_list4 = non_goal_tiles[:]
                tile_list4.remove(k)
                for l in tile_list4:
                    start_list = (i, j, k, l)
                    conflictadd = linear_conflicts(start_list,goal_list)
                    if conflictadd > 0:
                        combinations[start_list]=conflictadd 
                        
    # g x g x

    for i in goal_list:
        tile_list2 = goal_list[:]
        tile_list2.remove(i)
        for j in non_goal_tiles:
            tile_list3 = non_goal_tiles[:]
            tile_list3.remove(j)
            for k in tile_list2:
                for l in tile_list3:
                    start_list = (i, j, k, l)
                    conflictadd = linear_conflicts(start_list,goal_list)
                    if conflictadd > 0:
                        combinations[start_list]=conflictadd    
                        
    # g x x g

    for i in goal_list:
        tile_list2 = goal_list[:]
        tile_list2.remove(i)
        for j in non_goal_tiles:
            tile_list3 = non_goal_tiles[:]
            tile_list3.remove(j)
            for k in tile_list2:
                for l in tile_list3:
                    start_list = (i, j, k, l)
                    conflictadd = linear_conflicts(start_list,goal_list)
                    if conflictadd > 0:
                        combinations[start_list]=conflictadd     
    
    # x g g x

    for i in non_goal_tiles:
        tile_list2 = non_goal_tiles[:]
        tile_list2.remove(i)
        for j in goal_list:
            tile_list3 = goal_list[:]
            tile_list3.remove(j)
            for k in tile_list3:
                for l in tile_list2:
                    start_list = (i, j, k, l)
                    conflictadd = linear_conflicts(start_list,goal_list)
                    if conflictadd > 0:
                        combinations[start_list]=conflictadd      
    
    # x g x g
    
    for i in non_goal_tiles:
        tile_list2 = non_goal_tiles[:]
        tile_list2.remove(i)
        for j in goal_list:
            tile_list3 = goal_list[:]
            tile_list3.remove(j)
            for k in tile_list3:
                for l in tile_list2:
                    start_list = (i, j, k, l)
                    conflictadd = linear_conflicts(start_list,goal_list)
                    if conflictadd > 0:
                        combinations[start_list]=conflictadd      
      
    # x x g g
    
    for i in non_goal_tiles:
        tile_list2 = non_goal_tiles[:]
        tile_list2.remove(i)
        for j in tile_list2:
            for k in goal_list:
                tile_list3 = goal_list[:]
                tile_list3.remove(k)
                for l in tile_list3:
                    start_list = (i, j, k, l)
                    conflictadd = linear_conflicts(start_list,goal_list)
                    if conflictadd > 0:
                        combinations[start_list]=conflictadd      
      
    return combinations


class HeuristicObj(object):
    """ Object used to preprocess goal position for heuristic function """

    def __init__(self, goal):
        """
        Preprocess goal position to setup internal data structures
        that can be used to speed up heuristic.
        """
        
        build_conflict_table()
        
        self.goal_map = []
        for i in range(16):
            self.goal_map.append(i)    
        
        self.goal_lists = goal.tiles
        
        # preprocess for manhattan distance
        
        for row in range(4):
            for col in range(4):
                self.goal_map[goal.tiles[row][col]] = (row, col)
                
        # make access faster by changing to a tuple
        
        self.goal_map = tuple(self.goal_map)
                
        # preprocess for linear conflicts
        
        self.row_conflicts = []
        for row in range(4):
            t = goal.tiles[row]
            conf_dict = listconflicts([t[0],t[1],t[2],t[3]])
            self.row_conflicts.append(conf_dict)
            
        self.col_conflicts = []
        for col in range(4):
            col_list =[]
            for row in range(4):
                col_list.append(goal.tiles[row][col])
            conf_dict = listconflicts(col_list)
            self.col_conflicts.append(conf_dict)

    def heuristic(self, start):
        """ 
        
        Estimates the number of moves from start to goal.
        The goal was preprocessed in __init__.
        
        """
        
        distance = 0
        
        # local variables for instance variables
        
        t = start.tiles
        g = self.goal_map
        rc = self.row_conflicts
        cc = self.col_conflicts
        
        # calculate manhattan distance
        
        for row in range(4):
            for col in range(4):
                start_tilenum = t[row][col]
                if start_tilenum != 0:
                    (grow, gcol) = g[start_tilenum]
                    distance += abs(row - grow) + abs(col - gcol)
                                        
        # add linear conflicts 
        
        for row in range(4):
            curr_row = t[row]
            distance += rc[row][curr_row]
       
        for col in range(4):
            col_tuple = (t[0][col], t[1][col], t[2][col], t[3][col])
            distance += cc[col][col_tuple]
          
        return distance
        
# global variable for heuristic object

hob = None
        
def a_star(start_tiles, goal_tiles):
    """ Based on https://en.wikipedia.org/wiki/A*_search_algorithm """
    
    start = new_position(start_tiles)
    goal = new_position(goal_tiles)
    
    # Process goal position for use in heuristic
    
    global hob
    hob = HeuristicObj(goal)
    
    # The set of currently discovered nodes that are not evaluated yet.
    # Initially, only the start node is known.
    # For the first node, the fscore is completely heuristic.
    
    start.fscore = hob.heuristic(start)
    openSet = PriorityQueue([start])
 
    # The cost of going from start to start is zero.
    
    start.gscore = 0
    
    num_popped = 0
    
    while openSet.queue_length > 0:
        current = openSet.pop()
        if current == None: # tried to pop but only found old fscore values
            break
        num_popped += 1
        if num_popped % 100000 == 0:
            print(str(num_popped)+" positions examined")
        
        if current == goal:
            return reconstruct_path(current)
            
        for neighbor in current.neighbors():

            # The distance from start to a neighbor
            # All nodes are 1 move from their neighbors
            
            tentative_gScore = current.gscore + 1
            
            # update gscore and fscore if this is shorter path
            # to the neighbor node

            if tentative_gScore < neighbor.gscore: 
                neighbor.cameFrom = current
                neighbor.gscore = tentative_gScore
                neighbor.fscore = neighbor.gscore + hob.heuristic(neighbor)
                openSet.push(neighbor) # add to open set every time
                

def find_zero(tiles):
    """ file the 0 tile """
    for row in range(4):
        for col in range(4):
            if tiles[row][col] == 0:
                return (row, col)

def path_as_0_moves(path):
    """
    Takes the path which is a list of Position
    objects and outputs it as a string of rlud 
    directions to match output desired by 
    Rosetta Code task.
    """
    strpath = ""
    if len(path) < 1:
        return ""
    prev_pos = path[0]
    p_row, p_col = find_zero(prev_pos.tiles)
    for i in range(1,len(path)):
        curr_pos = path[i]
        c_row, c_col = find_zero(curr_pos.tiles)
        if c_row > p_row:
            strpath += 'd'
        elif c_row < p_row:
            strpath += 'u'
        elif c_col > p_col:
            strpath += 'r'
        elif c_col < p_col:
            strpath += 'l'
        # reset for next loop
        prev_pos = curr_pos
        p_row = c_row
        p_col = c_col
    return strpath
File - testone.py
"""

Runs one test of the solver passing a 
start and goal position.

"""

from astar import *
import time

# Rosetta Code start position


start_tiles =    [[ 15, 14,  1,  6],
                  [ 9, 11,  4, 12],
                  [ 0, 10,  7,  3],
                  [13,  8,  5,  2]]

goal_tiles =        [[ 1,  2,  3,  4],
                     [ 5,  6,  7,  8],
                     [ 9, 10, 11, 12],
                     [13, 14, 15,  0]]
                 

before = time.perf_counter()

result = a_star(start_tiles,goal_tiles)

after = time.perf_counter()

print(" ")
print("Path length = "+str(len(result) - 1))
print(" ")
print("Path using rlud:")
print(" ")
print(path_as_0_moves(result))
print(" ")
print("Run time in seconds: "+str(after - before))

Output:

C:\bobby\15puzzlesolver\my15puzzlesolver>testone.py
100000 positions examined
200000 positions examined
300000 positions examined
400000 positions examined
500000 positions examined
600000 positions examined
700000 positions examined
800000 positions examined

Path length = 52

Path using rlud:

rrruldluuldrurdddluulurrrdlddruldluurddlulurruldrrdd

Run time in seconds: 56.601139201

Racket

#lang racket

;;; Solution to the 15-puzzle game, based on the A* algorithm described
;;; at https://de.wikipedia.org/wiki/A*-Algorithmus

;;; Inspired by the Python solution of the rosetta code:
;;; http://rosettacode.org/wiki/15_puzzle_solver#Python

(require racket/set)
(require data/heap)

;; ----------------------------------------------------------------------------
;; Datatypes

;; A posn is a pair struct containing two integer for the row/col indices.
(struct posn (row col) #:transparent)

;; A state contains a vector and a posn describing the position of the empty slot.
(struct state (matrix empty-slot) #:transparent)

(define directions '(up down left right))

;; A node contains a state, a reference to the previous node, a g value (actual
;; costs until this node, and a f value (g value + heuristics).
(struct node (state prev cost f-value) #:transparent)

;; ----------------------------------------------------------------------------
;; Constants

(define side-size 4)

(define initial-state
  (state
    #(
      15  14  1  6
      9  11  4 12
      0  10  7  3
      13  8  5  2)
    (posn 2 0)))


(define goal-state
  (state
    #( 1  2  3  4
      5  6  7 8
      9 10 11 12
      13 14 15 0)
    (posn 3 3)))

;; ----------------------------------------------------------------------------
;; Functions

;; Matrices are simple vectors, abstracted by following functions.
(define (matrix-ref matrix row col)
  (vector-ref matrix (+ (* row side-size) col)))

(define (matrix-set! matrix row col val)
  (vector-set! matrix
               (+ (* row side-size) col)
               val))

(define (target-state? st)
  (equal? st goal-state))

;; Traverse all nodes until the initial state and generate a return a list
;; of symbols describing the path.
(define (reconstruct-movements leaf-node)
  ;; compute a pair describing the movement.
  (define (posn-diff p0 p1)
    (posn (- (posn-row p1) (posn-row p0))
          (- (posn-col p1) (posn-col p0))))
  
  ;; describe a single movement with a symbol (r, l, u d).
  (define (find-out-movement prev-st st)
    (let ([prev-empty-slot (state-empty-slot prev-st)]
          [this-empty-slot (state-empty-slot st)])
      (match (posn-diff prev-empty-slot this-empty-slot)
             [(posn  1  0) 'u]
             [(posn -1  0) 'd]
             [(posn  0  1) 'l]
             [(posn  0 -1) 'r]
             [#f 'invalid])))
  
  (define (iter n path)
    (if (or (not n) (not (node-prev n)))
        path
      (iter (node-prev n)
            (cons (find-out-movement (node-state n)
                                     (node-state (node-prev n)))
                  path))))
  (iter leaf-node '()))

(define (print-path path)
  (for ([dir (in-list (car path))])
       (display dir))
  (newline))

;; Return #t if direction is allowed for the given empty slot position.
(define (movement-valid? direction empty-slot)
  (match direction
         ['up (< (posn-row empty-slot) (- side-size 1))]
         ['down (> (posn-row empty-slot) 0)]
         ['left (< (posn-col empty-slot) (- side-size 1))]
         ['right (> (posn-col empty-slot) 0)]))

;; assumes move direction is valid (see movement-valid?).
;; Return a new state in the given direction.
(define (move st direction)
  (define m (vector-copy (state-matrix st)))
  (define empty-slot (state-empty-slot st))
  (define r (posn-row empty-slot))
  (define c (posn-col empty-slot))
  (define new-empty-slot
    (match direction
           ['up  (begin (matrix-set! m r c (matrix-ref m (+ r 1) c))
                        (matrix-set! m (+ r 1) c 0)
                        (posn (+ r 1) c))]
           ['down (begin (matrix-set! m r c (matrix-ref m (- r 1) c))
                         (matrix-set! m (- r 1) c 0)
                         (posn (- r 1) c))]
           ['left (begin (matrix-set! m r c (matrix-ref m r (+ c 1)))
                         (matrix-set! m r (+ c 1) 0)
                         (posn r (+ c 1)))]
           ['right (begin (matrix-set! m r c (matrix-ref m r (- c 1)))
                          (matrix-set! m r (- c 1) 0)
                          (posn r (- c 1)))]))
  (state m new-empty-slot))

(define (l1-distance posn0 posn1)
  (+ (abs (- (posn-row posn0) (posn-row posn1)))
     (abs (- (posn-col posn0) (posn-col posn1)))))

;; compute the L1 distance from the current position and the goal position for
;; the given val
(define (element-cost val current-posn)
  (if (= val 0)
      (l1-distance current-posn (posn 3 3))
    (let ([target-row (quotient (- val 1) side-size)]
          [target-col (remainder (- val 1) side-size)])
      (l1-distance current-posn (posn target-row target-col)))))

;; compute the l1 distance between this state and the goal-state
(define (state-l1-distance-to-goal st)
  (define m (state-matrix st))
  (for*/fold
    ([sum 0])
    ([i (in-range side-size)]
     [j (in-range side-size)])
    (let ([val (matrix-ref m i j)])
      (if (not (= val 0))
          (+ sum (element-cost val (posn i j)))
        sum))))

;; the heuristic used is the l1 distance to the goal-state + the number of
;; linear conflicts found
(define (state-heuristics st)
  (+ (state-l1-distance-to-goal st)
     (linear-conflicts st goal-state)))

;; given a list, return the number of values out of order (used for computing
;; linear conflicts).
(define (out-of-order-values lst)
  (define (iter val-lst sum)
    (if (empty? val-lst)
        sum
      (let* ([val (car val-lst)]
             [rst (cdr val-lst)]
             [following-smaller-values
               (filter (lambda (val2) (> val2 val))
                       rst)])
        (iter rst (+ sum (length following-smaller-values))))))
  (* 2 (iter lst 0)))

;; Number of conflicts in the given row. A conflict happens, when two elements
;; are already in the correct row, but in the wrong order.
;; For each conflicted pair add 2 to the value, but a maximum of 6.
(define (row-conflicts row st0 st1)
  (define m0 (state-matrix st0))
  (define m1 (state-matrix st1))
  
  (define values-in-correct-row
    (for/fold
      ([lst '()])
      ([col0 (in-range side-size)])
      (let* ([val0 (matrix-ref m0 row col0)]
             [in-goal-row?
               (for/first ([col1 (in-range side-size)]
                           #:when (= val0 (matrix-ref m1 row col1)))
                          #t)])
        (if in-goal-row? (cons val0 lst) lst))))
  
  (min 6 (out-of-order-values
           ; 0 doesn't lead to a linear conflict
           (filter positive? values-in-correct-row))))

;; Number of conflicts in the given row. A conflict happens, when two elements
;; are already in the correct column but in the wrong order.
;; For each conflicted pair add 2 to the value, but a maximum of 6, so that
;; the heuristic doesn't overestimate the actual costs.
(define (col-conflicts col st0 st1)
  (define m0 (state-matrix st0))
  (define m1 (state-matrix st1))
  
  (define values-in-correct-col
    (for/fold
      ([lst '()])
      ([row0 (in-range side-size)])
      (let* ([val0 (matrix-ref m0 row0 col)]
             [in-goal-col?
               (for/first ([row1 (in-range side-size)]
                           #:when (= val0 (matrix-ref m1 row1 col)))
                          #t)])
        (if in-goal-col? (cons val0 lst) lst))))
  (min 6 (out-of-order-values
           ; 0 doesn't lead to a linear conflict
           (filter positive? values-in-correct-col))))

(define (all-row-conflicts st0 st1)
  (for/fold ([sum 0])
            ([row (in-range side-size)])
            (+ (row-conflicts row st0 st1) sum)))

(define (all-col-conflicts st0 st1)
  (for/fold ([sum 0])
            ([col (in-range side-size)])
            (+ (col-conflicts col st0 st1) sum)))

(define (linear-conflicts st0 st1)
  (+ (all-row-conflicts st0 st1) (all-col-conflicts st0 st1)))

;; Return a list of pairs containing the possible next node and the movement
;; direction needed.
(define (next-state-dir-pairs current-node)
  (define st (node-state current-node))
  (define empty-slot (state-empty-slot st))
  (define valid-movements
    (filter (lambda (dir) (movement-valid? dir empty-slot))
            directions))
  (map (lambda (dir)
         (cons (move st dir) dir))
       valid-movements))

;; Helper function to pretty-print a state
(define (display-state st)
  (define m (state-matrix st))
  (begin
    (for ([i (in-range 0 side-size 1)])
         (newline)
         (for ([j (in-range 0 side-size 1)])
              (printf "~a\t" (matrix-ref m i j))))
    (newline)))


(define (A* initial-st)
  (define (compare-nodes n0 n1)
    (<= (node-f-value n0) (node-f-value n1)))
  (define open-lst (make-heap compare-nodes))
  (define initial-st-cost (state-heuristics initial-st))
  (heap-add! open-lst (node initial-st #f 0 (state-heuristics initial-st)))
  (define closed-set (mutable-set))
  
  (define (pick-next-node!)
    (define next-node (heap-min open-lst))
    (heap-remove-min! open-lst)
    next-node)
  
  (define (sort-lst lst)
    (sort lst
          (lambda (n0 n1)
            (< (node-f-value n0) (node-f-value n1)))))
  
  (define (expand-node n)
    
    (define n-cost (node-cost n))
    
    (define (iter lst)
      (if (empty? lst)
          '()
        (let* ([succ (car lst)]
               [succ-st (car succ)]
               [succ-dir (cdr succ)]
               [succ-cost (+ 1 n-cost)])
          (if (set-member? closed-set succ-st)
              (iter (cdr lst))
            
            (begin    (heap-add! open-lst
                                 (node succ-st
                                       n
                                       succ-cost
                                       (+ (state-heuristics succ-st)
                                          succ-cost)))
                   (iter (cdr lst)))))))
    
    (let ([successors (next-state-dir-pairs n)])
      (iter successors)))
  
  (define counter 0)
  (define (loop)
    (define current-node (pick-next-node!))
    (define current-state (node-state current-node))
    (set! counter (+ counter 1))
    (if (= (remainder counter 100000) 0)
        (printf "~a ~a ~a\n" counter
                (heap-count open-lst)
                (node-cost current-node))
      (void))
    
    (cond [(target-state? current-state)
           (let ([path (reconstruct-movements current-node)])
             (cons path (length path)))]
          [else
            (begin (set-add! closed-set (node-state current-node))
                   (expand-node current-node)
                   (if (= (heap-count open-lst) 0)
                       #f
                     (loop)))]))
  (loop))

(module+ main
         (print-path (A* initial-state)))

Raku

Translation of: C++
# 20210623 Raku programming solution vCSMt9hkak0

constant \Nr = <3 0 0 0 0 1 1 1 1 2 2 2 2 3 3 3>;
constant \Nc = <3 0 1 2 3 0 1 2 3 0 1 2 3 0 1 2>;

my ($n,$m) = 0,0 ;
my @N0 is default(0);
my @N3 is default(0);
my @N4 is default(0);
my @N2 is default(0);

sub fY() {
   if @N2[$n] == 0x123456789abcdef0 {
      say "Solution found in ", $n, " moves: ", [~] @N3[1..$n] and exit();
   }
   return @N4[$n] ≤ $m ?? fN() !! False ;
}

sub fN() {
   sub common { ++$n; return True if fY(); --$n }
   if (@N3[$n] ne 'u' && @N0[$n] div 4 < 3) { fI() and common }
   if (@N3[$n] ne 'd' && @N0[$n] div 4 > 0) { fG() and common }
   if (@N3[$n] ne 'l' && @N0[$n]  %  4 < 3) { fE() and common }
   if (@N3[$n] ne 'r' && @N0[$n]  %  4 > 0) { fL() and common }
   return False;
}

sub fI() {
   my \g = (11-@N0[$n])*4;
   my \a = @N2[$n] +& (15 +< g);
   @N0[$n+1]=@N0[$n]+4;
   @N2[$n+1]=@N2[$n]-a+(a+<16);
   @N3[$n+1]='d';
   @N4[$n+1]=@N4[$n]+(Nr[a+>g] ≤ @N0[$n] div 4 ?? 0 !! 1);
}

sub fG() {
   my \g = (19-@N0[$n])*4;
   my \a = @N2[$n] +& (15 +< g);
   @N0[$n+1]=@N0[$n]-4;
   @N2[$n+1]=@N2[$n]-a+(a+>16);
   @N3[$n+1]='u';
   @N4[$n+1]=@N4[$n]+(Nr[a+>g] ≥ @N0[$n] div 4 ?? 0 !! 1);
}

sub fE() {
   my \g = (14-@N0[$n])*4;
   my \a = @N2[$n] +& (15 +< g);
   @N0[$n+1]=@N0[$n]+1;
   @N2[$n+1]=@N2[$n]-a+(a+<4);
   @N3[$n+1]='r';
   @N4[$n+1]=@N4[$n]+(Nc[a+>g] ≤ @N0[$n]%4 ?? 0 !! 1);
}

sub fL(){
   my \g = (16-@N0[$n])*4;
   my \a = @N2[$n] +& (15 +< g);
   @N0[$n+1]=@N0[$n]-1;
   @N2[$n+1]=@N2[$n]-a+(a+>4);
   @N3[$n+1]='l';
   @N4[$n+1]=@N4[$n]+(Nc[a+>g] ≥ @N0[$n]%4 ?? 0 !! 1);
}

sub fifteenSolver(\n, \g){@N0[0]=n; @N2[0]=g}


fifteenSolver(8,0xfe169b4c0a73d852);
loop { fY() or ++$m }
Output:
Solution found in 52 moves: rrrulddluuuldrurdddrullulurrrddldluurddlulurruldrdrd

Rust

Translation of: C++
const NR: [i32; 16] = [3, 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3];
const NC: [i32; 16] = [3, 0, 1, 2, 3, 0, 1, 2, 3, 0, 1, 2, 3, 0, 1, 2];

const I: u8 = 1;
const G: u8 = 8;
const E: u8 = 2;
const L: u8 = 4;

struct FifteenSolver {
    n: usize,
    limit: usize,
    n0: [i32; 85],
    n3: [u8; 85],
    n4: [usize; 85],
    n2: [u64; 85],
}

impl FifteenSolver {
    fn f_y(&mut self) -> bool {
        if self.n2[self.n] == 0x123456789abcdef0 {
            return true;
        }
        if self.n4[self.n] <= self.limit {
            return self.f_n();
        }
        false
    }

    fn f_z(&mut self, w: u8) -> bool {
        if w & I != 0 {
            self.f_i();
            if self.f_y() {
                return true;
            }
            self.n -= 1;
        }
        if w & G != 0 {
            self.f_g();
            if self.f_y() {
                return true;
            }
            self.n -= 1;
        }
        if w & E != 0 {
            self.f_e();
            if self.f_y() {
                return true;
            }
            self.n -= 1;
        }
        if w & L != 0 {
            self.f_l();
            if self.f_y() {
                return true;
            }
            self.n -= 1;
        }
        false
    }

    fn f_n(&mut self) -> bool {
        self.f_z(match self.n0[self.n] {
            0 => match self.n3[self.n] {
                b'l' => I,
                b'u' => E,
                _ => I + E,
            },
            3 => match self.n3[self.n] {
                b'r' => I,
                b'u' => L,
                _ => I + L,
            },
            1 | 2 => match self.n3[self.n] {
                b'l' => I + L,
                b'r' => I + E,
                b'u' => E + L,
                _ => L + E + I,
            },
            12 => match self.n3[self.n] {
                b'l' => G,
                b'd' => E,
                _ => E + G,
            },
            15 => match self.n3[self.n] {
                b'r' => G,
                b'd' => L,
                _ => G + L,
            },
            13 | 14 => match self.n3[self.n] {
                b'l' => G + L,
                b'r' => E + G,
                b'd' => E + L,
                _ => G + E + L,
            },
            4 | 8 => match self.n3[self.n] {
                b'l' => I + G,
                b'u' => G + E,
                b'd' => I + E,
                _ => I + G + E,
            },
            7 | 11 => match self.n3[self.n] {
                b'd' => I + L,
                b'u' => G + L,
                b'r' => I + G,
                _ => I + G + L,
            },
            _ => match self.n3[self.n] {
                b'd' => I + E + L,
                b'l' => I + G + L,
                b'r' => I + G + E,
                b'u' => G + E + L,
                _ => I + G + E + L,
            },
        })
    }

    fn f_i(&mut self) {
        let g = (11 - self.n0[self.n]) * 4;
        let a = self.n2[self.n] & (15u64 << g);
        self.n0[self.n + 1] = self.n0[self.n] + 4;
        self.n2[self.n + 1] = self.n2[self.n] - a + (a << 16);
        self.n3[self.n + 1] = b'd';
        self.n4[self.n + 1] = self.n4[self.n];
        let cond = NR[(a >> g) as usize] <= self.n0[self.n] / 4;
        if !cond {
            self.n4[self.n + 1] += 1;;
        }
        self.n += 1;
    }

    fn f_g(&mut self) {
        let g = (19 - self.n0[self.n]) * 4;
        let a = self.n2[self.n] & (15u64 << g);
        self.n0[self.n + 1] = self.n0[self.n] - 4;
        self.n2[self.n + 1] = self.n2[self.n] - a + (a >> 16);
        self.n3[self.n + 1] = b'u';
        self.n4[self.n + 1] = self.n4[self.n];
        let cond = NR[(a >> g) as usize] >= self.n0[self.n] / 4;
        if !cond {
            self.n4[self.n + 1] += 1;
        }
        self.n += 1;
    }

    fn f_e(&mut self) {
        let g = (14 - self.n0[self.n]) * 4;
        let a = self.n2[self.n] & (15u64 << g);
        self.n0[self.n + 1] = self.n0[self.n] + 1;
        self.n2[self.n + 1] = self.n2[self.n] - a + (a << 4);
        self.n3[self.n + 1] = b'r';
        self.n4[self.n + 1] = self.n4[self.n];
        let cond = NC[(a >> g) as usize] <= self.n0[self.n] % 4;
        if !cond {
            self.n4[self.n + 1] += 1;
        }
        self.n += 1;
    }

    fn f_l(&mut self) {
        let g = (16 - self.n0[self.n]) * 4;
        let a = self.n2[self.n] & (15u64 << g);
        self.n0[self.n + 1] = self.n0[self.n] - 1;
        self.n2[self.n + 1] = self.n2[self.n] - a + (a >> 4);
        self.n3[self.n + 1] = b'l';
        self.n4[self.n + 1] = self.n4[self.n];
        let cond = NC[(a >> g) as usize] >= self.n0[self.n] % 4;
        if !cond {
            self.n4[self.n + 1] += 1;
        }
        self.n += 1;
    }

    fn new(n: i32, g: u64) -> Self {
        let mut solver = FifteenSolver {
            n: 0,
            limit: 0,
            n0: [0; 85],
            n3: [0; 85],
            n4: [0; 85],
            n2: [0; 85],
        };
        solver.n0[0] = n;
        solver.n2[0] = g;
        solver
    }

    fn solve(&mut self) {
        while !self.f_n() {
            self.n = 0;
            self.limit += 1;
        }
        println!(
            "Solution found in {} moves: {}",
            self.n,
            std::str::from_utf8(&self.n3[1..=self.n]).unwrap()
        );
    }
}

fn main() {
    FifteenSolver::new(8, 0xfe169b4c0a73d852).solve();
}
Output:
Solution found in 52 moves: rrrulddluuuldrurdddrullulurrrddldluurddlulurruldrdrd

alternative

use keyed_priority_queue::KeyedPriorityQueue;
use std::cmp::{Ord, Ordering, PartialOrd, Reverse};

static CORRECT_ORDER: [u8; 16] = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 0];
static ROW: [i32; 16] = [0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3];
static COLUMN: [i32; 16] = [0, 1, 2, 3, 0, 1, 2, 3, 0, 1, 2, 3, 0, 1, 2, 3];

#[derive(Debug, Clone)]
struct State {
    est_tot_moves: u8,
    moves: String,
    est_moves_rem: u8,
}

impl PartialOrd for State {
    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
        Some(self.est_tot_moves.cmp(&other.est_tot_moves))
    }
}

impl Eq for State {}

impl PartialEq for State {
    fn eq(&self, other: &Self) -> bool {
        self.est_tot_moves.cmp(&other.est_tot_moves) == Ordering::Equal
    }
}

impl Ord for State {
    fn cmp(&self, other: &Self) -> Ordering {
        self.est_tot_moves
            .partial_cmp(&other.est_tot_moves)
            .unwrap()
    }
}

impl State {
    fn init(order: &[u8; 16]) -> State {
        State {
            est_tot_moves: State::estimate_moves(&order),
            moves: String::from(""),
            est_moves_rem: 0,
        }
    }

    fn find_index(order: &[u8; 16], tile: &u8) -> usize {
        order.iter().position(|&x| x == *tile).unwrap()
    }

    fn estimate_moves(current: &[u8; 16]) -> u8 {
        let mut h = 0;
        for tile in current.iter() {
            let current_index = State::find_index(current, &tile);
            let correct_index = State::find_index(&CORRECT_ORDER, &tile);
            h += ((COLUMN[current_index] - COLUMN[correct_index]).abs()
                + (ROW[current_index] - ROW[correct_index]).abs()) as u8;
        }
        h
    }

    fn make_move(
        &self,
        order: &[u8; 16],
        dir: fn(usize, [u8; 16]) -> ([u8; 16], String),
    ) -> ([u8; 16], State) {
        let est_moves_rem = State::estimate_moves(order);
        let (new_order, from) = dir(State::find_index(order, &0), *order);
        let moves = format!("{}{}", self.moves, from);
        let new_state = State {
            est_tot_moves: moves.len() as u8 + est_moves_rem,
            moves,
            est_moves_rem,
        };
        return (new_order, new_state);
    }

    fn left(index: usize, mut order: [u8; 16]) -> ([u8; 16], String) {
        order.swap(index, index - 1);
        return (order, String::from("l"));
    }

    fn right(index: usize, mut order: [u8; 16]) -> ([u8; 16], String) {
        order.swap(index, index + 1);
        return (order, String::from("r"));
    }

    fn up(index: usize, mut order: [u8; 16]) -> ([u8; 16], String) {
        order.swap(index, index - 4);
        return (order, String::from("u"));
    }

    fn down(index: usize, mut order: [u8; 16]) -> ([u8; 16], String) {
        order.swap(index, index + 4);
        return (order, String::from("d"));
    }

    fn children(&self, order: &[u8; 16]) -> Vec<([u8; 16], State)> {
        let index = State::find_index(order, &0);
        let mut new_states: Vec<([u8; 16], State)> = Vec::new();
        if COLUMN[index] > 0 {
            new_states.push(self.make_move(order, State::left))
        }
        if COLUMN[index] < 3 {
            new_states.push(self.make_move(order, State::right))
        }
        if ROW[index] > 0 {
            new_states.push(self.make_move(order, State::up))
        }
        if ROW[index] < 3 {
            new_states.push(self.make_move(order, State::down))
        }
        new_states
    }
}

fn main() {
    let mut open_states = KeyedPriorityQueue::<[u8; 16], Reverse<State>>::new();
    let start_order = [15, 14, 1, 6, 9, 11, 4, 12, 0, 10, 7, 3, 13, 8, 5, 2];
    // let start_order = [0, 1, 2, 3, 5, 6, 7, 4, 9, 10, 11, 8, 13, 14, 15, 12];
    open_states.push(start_order, Reverse(State::init(&start_order)));
    let mut closed_states = KeyedPriorityQueue::<[u8; 16], Reverse<State>>::new();

    'outer: while let Some((parent_order, Reverse(parent_state))) = open_states.pop() {
        for (child_order, child_state) in parent_state.children(&parent_order) {
            match (
                open_states.get_priority(&child_order).as_ref(),
                closed_states.get_priority(&child_order).as_ref(),
            ) {
                (None, None) => {
                    if child_order == CORRECT_ORDER {
                        println!("There are {} entries in the open list.", open_states.len());
                        println!(
                            "There are {} entries in the closed list.",
                            closed_states.len()
                        );
                        println!(
                            "Reaching the final board took {} moves.",
                            child_state.moves.len()
                        );
                        println!("The moves used were {}.", child_state.moves);
                        println!(
                            "The final order is:\n{:?}\n{:?}\n{:?}\n{:?}.",
                            &child_order[0..4],
                            &child_order[4..8],
                            &child_order[8..12],
                            &child_order[12..16]
                        );
                        break 'outer;
                    }
                    open_states.push(child_order, Reverse(child_state.clone()));
                }
                (Some(&Reverse(open_state)), None)
                    if open_state.moves.len() > child_state.moves.len() =>
                {
                    open_states.set_priority(&child_order, Reverse(child_state.clone()));
                }
                // (None, Some(&Reverse(closed_state))) if closed_state.moves.len() > child_state.moves.len() => {
                //     closed_states.remove_item(&child_order);
                //     open_states.push(child_order, Reverse(child_state.clone()));
                // },
                _ => {}
            };
        }
        closed_states.push(parent_order, Reverse(parent_state));
    }
}
Output:
There are 22525454 entries in the open list.
There are 24568220 entries in the closed list.
Reaching the final board took 52 moves.
The moves used were rrruldluuldrurdddluulurrrdlddruldluurddlulurruldrrdd.
The final order is:
[1, 2, 3, 4]
[5, 6, 7, 8]
[9, 10, 11, 12]
[13, 14, 15, 0].

Scala

Using a weighted A* (not sure if this is the right terminology). Normally A* sums the heuristic (h) + steps travelled (t) so far.

In this solution, we added additional weighting A* = wH * h + wT * t. The reason behind adding this weighting is so that we can fiddle with how much state exploration we want to do. For example, for the main task weighting chosen is wH = 5, wT = 4 so we manage to converge to find one of the 52 steps solution. For the extra credits we choose wH = 2, wT = 1, because we wanted to find fast a solution without exploring much (well I tried, not much luck yet).

The more weight given to the heuristics, the less number of states are explored, with the trade off that solution becomes not optimal (longer than expected). As you can see on the output, we found minimum solution for the easy problem, meanwhile with above setting the hard problem was not really solved, it explore only few states and found an unoptimal solution with 98 length.

Implementation

import scala.collection.mutable
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
    // Fancy way to access table(r)(c) in linear array
    // Equivalent with cTable(r*4 + c) 
    cTable(r << 2 | c) = table(rr << 2 | cc)
    cTable(rr << 2 | cc) = table(r << 2 | 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 = Board.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
  // Find children/neighbours, flatten eliminates the empty boundaries
  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 << 2 | y)) = (x, y)
    }
    res.toMap
  }
}

import Board._
object Solver extends App {
  def solve(initBoard: Board, wTravel:Int, wHeuristic: Int ) {
    // Setup weights for the heuristic, more weight to heuristic faster but longer solution.
    def aStar(b:  Board) = wHeuristic * b.heuristic() + wTravel * b.travelled()
    implicit val ob = new Ordering[Board] {
      override def compare(x: Board, y: Board) = {
        aStar(y) - aStar(x)
      }
    }
    val start = System.currentTimeMillis()
    var found = false
    var head = initBoard
    val pq: mutable.PriorityQueue[Board] = new mutable.PriorityQueue()
    pq.enqueue(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(child  !visited.contains(child.key))
        visited ++= (newChildren.map(_.key))
        pq.enqueue(newChildren: _*)
        explore += 1
      } else found = true
      if (explore % 5000 == 0)
        println(s"# $explore: A* ${aStar(head)}  g:${head.travelled()} h:${head.heuristic()}")
    }
    if (found) {
      println(s"Exploring $explore states, solution with length ${head.parent.length}.")
      println(s"Weighted heuristic used $wHeuristic * heuristic + $wTravel * travel.")
      println(initBoard.format)
      println(head.parent.mkString.reverse)
    }
    println("Total time: " + (System.currentTimeMillis() - start) / 1000.0 + " seconds")
  }
  solve(startEasy, 4, 5)
  solve(startHard, 1, 2 )
}

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)
}

Results

Standard Task

Exploring 698181 states, found solution with length 52.
Weighted heuristic used 5 * heuristic + 4 * travel.
  15  14   1   6
   9  11   4  12
   0  10   7   3
  13   8   5   2
rrruldluuldrurdddluulurrrdlddruldluurddlulurruldrrdd
Total time: 48.884 seconds

Retracing: https://scalafiddle.io/sf/iUgGLJS/2

Extra Task, h(2) t(1)

Exploring 6117 states, found solution with length 106.
Weighted heuristic used 2 * heuristic + 1 * travel.
   0  12   9  13
  15  11  10  14
   3   7   2   5
   4   8   6   1
ddrrurdlulddruldrruluuldlddrruulldrrdruuuldlldrdruruuldldrrululdlurdddluurdrdluldrruluurrddldruulddrulurdd
Total time: 0.328 seconds

Since this is very lightweight exploring very few states, we can even run this on JS

https://scalafiddle.io/sf/iUgGLJS/1

Extra Task, h(9) t(5)

Exploring 728832 states, solution with length 98.
Weighted heuristic used 9 * heuristic + 5 * travel.
   0  12   9  13
  15  11  10  14
   3   7   2   5
   4   8   6   1
ddrruldrdlururddluuuldlddrruulldrrruuldlldrrruuldldrrululdlurddlurdrrdllluruurrddldllurdrrulldrurd
Total time: 117.666 seconds

Wren

Translation of: Go
Library: Wren-long

This works but is very slow (132 seconds).

import "./long" for ULong

var Nr = [3, 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3]
var Nc = [3, 0, 1, 2, 3, 0, 1, 2, 3, 0, 1, 2, 3, 0, 1, 2]
var n  = 0
var n1 = 0
var N0 = List.filled(85, 0)
var N2 = List.filled(85, 0)
var N3 = List.filled(85, 0)
var N4 = List.filled(85, 0)
var i  = 1
var g  = 8
var e  = 2
var l  = 4

var fN  // forward declaration

var xx = ULong.fromBaseString("123456789abcdef0", 16)
var fifteen = ULong.new(15)

var fY = Fn.new {
    if (N2[n] == xx ) return true
    if (N4[n] <= n1) return fN.call()
    return false
}

var fI = Fn.new {
    var g = (11 - N0[n]) * 4
    var a = N2[n] & (fifteen << g)
    N0[n+1] = N0[n] + 4
    N2[n+1] = N2[n] - a + (a << 16)
    N3[n+1] = "d"
    N4[n+1] = N4[n]
    var cond = Nr[(a >> g).toSmall] <= (N0[n] >> 2)
    if (!cond) N4[n+1] = N4[n+1] + 1
    n = n + 1
}

var fG = Fn.new {
    var g = (19 - N0[n]) * 4
    var a = N2[n] & (fifteen << g)
    N0[n+1] = N0[n] - 4
    N2[n+1] = N2[n] - a + (a >> 16)
    N3[n+1] = "u"
    N4[n+1] = N4[n]
    var cond = Nr[(a >> g).toSmall] >= (N0[n] >> 2)
    if (!cond) N4[n+1] = N4[n+1] + 1
    n = n + 1
}

var fE = Fn.new {
    var g = (14 - N0[n]) * 4
    var a = N2[n] & (fifteen << g)
    N0[n+1] = N0[n] + 1
    N2[n+1] = N2[n] - a + (a << 4)
    N3[n+1] = "r"
    N4[n+1] = N4[n]
    var cond = Nc[(a >> g).toSmall] <= N0[n] % 4
    if (!cond) N4[n+1] = N4[n+1] + 1
    n = n + 1
}

var fL = Fn.new {
    var g = (16 - N0[n]) * 4
    var a = N2[n] & (fifteen << g)
    N0[n+1] = N0[n] - 1
    N2[n+1] = N2[n] - a + (a >> 4)
    N3[n+1] = "l"
    N4[n+1] = N4[n]
    var cond = Nc[(a >> g).toSmall] >= N0[n] % 4
    if (!cond) N4[n+1] = N4[n+1] + 1
    n = n + 1
}

var fZ = Fn.new { |w|
    if (w&i > 0) {
        fI.call()
        if (fY.call()) return true
        n = n - 1
    }
    if (w&g > 0) {
        fG.call()
        if (fY.call()) return true
        n = n - 1
    }
    if (w&e > 0) {
        fE.call()
        if (fY.call()) return true
        n = n - 1
    }
    if (w&l > 0) {
        fL.call()
        if (fY.call()) return true
        n = n - 1
    }
    return false
}

fN = Fn.new {
    var p0 = N0[n]
    if (p0 == 0) {
        var p3 = N3[n]
        if (p3 == "l") return fZ.call(i)
        if (p3 == "u") return fZ.call(e)
        return fZ.call(i + e)
    } else if (p0 == 3) {
        var p3 = N3[n]
        if (p3 == "r") return fZ.call(i)
        if (p3 == "u") return fZ.call(l)
        return fZ.call(i + l)
    } else if (p0 == 1 || p0 == 2) {
        var p3 = N3[n]
        if (p3 == "l") return fZ.call(i + l)
        if (p3 == "r") return fZ.call(i + e)
        if (p3 == "u") return fZ.call(e + l)
        return fZ.call(l + e + i)
    } else if (p0 == 12) {
        var p3 = N3[n]
        if (p3 == "l") return fZ.call(g)
        if (p3 == "d") return fZ.call(e)
        return fZ.call(e + g)
    } else if (p0 == 15) {
        var p3 = N3[n]
        if (p3 == "r") return fZ.call(g)
        if (p3 == "d") return fZ.call(l)
        return fZ.call(g + l)
    } else if (p0 == 13 || p0 == 14) {
        var p3 = N3[n]
        if (p3 == "l") return fZ.call(g + l)
        if (p3 == "r") return fZ.call(e + g)
        if (p3 == "d") return fZ.call(e + l)
        return fZ.call(g + e + l)
    } else if (p0 == 4 || p0 == 8) {
        var p3 = N3[n]
        if (p3 == "l") return fZ.call(i + g)
        if (p3 == "u") return fZ.call(g + e)
        if (p3 == "d") return fZ.call(i + e)
        return fZ.call(i + g + e)
    } else if (p0 == 7 || p0 == 11) {
        var p3 = N3[n]
        if (p3 == "d") return fZ.call(i + l)
        if (p3 == "u") return fZ.call(g + l)
        if (p3 == "r") return fZ.call(i + g)
        return fZ.call(i + g + l)
    } else {
        var p3 = N3[n]
        if (p3 == "d") return fZ.call(i + e + l)
        if (p3 == "l") return fZ.call(i + g + l)
        if (p3 == "r") return fZ.call(i + g + e)
        if (p3 == "u") return fZ.call(g + e + l)
        return fZ.call(i + g + e + l)
    }
}

var fifteenSolver = Fn.new { |n, g|
    N0[0] = n
    N2[0] = g
    N4[0] = 0
}

var solve // recursive function
solve = Fn.new {
    if (fN.call()) {
        System.print("Solution found in %(n) moves: ")
        for (g in 1..n) System.write(N3[g])
        System.print()
    } else {
        n = 0
        n1 = n1 + 1
        solve.call()
    }
}

fifteenSolver.call(8, ULong.fromBaseString("fe169b4c0a73d852", 16))
solve.call()
Output:
Solution found in 52 moves: 
rrrulddluuuldrurdddrullulurrrddldluurddlulurruldrdrd