Recursive script for Sisyphus-I



function main ()
{
  //Test if the problem is solvable
  spec Employee : ?      | count | set number_of_employees;
  spec Small_office : ?  | count | set number_of_small_offices;
  spec Large_office : ?  | count | set number_of_large_offices;
  set number_of_places
             `expr 2 \* $number_of_large_offices + $number_of_small_offices`;
  if ($number_of_places < $number_of_employees)
  { print "Insolvable problem: there are" $number_of_employees
          "employees and only" $number_of_places "places in the offices";
  }
  else
  { //List employees already placed if any
    spec [Employee]->(In)->[Office] | count | set number_of_placed_employees;
    if ($number_of_placed_employees > 0)
    { print "Employees already placed:";  spec [Employee]->(In)->[Office]; }

    //Initialise global variables and call the function solving the problem
    spec Employee   : ?  |  set The_employees;
    spec Group_head : ?  |  set The_group_head;
    spec Secretary  : ?  |  set The_secretaries;
    spec Manager    : ?  |  set The_managers;
    spec Large_project_head : ?  |  set The_head_of_large_projects;
    spec Simple_researcher  : ?  |  set The_simple_researchers;
    spec "[Employee:$The_group_head]->(In)->[Office:?]" | set Office_of_group_head;
    spec [Employee:?]- { (Agent)<-[Smoke]; } | set Smokers;
    spec [Employee:?]- { (Agent)<-[Hack]; }  | set Hackers;
    spec [Employee:?]- { (Agent)<-[Hack]; (Agent)<-[Smoke]; } | set Hackers_smokers;

    subtract "$Smokers" "$Hackers_smokers" | set Smokers_non_hackers;
    subtract "$Hackers" "$Hackers_smokers" | set Hackers_non_smokers;
    subtract "$The_employees" "$Smokers" | set Non_smokers;
    subtract "$Non_smokers" "$Hackers_non_smokers" | set Non_hackers_non_smokers;

    set Trace no;   //By default, reasoning traces are printed.
                    //The user must type "set Trace no" to have no trace
    set Answers "";
    if (possible_to_place_the_employees_without_office("")) { print $Answers; }
    else { if ($Answers!="") { print $Answers; }
           else { print "Internal error: an answer should have been found"; }
         }
  }
}


/* The next function returns true if it places 1 employee without office
   or if all employees have an office. Actually, this function tries to place
   all employees without office since it is indirectly recursive via the
   function possible_to_place_first(). This last function manages the backtraking
   when a sequence of office allocation fails. Indented traces shows the sequence
   of office allocations and the backtracking. The parameter "i" contains the
   current indentation for traces.
*/
boolean function possible_to_place_the_employees_without_office (i)
{
  if ($Office_of_group_head == "")
  { if (possible_to_place_head_of_group(Large_central_office))  { return true; }
    if (possible_to_place_head_of_group(Small_central_office))  { return true; }
    if (possible_to_place_head_of_group(Large_and_not_central_office)){return true;}
    if (possible_to_place_head_of_group(Small_and_not_central_office)){return true;}
    set Answers "No office left for the head of group $The_group_head";
    return false;
  }

  set s get_an_employee_without_office("$The_secretaries");
  if ($s)
  { //First, try to place $s with another secretary
    for s2 in $The_secretaries
    { spec "[Employee:$s2]->(In)->[Office:?]" | set offices_with_a_secretary;
      for o in $offices_with_a_secretary
      { if (office_not_fully_occupied($o))
        { if (possible_to_place_first($s,$o," ")) { return true; } }
      }
    }
    //Try to place $s alone nearest to the head of group
    if (possible_to_place_nearest_head_of_group($s,Large_office,$i)) {return true;}
    if (possible_to_place_nearest_head_of_group($s,Small_office,$i)) {return true;}
    set Answers "The secretary $s cannot be placed close to
          $Office_of_group_head, the office of head of group $The_group_head";
    return false;
  }

  set m get_an_employee_without_office("$The_managers");
  if ($m)
  { if (possible_to_place_nearest_head_of_group($m,Small_central_office,$i))
    { return true; }
    if (possible_to_place_nearest_head_of_group($m,Small_and_not_central_office,$i))
    { return true; }
    if (possible_to_place_nearest_head_of_group($m,Large_central_office,$i))
    { return true; }
    if (possible_to_place_nearest_head_of_group($m,Large_and_not_central_office,$i))
    { return true; }
    set Answers "The manager $m cannot be placed close to
           $Office_of_group_head, the office of head of group $The_group_head";
    return false;
  }

  set h get_an_employee_without_office("$The_head_of_large_projects");
  if ($h)
  { if (possible_to_place_nearest_head_of_group($h,Small_office,$i)) {return true;}
    if (possible_to_place_nearest_head_of_group($h,Large_office,$i)) {return true;}
    set Answers "The head of large project $h cannot be placed close to
           $Office_of_group_head, the office of head of group $The_group_head";
    return false;
  }


  set r get_an_employee_without_office("$The_simple_researchers");
  if ($r)
  { if (possible_to_place_alone($r,Small_office,$i)) { return true; }
    if (possible_to_place_alone($r,Large_office,$i)) { return true; }
    if (possible_to_place_with_another_simple_researcher($r,$i)) { return true; }
    set Answers "No office left for the researcher $r";
    return false;
  }

  return true; //all employees are already placed
}


string function get_an_employee_without_office (employees)
{ for e in $employees
  { spec "[Employee:$e]->(In)->[Office]" | set office_of_this_employee;
    if ("$office_of_this_employee" == "") { return $e; }
  }
  return "";
}


