(*
 * blackbox
 * try to determine the location of n balls in a black box
 *)

program blackbox(input, output);

const
   side     =  8;     (* there are 8 squares to a side *)
   siden1   =  9;     (* the length of one side plus one *)
   maxstart = 32;     (* maximum possible location for ray *)
   absorbed =  0;     (* what track returns when ray absorbed *)
   boxsize  = 64;     (* size of the box *)

type
   course   = (up,down,left,right);   (* possible directions for rays *)
   lines    = array[0..siden1] of boolean;  (* possible locations in the box *)
   location = 0..boxsize;                   (* 0 is for absorption *)

var
   box       : array[0..siden1] of lines;  (* the black box *)
   direction : course;                     (* the ray's current direction *)
   startray  : 1..maxstart;                (* the ray's starting location *)
   numballs  : integer;                    (* the number of balls *)

(*
 * track
 * recursive function that follows a ray's course through the box
 *)

function track(place:location):location;

var
   rownum : 1..side;
   colnum : 1..side;

(*
 * onside
 * tests to see if there is a ball on either side of a ray
 *)

function onside:boolean;

begin
   if (direction = right) or (direction = left) then
      onside := box[rownum-1][colnum] or box[rownum+1][colnum]
   else
      onside := box[rownum][colnum-1] or box[rownum][colnum+1]
end;

(*
 * edge
 * tests if a ray has reached the end of the box
 *)

function edge:boolean;

begin
   case direction of
      left : edge := ( (place - 1) mod side ) = 0;
      right: edge := ( (place - 1) mod side ) = side - 1;
      up   : edge := ( (place - 1) div side ) = 0;
      down : edge := ( (place - 1) div side ) = side - 1
   end
end;

(*
 * diagup
 * tells me if there is a ball on the upward diagonal
 *)

function diagup:boolean;

begin
   case direction of
      left : diagup := box[rownum-1][colnum-1];
      right: diagup := box[rownum-1][colnum+1];
      up   : diagup := box[rownum-1][colnum-1];
      down : diagup := box[rownum+1][colnum-1]
   end
end;

(*
 * diagdown
 * tells me if there is a ball on the diagonal downward
 *)

function diagdown:boolean;

begin
   case direction of
      left : diagdown := box[rownum+1][colnum-1];
      right: diagdown := box[rownum+1][colnum+1];
      up   : diagdown := box[rownum-1][colnum+1];
      down : diagdown := box[rownum+1][colnum+1]
   end
end;

(*
 * change
 * moves the ray one square in the current direction
 *)

procedure change(var num:location);

begin
   (* if we are on an edge, then don't move *)
   if not edge then
      case direction of
         left : num := num - 1;
         right: num := num + 1;
         up   : num := num - side;
         down : num := num + side
      end
end;

(*
 * track
 *)

begin
   rownum := (place - 1) div side + 1;
   colnum := (place - 1) mod side + 1;

   if box[rownum][colnum] then
      track := absorbed
   else
   begin
      if onside then
      begin
         case direction of
            left : direction := right;
            right: direction := left;
            up   : direction := down;
            down : direction := up
         end;
         track := place
      end
      else
      begin
         if diagup and diagdown then
         begin
            case direction of
               left : direction := right;
               right: direction := left;
               up   : direction := down;
               down : direction := up
            end;
            change(place);
            track := track(place)
         end
         else
         begin
            if diagup then
            begin
               case direction of
                  left, right : direction := down;
                  up,down     : direction := right
               end;
               change(place);
               track := track(place)
            end
            else
            if diagdown then
            begin
               case direction of
                  left,right : direction := up;
                  up,down    : direction := left
               end;
               change(place);
               track := track(place)
            end
            else
            if not edge then
            begin
               change(place);
               track := track(place)
            end
            else
               track := place
         end
      end
   end
end;

(*
 * clearballs
 * removes all the balls from the box
 *)

procedure clearballs;

var
   i,j : 0..siden1;

begin
   for i := 0 to siden1 do
      for j := 0 to siden1 do
         box[i][j] := false
end;

(*
 * placeballs
 * asks the user for the number of balls to place in the box, then
 * randomly places the balls in the box
 *)

procedure placeballs;

var
   i,j    : 0..siden1;
   placed : integer;

begin
   randomize;
   clearballs;

   writeln;
   write('How many balls? ');
   readln(numballs);
   randomize;
   placed := 0;
   while placed < numballs do
   begin
      i := 1 + random(side);
      j := 1 + random(side);
      if not box[i][j] then
      begin
         box[i][j] := true;
         placed := placed + 1
      end
   end
end;

(*
 * getray
 * sits in a loop getting numbers from the user, and using them to determine
 * where to fire a ray. if the number is 0, then the user is readu to guess
 * where the balls are located.
 *)

procedure getray;

var
   raynum : integer;
   outat  : location;

(*
 * raytobox
 * converts a user number into the equivalent location in the box
 *)

procedure raytobox(var num : integer);

begin
   if num <> 0 then
   begin
      if num <= side then
      begin
         direction := right;
         num := (num-1)*side + 1
      end
      else
      if num <= (side + side) then
      begin
         direction := up;
         num := sqr(side) - ((2*side)-num)
      end
      else
      if num <= (3 * side) then
      begin
         direction := left;
         num := (3 * side - num + 1) * side
      end
      else
      begin
         direction := down;
         num := (4 * side - num + 1)
      end
   end
end;

(*
 * boxtoray
 * returns the ray location of an edge
 *)

function boxtoray(place:location):location;

begin
   case direction of
      right : boxtoray := place div side + 17;
      left  : boxtoray := (place + (side-1)) div side;
      up    : boxtoray := 33 - place;
      down  : boxtoray := place - 48
   end
end;

begin
   repeat
      write('Where should I shoot now? ');
      readln(raynum);
      while not (raynum in [0..maxstart]) do
      begin
         writeln('Sorry, but only numbers between 0 and ',maxstart);
         write('Start where? ');
         readln(raynum)
      end;
      if raynum <> 0 then
      begin
         write('in at ',raynum:2,' ');
         outat := 0;
         raytobox(raynum);
         outat := track(raynum);
         if outat = 0 then
            writeln('Absorbed')
         else
            writeln('It came out at ',boxtoray(outat))
      end
   until raynum = 0
end;

(*
 * printballs
 * prints out the balls
 *)

procedure printballs;

var

   i,j : 1..side;

begin
   writeln('  32 31 30 29 28 27 26 25');
   writeln('  -------------------------');
   for i := 1 to side do
   begin
      write(i:2,'(');
      for j := 1 to side do
         if box[i][j] then
            write('*  ')
         else
            write('.  ');
      writeln(')',25-i)
   end;
   writeln('  -------------------------');
   writeln('  9  10 11 12 13 14 15 16')
end;

begin
   clearballs;
   printballs;
   placeballs;
   getray;
   printballs;

end.




