// -----------------------------------------------------------------
// The goat, wolf, and cabbage problem.
// -----------------------------------------------------------------

// the maximum number of moves considered
val N:ℕ;
type Num = ℕ[N];

// the entities to be transfered across the river
type Entity = ℕ[3];
val farmer  = 0;
val goat    = 1;
val wolf    = 2;
val cabbage = 3;

// this type definition yiedls a less nice printout
// enumtype Entity = farmer | goat | wolf | cabbage;

// the sides of the river
type Place = Bool;
val left  = false;
val right = true;

// this type definition yields a less nice printout
// enumtype Place = left | right;

// the side opposite to p
fun other(p:Place):Place = ¬p;

// a placement of the entity to river sides
// and a sequence of such placements (including the initial one)
type Placement = Map[Entity,Place];
type PlacementSeq = Array[N+1,Placement];

// -----------------------------------------------------------------
// the rules of the game
// -----------------------------------------------------------------

// the initial and the goal situation
pred init(p:Placement) ⇔ ∀e:Entity. p[e] = left;
pred goal(p:Placement) ⇔ ∀e:Entity. p[e] = right;

// placement p is safe
pred safe(p:Placement) ⇔
  let q = other(p[farmer]) in
  ¬(p[wolf] = q ∧ p[goat] = q) ∧
  ¬(p[goat] = q ∧ p[cabbage] = q);
  
// from placement p we derive q by a transfer of the farmer alone
pred move0(p:Placement, q:Placement) ⇔
  p[farmer] ≠ q[farmer] ∧
  ∀e:Entity with e ≠ farmer. p[e] = q[e];

// from p we get q by a transfer of farmer together with entity e
pred move1(p:Placement, q:Placement, e:Entity)
  requires e ≠ farmer;
⇔ p[farmer] ≠ q[farmer] ∧
  p[e] = p[farmer] ∧ q[e] = q[farmer] ∧
  ∀e0:Entity with e0 ≠ farmer ∧ e0 ≠ e. p[e0] = q[e0];

// from placement p we derive a safe placement q by some transfer
pred move(p:Placement, q:Placement) ⇔
  safe(q) ∧
  (move0(p, q) ∨ (∃e:Entity with e ≠ farmer. move1(p, q, e)));

// -----------------------------------------------------------------
// executing/checking the system
// -----------------------------------------------------------------

// return a triple (g,n,ps) which includes a boolean b that
// indicates whether a problem solution was found; if yes, then
// ps represents a sequence of n placements solving the problem
proc Farmer(): Tuple[Bool,Num,PlacementSeq]
{
  choose p0:Placement with init(p0);
  var ps: PlacementSeq = Array[N+1,Placement](p0);
  var p:Placement = p0;
  assert safe(p);
  var i:Num = 0;
  while i < N ∧ ¬goal(p) do
  {
    choose p1:Placement with move(p, p1);
    p ≔ p1;
    assert safe(p);
    ps[i+1] ≔ p;
    i ≔ i+1;
  }
  return ⟨goal(p),i,ps⟩;
}

// a violation of this theorem denotes a solution (which is printed)
theorem noSolution() ⇔ 
  let r = Farmer() in 
  if r.1 then print ⟨r.2,r.3⟩ in false else true;

// -----------------------------------------------------------------
// an alternative (action-based) formulation
// -----------------------------------------------------------------

// a transfer of the farmer alone or with an entity
rectype(1) Action = farmer1 | farmer2(Entity);
type ActionSeq = Array[N,Action];

// perform the move denoted by the action in the current placement
// and return the new placement
fun move(a:Action, p:Placement): Placement =
  let p0 = other(p[farmer]) in
  match a with
  { 
    farmer1 -> 
      p with [farmer] = p0;
    farmer2(e:Entity) ->
      p with [farmer] = p0 with [e] = p0;
  };
  
// action a is admissible with current placement
pred admissible(a:Action, p:Placement) ⇔
  match a with
  {
    farmer1 -> ⊤;
    farmer2(e:Entity) -> p[e] = p[farmer];
  } ∧ safe(move(a,p));

// return a triple (g,n,as) which includes a boolean b that
// indicates whether a problem solution was found; if yes, then
// as represents a sequence of n moves solving the problem
proc Farmer0(): Tuple[Bool,Num,ActionSeq]
{
  choose p0:Placement with init(p0);
  var p:Placement = p0;
  assert safe(p);
  var as:ActionSeq = Array[N,Action](Action!farmer1);
  var i:Num = 0;
  while i < N ∧ ¬goal(p) do
  {
    choose a:Action with admissible(a, p);
    p ≔ move(a,p);
    assert safe(p);
    as[i] ≔ a;
    i ≔ i+1;
  }
  return ⟨goal(p),i,as⟩;
}

// a violation of this theorem denotes a solution (which is printed)
theorem noSolution0() ⇔ 
  let r = Farmer0() in 
  if r.1 then print ⟨r.2,r.3⟩ in false else true;

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