boolean function possible_to_place_head_of_group (kind_of_office)
{
  spec $kind_of_office : ?  |  set office_of_this_kind;
  for o in $office_of_this_kind
  { spec "[Employee]->(In)->[Office:$o]" | set occupied_office;
    if ("$occupied_office" == "") //$o is empty
    { set Office_of_group_head $o;
      if (possible_to_place_first($The_group_head,$o," ")) { return true; }
    }
  }
  return false;
}


boolean function office_not_fully_occupied (office)
{
  spec "[Employee]->(In)->[Office:$office]" | count | set number_of_occupants;
  if ($number_of_occupants == 0) { return true; }
  ? Large_office : $office | set office_is_large;
  if ($office_is_large)
  { if ($number_of_occupants < 2) { return true; } }
  return false;
}


boolean function possible_to_place_nearest_head_of_group (employee,kind_of_office,i)
{ set near_chain "->(Near)->";  //first, try very close offices
  while (true)                  //then the loop progressively increases $near_chain
  { spec "[Office:?]$near_chain[Office:$Office_of_group_head]"
        | set offices_near_group_head;
    if ($offices_near_group_head == "") { break; } //no more office at such distance
    for o in $offices_near_group_head
    { ? $kind_of_office : $o  | set office_is_suitable;
      if ($office_is_suitable)
      { spec "[Employee]->(In)->[Office:$o]" | set occupied_office;
         if ("$occupied_office" == "") //$o is empty
         { if (possible_to_place_first($employee,$o,$i)) { return true; } }
      }
    }
    set near_chain "$near_chain[Office]->(Near)->";
  }
  return false;
}


boolean function possible_to_place_alone (employee, kind_of_office, i)
{ /*print $employee; print $kind_of_office;*/
  spec $kind_of_office : ?  |  set office_of_this_kind;
  for o in $office_of_this_kind
  { spec "[Employee]->(In)->[Office:$o]" | set occupied_office;
    if ("$occupied_office" == "") //$o is empty, the employee can be alone
    { if (possible_to_place_first($employee,$o,$i)) { return true; } }
  }
  return false;
}

/* 1) Place smoker with smoker, non-smoker with non-smoker
   2) Place researcher with researcher of a different project
   3) Place hacker with hacker, non hacker with non-hacker
*/
boolean function possible_to_place_with_another_simple_researcher (e, i)
{
  //print "possible_to_place $e with_another_simple_researcher";
  spec "[Employee:$e]<-(Agent)<-[Hack]"  | set employee_hacks;
  spec "[Employee:$e]<-(Agent)<-[Smoke]" | set employee_smokes;
  spec "[Employee:$e]->(Project)->[Project:?]" | set p; //$p: project of $e

  //Place $e with another one with similar smoking and hacking habits
  //and from a different project
  if ("$employee_hacks")
  { if ("$employee_smokes")
    { if (possible_to_place_with($e,Hackers_smokers,$p,$i)) { return true; }
      if (possible_to_place_with($e,Hackers_smokers,"",$i)) { return true; }
    }
    else
    { if (possible_to_place_with($e,Hackers_non_smokers,$p,$i)) { return true; }
      if (possible_to_place_with($e,Hackers_non_smokers,"",$i)) { return true; }
    }
  }
  else
  { if ("$employee_smokes")
    { if (possible_to_place_with($e,Smokers_non_hackers,$p,$i)) { return true; }
      if (possible_to_place_with($e,Smokers_non_hackers,"",$i)) { return true; }
    }
    else
    { if (possible_to_place_with($e,Non_hackers_non_smokers,$p,$i)) { return true; }
      if (possible_to_place_with($e,Non_hackers_non_smokers,"",$i)) { return true; }
    }
  }

  //Now the hacking constraint is relaxed
  if ("$employee_smokes")
  { if (possible_to_place_with($e,Smokers,$p,$i)) { return true; }
    if (possible_to_place_with($e,Smokers,"",$i)) { return true; }
  }
  else
  { if (possible_to_place_with($e,Non_smokers,$p,$i)) { return true; }
    if (possible_to_place_with($e,Non_smokers,"",$i)) { return true; }
  }

  return false;
}


boolean function possible_to_place_with (employee, kind_of_co-occupants,
                                         project, i)
{ set suitable_co-occupants $$kind_of_co-occupants;
  //print "possible_to_place $e ($project) with $kind_of_co-occupants: $suitable_co-occupants .";
  for e2 in $suitable_co-occupants
  { if ($e2 != $employee)
    { ? Simple_researcher: $e2  | set e2_is_a_simple_researcher;
      if ($e2_is_a_simple_researcher)
      { spec "[Employee:$e2]->(In)->[Office:?]" | set o;
        if ($o)
        { ? Large_office : $o | set office_is_large;
          if ($office_is_large)
          { spec "[Employee]->(In)->[Office:$o]" | count | set number_of_occupants;
            if ($number_of_occupants < 2)
            { spec "[Employee:$e2]->(Project)->[Project:?]" | set p2;
              if ($project)
              { if ($p2)
                { if ($p2 != $project)
                  { if (possible_to_place_first($employee,$o,$i)) { return true; } }
                }
              }
              else { if (possible_to_place_first($employee,$o,$i)) {return true;} }
            }
          }
        }
      }
    }
  }

  return false;
}



boolean function possible_to_place_first (employee, office, i)
{
  if ($Trace != no) { print $i "$employee placed in $office"; }
  print "  " $i | set new_indentation;

  name "#$employee$office" "[Employee: $employee]->(In)->[Office: $office]";
  //print "---------------------------------------------------------------";
  if (possible_to_place_the_employees_without_office("$new_indentation"))
  { set Answers "$employee placed in $office
$Answers";
    return true;
  }

  if ($Trace != no) { print $i "$employee cannot be placed in $office"; }
  delCG "#$employee$office";
  return false;
}


main();  //run the program