// -----------------------------------------------------------------
// The Knight's Tour problem
// -----------------------------------------------------------------

// the size of the board (tours exist only for N = 1 or N ≥ 5)
val N:ℕ;
axiom minSize ⇔ N ≥ 1;

// the number types depending on N
type Coord = ℕ[N-1];                // a board coordinate
type Pos = Record[x:Coord,y:Coord]; // a board position

// the 8 possible jumps of a knight and the corresponding offsets
// (0: right+up, 1: right+down, then counter-clockwise)
type Jump = ℕ[7];
type Offset = ℤ[-2,2];
val x = Map[Jump,Offset](0)
  with [0] =  2 with [1] =  2 with [2] = -1 with [3] =  1
  with [4] = -2 with [5] = -2 with [6] =  1 with [7] = -1;
val y = Map[Jump,Offset](0)
  with [0] =  1 with [1] = -1 with [2] =  2 with [3] =  2
  with [4] = -1 with [5] =  1 with [6] = -2 with [7] = -2;

// the types corresponding to an execution
type Step = ℕ[N⋅N];
type Tour = Array[N⋅N-1,Jump];  
type Positions = Array[N⋅N,Pos];

// starting at position (x0,y0), t[i].. is a valid tour
pred valid(t:Tour, i:Step, x0:Coord, y0:Coord) ⇔
  if i = N⋅N-1 then
    ⊤
  else
    let x1 = x0+x[t[i]], y1 = y0+y[t[i]] in
    0 ≤ x1 ∧ x1 < N ∧ 0 ≤ y1 ∧ y1 < N ∧
    valid(t, i+1, x1, y1);
pred valid(t:Tour) ⇔ valid(t, 0, 0, 0);

// next position after position p by performing jump j
fun position(j:Jump, p:Pos):Pos =
  ⟨x:p.x+x[j], y:p.y+y[j]⟩;
  
// p are the positions resulting from a tour t
pred positions(t:Tour, p:Positions) ⇔
  p[0] = ⟨x:0,y:0⟩ ∧
  ∀i:Step with i < N⋅N-1. p[i+1] = position(t[i], p[i]);

// compute the positions resulting from a tour t
fun positions(t:Tour, i:Step, p:Positions): Positions =
  if i = N⋅N-1 then 
    p
  else
    positions(t, i+1, p with [i+1] = position(t[i],p[i]));
fun positions(t:Tour): Positions 
  ensures positions(t, result);
= positions(t, 0, Array[N*N,Pos](⟨x:0,y:0⟩));

// t is a knight's tour starting at (0,0)
pred tour(t:Tour) ⇔
  valid(t) ∧
  // choose pos:Positions with positions(t, pos) in
  let pos = positions(t) in
  (∀p:Pos with p.x < N ∧ p.y < N.
    ∃i:Step with i < N⋅N. pos[i] = p);

// -----------------------------------------------------------------
// implicit computation of a tour (much too time-consuming)
// -----------------------------------------------------------------

// determine a knight's tour 
fun tour(): Tour = choose t:Tour with tour(t);

// also a violation of this theorem determines a tour for size N
// (allows to apply multiple threads in parallel)
theorem notour(t:Tour) ⇔ ¬tour(t);

// -----------------------------------------------------------------
// computation of a tour by a non-deterministic transition system
// -----------------------------------------------------------------
  
// is jump j legal after having jumped to positions ps[0]..ps[i]
pred admissible(j:Jump, i:Step, ps:Positions) ⇔
  let x0 = ps[i].x, y0 = ps[i].y in
  let x1 = x0+x[j], y1 = y0+y[j] in
  0 ≤ x1 ∧ x1 < N ∧ 0 ≤ y1 ∧ y1 < N ∧
  ∀k:Step with k < i. ps[k] ≠ ⟨x:x1,y:y1⟩;

// returns (b,t) such that, if b is true, then t is a tour
proc tourSystem(): Tuple[Bool,Tour]
  ensures result.1 ⇒ tour(result.2);
{
  var okay:Bool = ⊤;
  var tour:Tour = Array[N⋅N-1,Jump](0);
  var ps:Positions = Array[N⋅N,Pos](⟨x:0,y:0⟩);
  var i:Step = 0;
  while okay ∧ i < N⋅N-1 do
  {
    choose j:Jump with admissible(j, i, ps) then
    {
      tour[i] ≔ j;
      ps[i+1] ≔ position(j,ps[i]);
      i ≔ i+1;
    }
    else okay ≔ ⊥;
  }
  if okay then { print tour ; print positions(tour); }
  return ⟨okay,tour⟩;
}

// checking the theorem determines one/all tours
theorem notour() ⇔ 
  let r = tourSystem() in ¬r.1; // ⊤ prints all tours

// -----------------------------------------------------------------
// explicit computation of a tour
// -----------------------------------------------------------------

// extend tour t of i steps and i+1 positions p to a complete tour
// result.1 is false, if no such tour exists
proc tourProc(i:Step, t:Tour, p:Positions): Tuple[Bool,Tour]
  ensures result.1 ⇒ tour(result.2);
{
  var result:Tuple[Bool,Tour] = ⟨⊤,t⟩;
  if i < N⋅N-1 then
  {
    result.1 ≔ ⊥;
    var j:ℕ[8] = 0;
    while j < 8 ∧ ¬result.1 do
    {
      val x1 = p[i].x+x[j];
      val y1 = p[i].y+y[j];
      if 0 ≤ x1 ∧ x1 < N ∧ 0 ≤ y1 ∧ y1 < N then
      {
        val p1 = ⟨x:x1,y:y1⟩;
        if ∀i0:Step with i0 ≤ i. p[i0] ≠ p1 then
        {
          result ≔ tourProc(i+1, t with [i] = j, p with [i+1] = p1);
        }
      }
      j ≔ j+1;
    }
  }
  return result;
}

// compute tour, result.1 is false, if no such tour exists
fun tourProc(): Tuple[Bool,Tour]
  ensures print positions(result.2) in ⊤;
= tourProc(0, Array[N⋅N-1,Jump](0), Array[N⋅N,Pos](⟨x:0,y:0⟩));

// -----------------------------------------------------------------
// end of file
// -----------------------------------------------------------------







