#! /bin/sh
# This is a shell archive, meaning:
# 1. Remove everything above the #! /bin/sh line.
# 2. Save the resulting text in a file.
# 3. Execute the file with /bin/sh (not csh) to create:
#	arcana.pas
#	brdhndlr.pas
#	butler.pas
#	fumiko.doc
#	fumiko.pas
#	globlctv.pas
#	groupmgr.pas
#	sequence.pas
#	tactics.pas
#	trickle.fum
# This archive created: Mon Aug 10 20:12:28 1992
export PATH; PATH=/bin:/usr/bin:$PATH
if test -f 'arcana.pas'
then
	echo shar: "will not over-write existing file 'arcana.pas'"
else
cat << \SHAR_EOF > 'arcana.pas'
UNIT Arcana;
  {Silly but necessary routines for Fumiko}


INTERFACE


  USES
    Dos, GloblCTV;


  CONST
    point_interpretation_size = sizeof (point_interpretation);


  FUNCTION Plenty_Of_Time (var turn: integer): boolean;
    {Returns true if the game is running on or ahead of schedule}


  FUNCTION On_Board (var x, y: byte): boolean;
    {Returns true if the coordinates name a point on the board}


  FUNCTION Screen_Char (var point: color_type): char;
    {Returns the character to be printed in the board diagram for the point}


  FUNCTION Opposite (var point: color_type): color_type;
    {Black_stone and white_stone are opposites}


  FUNCTION On_Edge (var x: byte): boolean;
    {Returns true if the coordinate is at the edge of the board}


  FUNCTION Line (var x: byte): byte;
    {Returns the distance to the nearest edge}


  FUNCTION Equal (var a, b; size: word): boolean;
    {Compares two records.  From TP60 Programmer's Guide, p. 107}


  PROCEDURE Compress (var removed: byte; var board: board_type);
    {Puts the last item in the list into the hole}


  PROCEDURE Find_Member (var group: group_type; var member: location_type);
    {Sets member to the coordinates of a member of the group}


  FUNCTION Highest_Rated (var rating: integer_board): location_list;
    {Generates a list of the search_breadth highest rated locations, with}
    {the highest-rated ones at the bottom}


  PROCEDURE Clean_Out_Location_List (var list: location_list);
    {Deallocates the list}


  FUNCTION Count_Groups (var board: board_type): byte;
    {Debugging function--count the number of groups}


IMPLEMENTATION


  TYPE
    bytes = array [0..maxint] of byte;  {Used in equal}


  FUNCTION Plenty_Of_Time (var turn: integer): boolean;
    {Returns true if the game is running on or ahead of schedule}
    Begin  {Plenty Of Time}
      plenty_of_time := (100 * (125 - (turn div 2)) div time_left) < (12500 div total_time)
    End;  {Plenty Of Time}


  FUNCTION On_Board (var x, y: byte): boolean;
    {Returns true if the coordinates name a point on the board}
    Begin  {On Board}
      on_board := (x > 0) and (x <= board_size) and (y > 0) and (y <= board_size)
    End;  {On Board}


  FUNCTION Screen_Char (var point: color_type): char;
    {Returns the character to be printed in the board diagram for the point}
    Begin  {Screen Char}
      Case point of
        black_stone: screen_char := #1;
        white_stone: screen_char := #2;
        empty: screen_char := #250;
        off_board: screen_char := '?'
      End
    End;  {Screen Char}


  FUNCTION Opposite (var point: color_type): color_type;
    {black_stone and white_stone are opposites}
    Begin  {Opposite}
      If point = black_stone
        Then opposite := white_stone
        Else opposite := black_stone
    End;  {Opposite}


  FUNCTION On_Edge (var x: byte): boolean;
    {Returns true if the coordinate is at the edge of the board}
    Begin  {On Edge}
      on_edge := (x = 1) or (x = board_size)
    End;  {On Edge}


  FUNCTION Line (var x: byte): byte;
    {Returns the distance to the nearest edge}
    Begin  {Line}
      If x > (board_size + 1 - x)
        Then line := board_size + 1 - x
        Else line := x
    End;  {Line}


  FUNCTION Equal (var a, b; size: word): boolean;
    {Compares two records.  From TP60 Programmer's Guide, p. 107}
    Var
      n: integer;
    Begin  {Equal}
      n := 0;
      While (n < size) and (bytes (a) [n] = bytes (b) [n]) do
        inc (n);
      equal := n = size
    End;  {Equal}


  PROCEDURE Compress (var removed: byte; var board: board_type);
    {Puts the last item in the list into the hole}
    Var
      last_group, x, y: byte;
      buffer: group_pointer;
    Begin  {Compress}
      last_group := removed;
      While board.groups [last_group + 1]^.size <> 0 do
        Inc (last_group);
      If last_group <> removed
        Then Begin
               For x := 1 to board_size do
                 For y := 1 to board_size do
                   With board.groups [last_group]^.interpretation [x, y] do
                     If member
                       Then board.data [x, y].group := removed;
               buffer := board.groups [removed];
               board.groups [removed] := board.groups [last_group];
               board.groups [last_group] := buffer;
               board.groups [last_group]^.size := 0
             End
        Else board.groups [removed]^.size := 0
    End;  {Compress}


  PROCEDURE Find_Member (var group: group_type; var member: location_type);
    {Sets member to the coordinates of a member of the group}
    Var
      x, y: byte;
    Begin  {Find Member}
      x := 1;
      y := 1;
      member.x := 0;
      Repeat
        If group.interpretation [x, y].member
          Then Begin
                 member.x := x;
                 member.y := y
               End;
        Inc (x);
        If x > board_size
          Then Begin
                 x := 1;
                 Inc (y)
               End
      Until member.x <> 0
    End;  {Find Member}


  PROCEDURE Swap_Values (var node_1, node_2: location_list);
    {Swaps the location and value fields of the two nodes.  LOCAL}
    Var
      location_buffer: location_type;
      value_buffer: integer;
    Begin  {Swap Values}
      location_buffer := node_1^.location;
      value_buffer := node_1^.value;
      node_1^.location := node_2^.location;
      node_1^.value := node_2^.value;
      node_2^.location := location_buffer;
      node_2^.value := value_buffer
    End;  {Swap Values}


  PROCEDURE Filter_Down (var list: location_list; var x, y: byte; var value: integer);
    {The ugly part of highest_rated.  Moves the new item along the list}
    {until it meets a higher-value item.  LOCAL}
    Var
      this, that: location_list;
      swapped: boolean;
    Begin  {Filter Down}
      New (this);
      this^.location.x := x;
      this^.location.y := y;
      this^.value := value;
      this^.next := nil;
      that := list;
      Repeat
        If this^.value > that^.value
          Then Begin
                 Swap_Values (this, that);
                 If this^.next = nil
                   Then Dispose (this);
                 this := that;
                 that := that^.next;
                 swapped := true
               End
          Else swapped := false
      Until (that = nil) or (not swapped);
      If that = list  {The new item did not make the list}
        Then Dispose (this)
    End;  {Filter Down}


  FUNCTION Highest_Rated (var rating: integer_board): location_list;
    {Generates a list of the search_breadth highest rated locations, with}
    {the highest-rated ones at the bottom}
    Var
      x, y: byte;
      best_moves, this_move, next_move: location_list;
    Begin  {Highest Rated}
      best_moves := nil;
      For x := 1 to search_breadth do
        Begin
          New (this_move);
          this_move^.value := 0;
          this_move^.next := best_moves;
          best_moves := this_move
        End;
      For x := 1 to board_size do
        For y := 1 to board_size do
          If rating [x, y] > 0
            Then Filter_Down (best_moves, x, y, rating [x, y]);
      this_move := best_moves;  {Get rid of any leftover empty moves}
      While best_moves^.value = 0 do
        best_moves := best_moves^.next;
      While this_move <> best_moves do
        Begin
          next_move := this_move^.next;
          Dispose (this_move);
          this_move := next_move
        End;
      highest_rated := best_moves
    End;  {Highest Rated}


  PROCEDURE Clean_Out_Location_List (var list: location_list);
    {Deallocates the list}
    Var
      next: location_list;
    Begin  {Clean Out Location List}
      While list <> nil do
        Begin
          next := list^.next;
          Dispose (list);
          list := next
        End
    End;  {Clean Out Location List}


  FUNCTION Count_Groups (var board: board_type): byte;
    {Debugging function--count the number of groups}
    Var
      tally: byte;
    Begin  {Count Groups}
      tally := 0;
      While board.groups [tally + 1]^.size <> 0 do
        Inc (tally);
      count_groups := tally
    End;  {Count Groups}


END.  {Arcana}
SHAR_EOF
fi
if test -f 'brdhndlr.pas'
then
	echo shar: "will not over-write existing file 'brdhndlr.pas'"
else
cat << \SHAR_EOF > 'brdhndlr.pas'
UNIT BrdHndlr;
  {Board handling routines for Fumiko}


INTERFACE


  USES
    CRT, GloblCTV, Arcana;


  FUNCTION Legal_Move (var x, y: byte; player: color_type; var board: board_type): boolean;
    {Returns true if the move in question is legal}


  FUNCTION Suicidal (var x, y: byte; var player: color_type; var board: board_type): boolean;
    {Returns true if the move in question would result in its own capture}


  PROCEDURE Draw_Board (var board: board_type);
    {Draws the board on the screen}


  PROCEDURE Get_User_Move (player: color_type; var board: board_type);
    {Gets a move from the user and updates the board}


  PROCEDURE Pass (var board: board_type);
    {Increments the pass and turn counters and clears the ko variable}


  PROCEDURE Play_At (var x, y: byte; var player: color_type; var board: board_type; depth: byte);
    {Plays at the indicated place and updates the groups, passes,}
    {turn, and ko variables}


  PROCEDURE Estimate_Score (var board: board_type; var b, w: integer; var points: integer_board);
    {Calculates influence for each point and adds up the score}


  PROCEDURE Show_Score (var board: board_type);
    {Estimates the score and shows the influence around the board}


  PROCEDURE Save_Board (var board: board_type; var filename: string);
    {Saves the board to a file and increments board_number}


  PROCEDURE Load_Board (var board: board_type; var filename: string);
    {Loads board from a file}


  PROCEDURE Delete_Board (var filename: string);
    {Deletes a saved board}


  PROCEDURE Show_Final_Score (var board: board_type);
    {Removes groups of aliveness 1-2, counts the score, and displays it}


IMPLEMENTATION


  USES
    GroupMgr, Sequence;


  FUNCTION A_Neighboring_Point_Is_Unoccupied (var x, y: byte; var board: board_type): boolean;
    {Returns true if any adjacent point is empty.  LOCAL}
    Var
      direction: direction_type;
      neighbor: location_type;
      found_one: boolean;
    Begin  {A Neighboring Point Is Unoccupied}
      found_one := false;
      For direction := north to west do
        Begin
          neighbor := neighbors [x, y, direction];
          If on_board (neighbor.x, neighbor.y) and (board.data [neighbor.x, neighbor.y].color = empty)
            Then found_one := true
        End;
      a_neighboring_point_is_unoccupied := found_one
    End;  {A Neighboring Point Is Unoccupied}


  FUNCTION Legal_Move (var x, y: byte; player: color_type; var board: board_type): boolean;
    {Returns true if the move in question is legal}
    Begin  {Legal Move}
      If board.data [x, y].color <> empty
        Then legal_move := false
        Else If a_neighboring_point_is_unoccupied (x, y, board)
               Then legal_move := true
               Else legal_move := not ((board.ko.x = x) and (board.ko.y = y))
    End;  {Legal Move}


  FUNCTION Suicidal (var x, y: byte; var player: color_type; var board: board_type): boolean;
    {Returns true if the move in question would result in its own capture}
    Var
      direction: direction_type;
      safe: boolean;
      adjacent_point: location_type;
    Begin  {Suicidal}
      If a_neighboring_point_is_unoccupied (x, y, board)
        Then safe := true
        Else Begin
               safe := false;
               For direction := north to west do
                 Begin
                   adjacent_point := neighbors [x, y, direction];
                   If on_board (adjacent_point.x, adjacent_point.y)
                     Then If board.data [adjacent_point.x, adjacent_point.y].color = player
                            Then Begin
                                   If board.groups [board.data [adjacent_point.x, adjacent_point.y].group]^.liberties > 1
                                     Then safe := true
                                 End
                            Else If (board.data [adjacent_point.x, adjacent_point.y].color in [black_stone, white_stone])
                                      and (board.groups [board.data [adjacent_point.x, adjacent_point.y].group]^.liberties = 1)
                                   Then safe := true
                 End
             End;
      suicidal := not safe
    End;  {Suicidal}


  PROCEDURE Draw_Board (var board: board_type);
    {Draws the board on the screen}
    Var
      x, y: byte;
      c: char;
    Begin  {Draw Board}
      ClrScr;
      WriteLn (board_index_line);
      For y := board_size downto 1 do
        Begin
          Write (y:2, ' ');
          For x := 1 to board_size do
            Begin
              c := screen_char (board.data [x, y].color);
              If (c = #250) and (x in [4, 10, 16]) and (y in [4, 10, 16])
                Then c := '+';
              Write (c, ' ')
            End;
          WriteLn (y)
        End;
      WriteLn (board_index_line);
      WriteLn
    End;  {Draw Board}


  PROCEDURE Show_Influence (var group: group_type);
    {Displays the status of a group.  LOCAL}
    Var
      x, y: byte;
    Begin  {Show Influence}
      ClrScr;
      WriteLn (board_index_line);
      For y := board_size downto 1 do
        Begin
          Write (y:2, ' ');
          For x := 1 to board_size do
            If group.interpretation [x, y].too_distant
              Then Write ('. ')
              Else If group.interpretation [x, y].connected
                     Then Write ('@ ')
                     Else Write ('+ ');
          WriteLn (y)
        End;
      WriteLn (board_index_line);
      WriteLn;
      WriteLn ('Size: ', group.size, '   Liberties: ', group.liberties);
      WriteLn ('Aliveness: ', group.aliveness, '   Room: ', group.room);
      WriteLn ('To kill: ', group.to_kill.x, ',', group.to_kill.y, '   To save: ', group.to_save.x, ',', group.to_save.y);
      WriteLn ('(Return to continue)');
      ReadLn
    End;  {Show Influence}


  PROCEDURE Show_Points_To_Attack (var group: group_type; var board: board_type);
    {Performs and displays results of find_points_to_attack.  LOCAL}
    Var
      result: boolean_board;
      x, y: byte;
      points, here: location_list;
    Begin  {Show Points To Attack}
      points := critical_points_for (group, board);
      ClrScr;
      WriteLn ('Points to attack that group: ');
      WriteLn;
      Draw_Board (board);
      here := points;
      While here <> nil do
        Begin
          GotoXY (2 + (2 * here^.location.x), 2 + board_size - here^.location.y);
          Write ('x');
          here := here^.next
        End;
      GotoXY (1, 4 + board_size);
      WriteLn ('(Return to continue)');
      Clean_Out_Location_List (points);
      ReadLn
    End;  {Show Points To Attack}


  PROCEDURE Get_User_Move (player: color_type; var board: board_type);
    {Gets a move from the user and updates the board}
    Var
      move: string;
      x, y: byte;
      error_code: integer;
      legal: boolean;
      command: char;
    Begin  {Get User Move}
      Repeat
        Draw_Board (board);
        Write ('Turn #', board.turn, '   ');
        If player = black_stone
          Then Write ('Black (*) to move: ')
          Else Write ('White (O) to move: ');
        ReadLn (move);
        legal := true;
        command := ' ';
        If move <> 'pass'
          Then Begin
                 If move [1] in ['@', '!', '?', '<']
                   Then Begin
                          legal := false;
                          command := move [1];
                          move := copy (move, 2, 3)
                        End;
                 Case upcase (move [1]) of
                   'A'..'H': x := Ord (upcase (move [1])) - Ord ('A') + 1;
                   'J'..'T': x := Ord (upcase (move [1])) - Ord ('A')
                   Else legal := false
                 End;
                 Val (copy (move, 2, 2), y, error_code);
                 If legal
                   Then legal := (error_code = 0) and (on_board (x, y));
                 If legal
                   Then legal := legal_move (x, y, player, board)
               End;
        If not legal
          Then If command <> ' '
                 Then Begin
                        If (on_board (x, y) and (board.data [x, y].group <> 0)) or (command in ['?', '<'])
                          Then Case command of
                                 '@': Show_Influence (board.groups [board.data [x, y].group]^);
                                 '!': Show_Points_To_Attack (board.groups [board.data [x, y].group]^, board);
                                 '?': Begin
                                        WriteLn (strategy (board, player));
                                        WriteLn ('(Return to continue)');
                                        ReadLn
                                      End;
                                 '<': Begin
                                        Load_Board (board, last_board_filename);
                                        Draw_Board (board)
                                      End
                               End
                      End
                 Else Begin
                        Write ('That is not a legal move -- return to continue');
                        ReadLn
                      End
      Until legal;
      Save_Board (physical_board, last_board_filename);
      If move = 'pass'
        Then Pass (board)
        Else Play_At (x, y, player, board, 0)
    End;  {Get User Move}


  PROCEDURE Pass (var board: board_type);
    {Increments the pass and turn counters and clears the ko variable}
    Begin  {Pass}
      Inc (board.passes);
      Inc (board.turn);
      board.ko.x := 0;
      board.ko.y := 0
    End;  {Pass}


  PROCEDURE Update_Relevant_Groups (var board: board_type; var changed: boolean_board; var depth: byte);
    {An ugly part of play_at.  Updates all groups who do not consider all of}
    {the affected points too_distant.  LOCAL}
    Var
      color: color_type;
      group, x, y: byte;
    Begin  {Update Relevant Groups}
      group := 1;
      While board.groups [group]^.size <> 0 do
        Begin
          For x := 1 to board_size do
            For y := 1 to board_size do
              If changed [x, y] and (not board.groups [group]^.interpretation [x, y].too_distant)
                   and (board.groups [group]^.last_update < board.turn)
            Then Update_Interpretation (group, board, depth);
          Inc (group)
        End
    End;  {Update Relevant Groups}


  PROCEDURE Play_At (var x, y: byte; var player: color_type; var board: board_type; depth: byte);
    {Plays at the indicated place and updates the groups, passes,}
    {turns, and ko variables}
    Var
      direction: direction_type;
      neighbor: location_type;
      stones_captured: byte;
      changes: boolean_board;
      trickle: text;
    Begin  {Play At}
      board.data [x, y].color := player;
      stones_captured := 0;
      Make_New_Group (x, y, board);
      changes := false_board;
      changes [x, y] := true;
      For direction := north to west do
        Begin
          neighbor := neighbors [x, y, direction];
          If on_board (neighbor.x, neighbor.y)
            Then Begin
                   If board.data [neighbor.x, neighbor.y].color = player
                     Then Begin
                            If board.data [x, y].group <> board.data [neighbor.x, neighbor.y].group
                              Then Merge_Groups (board.data [x, y].group, board.data [neighbor.x, neighbor.y].group, board)
                          End
                     Else If board.data [neighbor.x, neighbor.y].color in [black_stone, white_stone]
                            Then Begin  {It's occupied by the other player}
                                   Update_Interpretation (board.data [neighbor.x, neighbor.y].group, board, depth);
                                   If board.groups [board.data [neighbor.x, neighbor.y].group]^.liberties = 0
                                     Then Begin
                                            board.ko := neighbor;
                                            Inc (stones_captured,
                                                  board.groups [board.data [neighbor.x, neighbor.y].group]^.size);
                                            Capture (board.data [neighbor.x, neighbor.y].group, board, changes, depth)
                                          End
                                 End
                 End
        End;
      Update_Interpretation (board.data [x, y].group, board, depth);
      If (stones_captured = 0) and (board.groups [board.data [x, y].group]^.liberties = 0)
        Then Capture (board.data [x, y].group, board, changes, depth)
        Else If (stones_captured <> 1) or (board.groups [board.data [x, y].group]^.size <> 1)
                  or (board.groups [board.data [x, y].group]^.liberties <> 1)
               Then Begin
                      board.ko.x := 0;
                      board.ko.y := 0
                    End;
      Update_Relevant_Groups (board, changes, depth);
      board.passes := 0;
      If depth = 0
        Then Begin
               Assign (trickle, trickle_filename);
               Append (trickle);
               WriteLn (trickle, '+', board.turn);
               WriteLn (trickle, x);
               WriteLn (trickle, y);
               Close (trickle)
             End;
      Inc (board.turn)
    End;  {Play At}


  PROCEDURE Estimate_Score (var board: board_type; var b, w: integer; var points: integer_board);
    {Calculates influence for each point and adds up the score}
    Var
      x, y, group: byte;
      influence: integer;
      color: color_type;
    Begin  {Estimate Score}
      b := 0;
      w := 0;
      For x := 1 to board_size do
        For y := 1 to board_size do
          Begin
            points [x, y] := 0;
            group := 1;
            While board.groups [group]^.size <> 0 do
              Begin
                If board.groups [group]^.interpretation [x, y].connected
                  Then Begin
                         If board.groups [group]^.interpretation [x, y].member
                           Then influence := board.groups [group]^.aliveness * 200
                           Else influence := (board.groups [group]^.aliveness * 100)
                                               div board.groups [group]^.interpretation [x, y].distance;
                         If board.groups [group]^.owner = black_stone
                           Then points [x, y] := points [x, y] + influence
                           Else points [x, y] := points [x, y] - influence
                       End;
                Inc (group)
              End;
            If points [x, y] > 0
              Then b := b + 1
              Else If points [x, y] < 0
                     Then w := w + 1
          End
    End;  {Estimate Score}


  PROCEDURE Show_Score (var board: board_type);
    {Estimates the score and shows the influence around the board}
    Var
      x, y: byte;
      b, w: integer;
      points: integer_board;
    Begin  {Show Score}
      Estimate_Score (board, b, w, points);
      ClrScr;
      WriteLn (board_index_line);
      For y := board_size downto 1 do
        Begin
          Write (y:2, ' ');
          For x := 1 to board_size do
            Case points [x, y] of
              -maxint..-1000: Write ('W ');
              -999..-100: Write ('w ');
              -99..-1: Write ('- ');
              0: Write ('. ');
              1..99: Write ('| ');
              100..999: Write ('b ');
              1000..maxint: Write ('B ')
            End;
          WriteLn (y)
        End;
      WriteLn (board_index_line);
      WriteLn;
      WriteLn ('Black: ', b, '  White: ', w);
      ReadLn
    End;  {Show Score}


  PROCEDURE Save_Board (var board: board_type; var filename: string);
    {Saves the board to a file and increments board_number}
    Var
      board_file: board_file_type;
      group_file: group_file_type;
      count: byte;
    Begin  {Save Board}
      If Copy (filename, 3, 8) <> 'PHYSICAL'
        Then Begin
               Str (board_number, filename);
               filename := drive_name + 'F' + filename;
               board_number := (board_number + 1) mod maxint
             End;
      Assign (board_file, filename + '.BRD');
      Rewrite (board_file);
      Write (board_file, board);
      Close (board_file);
      Assign (group_file, filename + '.GRP');
      Rewrite (group_file);
      count := 1;
      While (count <= max_groups) and (board.groups [count]^.size <> 0) do
        Begin
          Write (group_file, board.groups [count]^);
          Inc (count)
        End;
      Close (group_file)
    End;  {Save Board}


  PROCEDURE Load_Board (var board: board_type; var filename: string);
    {Loads board from a file}
    Var
      board_file: board_file_type;
      group_file: group_file_type;
      count: byte;
    Begin  {Load Board}
      Assign (board_file, filename + '.BRD');
      {$I-}
      Reset (board_file);
      {$I+}
      If ioresult = 0
        Then Begin
               Read (board_file, board);
               Close (board_file);
               Assign (group_file, filename + '.GRP');
               Reset (group_file);
               count := 1;
               While not eof (group_file) do
                 Begin
                   Read (group_file, board.groups [count]^);
                   Inc (count)
                 End;
               Close (group_file);
               If (count <= max_groups)
                 Then board.groups [count]^.size := 0
             End
    End;  {Load Board}


  PROCEDURE Delete_Board (var filename: string);
    {Deletes a saved board}
    Var
      deadfile: file;
    Begin  {Delete Board}
      Assign (deadfile, filename + '.BRD');
      Erase (deadfile);
      Assign (deadfile, filename + '.GRP');
      Erase (deadfile)
    End;  {Delete Board}


  PROCEDURE Show_Final_Score (var board: board_type);
    {Removes groups of aliveness 1-2, counts the score, and displays it}
    Var
      count, changes, x, y, black_neighbors, white_neighbors: byte;
      counted: boolean_board;
      black_score, white_score: real;
      score_array: integer_board;
      done: boolean;
      direction: direction_type;
      neighbor: location_type;
    Begin  {Show Final Score}
      count := 1;
      time_left := maxint;
      WriteLn ('Counting score...');
      Repeat
        changes := 0;
        While board.groups [count]^.size <> 0 do
          Begin
            Update_Interpretation (count, board, 0);
            If board.groups [count]^.aliveness < 3
              Then Begin
                     Capture (count, board, counted, 0);
                     Inc (changes)
                   End
              Else Inc (count);
            WriteLn ('Next: size ', board.groups [count]^.size)
          End
      Until changes = 0;
      Draw_Board (board);
      black_score := 0;
      white_score := 0;
      score_array := zero_board;
      counted := false_board;
      Repeat
        For x := 1 to board_size do
          For y := 1 to board_size do
            If not counted [x, y]
              Then Begin
                     count := 0;
                     If board.data [x, y].color in [black_stone, white_stone]
                       Then If board.data [x, y].color = black_stone
                              Then score_array [x, y] := 12
                              Else score_array [x, y] := -12
                       Else Begin
                              white_neighbors := 0;
                              black_neighbors := 0;
                              For direction := north to west do
                                Begin
                                  neighbor := neighbors [x, y, direction];
                                  If on_board (neighbor.x, neighbor.y)
                                    Then If board.data [neighbor.x, neighbor.y].color = black_stone
                                           Then Inc (black_neighbors)
                                           Else If board.data [neighbor.x, neighbor.y].color = white_stone
                                                  Then Inc (white_neighbors)
                                End;
                              Case (black_neighbors + white_neighbors) of
                                1: score_array [x, y] := (12 * black_neighbors) - (12 * white_neighbors);
                                2: score_array [x, y] := (6 * black_neighbors) - (6 * white_neighbors);
                                3: score_array [x, y] := (4 * black_neighbors) - (4 * white_neighbors);
                                4: score_array [x, y] := (3 * black_neighbors) - (3 * white_neighbors)
                                Else Begin
                                       For direction := north to west do
                                         Begin
                                           neighbor := neighbors [x, y, direction];
                                           If on_board (neighbor.x, neighbor.y)
                                             Then Begin
                                                    If counted [neighbor.x, neighbor.y]
                                                      Then Inc (count);
                                                    If score_array [neighbor.x, neighbor.y] > 0
                                                      Then Inc (black_neighbors)
                                                      Else If score_array [neighbor.x, neighbor.y] < 0
                                                           Then Inc (white_neighbors)
                                                  End
                                         End;
                                       If (black_neighbors > 0) and (white_neighbors > 0)
                                         Then score_array [x, y] := 0
                                         Else Case (black_neighbors + white_neighbors) of
                                                1: score_array [x, y] := (12 * black_neighbors) - (12 * white_neighbors);
                                                2: score_array [x, y] := (6 * black_neighbors) - (6 * white_neighbors);
                                                3: score_array [x, y] := (4 * black_neighbors) - (4 * white_neighbors);
                                                4: score_array [x, y] := (3 * black_neighbors) - (3 * white_neighbors)
                                              End
                                     End
                              End
                            End;
                     counted [x, y] := (black_neighbors > 0) or (white_neighbors > 0);
                     If counted [x, y]
                       Then If score_array [x, y] > 0
                              Then black_score := black_score + (score_array [x, y] / 12)
                              Else white_score := white_score - (score_array [x, y] / 12)
                       Else score_array [x, y] := 0
                   End;
        done := true;
        For x := 1 to board_size do
          For y := 1 to board_size do
            If not counted [x, y]
              Then done := false
      Until done;
      WriteLn;
      WriteLn ('Black:  ', black_score:6:2, '  White: ', white_score:6:2)
    End;  {Show Final Score}


END.  {BrdHndlr}
SHAR_EOF
fi
if test -f 'butler.pas'
then
	echo shar: "will not over-write existing file 'butler.pas'"
else
cat << \SHAR_EOF > 'butler.pas'
UNIT Butler;
  {Setup and cleanup procedures for Fumiko}


INTERFACE


  USES
    Dos, Crt, GloblCTV, Arcana;


  PROCEDURE Initialize;
    {Initializes all of the variables at the beginning of a run}


IMPLEMENTATION


  USES
    BrdHndlr;


  PROCEDURE Find_Neighbor_Coordinates (var x, y: byte; var direction: direction_type; var neighbor: location_type);
    {Finds the coordinates of a point's neighbor in the specified direction,}
    {and returns (0, 0) if the result is off the board.  LOCAL}
    Begin  {Find Neighbor Coordinates}
      neighbor.x := x;
      neighbor.y := y;
      Case direction of
        north: neighbor.y := y + 1;
        south: neighbor.y := y - 1;
        east: neighbor.x := x + 1;
        west: neighbor.x := x - 1;
        nw: Begin
              neighbor.x := x - 1;
              neighbor.y := y + 1
            End;
        sw: Begin
              neighbor.x := x - 1;
              neighbor.y := y - 1
            End;
        se: Begin
              neighbor.x := x + 1;
              neighbor.y := y - 1
            End;
        ne: Begin
              neighbor.x := x + 1;
              neighbor.y := y + 1
            End
      End;
      If not on_board (neighbor.x, neighbor.y)
        Then Begin
               neighbor.x := 0;
               neighbor.y := 0
             End
    End;  {Find Neighbor Coordinates}


  PROCEDURE Build_Neighbor_Array;
    {Builds the neighboring-coordinates array.  LOCAL}
    Var
      x, y: byte;
      direction: direction_type;
    Begin  {Build Neighbor Array}
      For x := 1 to board_size do
        For y := 1 to board_size do
          For direction := north to ne do
            Begin
              Find_Neighbor_Coordinates (x, y, direction, neighbors [x, y, direction]);
              If not on_board (neighbors [x, y, direction].x, neighbors [x, y, direction].y)
                Then Begin
                       neighbors [x, y, direction].x := 0;
                       neighbors [x, y, direction].y := 0
                     End
            End;
      For direction := north to ne do
        Begin
          neighbors [0, 0, direction].x := 0;
          neighbors [0, 0, direction].y := 0
        End
    End;  {Build Neighbor Array}


  PROCEDURE Clear_Off (var board: board_type);
    {Sets all board points to empty, group pointers to nil.  LOCAL}
    Var
      x, y: byte;
    Begin  {Clear Off}
      With board do
        Begin
          For x := 1 to board_size do
            For y := 1 to board_size do
              With data [x, y] do
                Begin
                  color := empty;
                  group := 0;
                End;
          For x := 1 to max_groups do
            Begin
              New (groups [x]);
              groups [x]^.size := 0;
              groups [x]^.to_kill.x := 0;
              groups [x]^.to_kill.y := 0;
              groups [x]^.to_save.x := 0;
              groups [x]^.to_save.y := 0
            End;
          ko.x := 0;
          ko.y := 0;
          passes := 0;
          turn := 1
        End
    End;  {Clear Off}


  PROCEDURE Set_Terrain (var terrain: integer_board);
    Var
      x, y: byte;
    Begin  {Set Terrain}
      terrain := zero_board;
      For x := 1 to board_size do
        For y := 1 to board_size do
          Begin
            Case line (x) of
              2, 8..board_size: Inc (terrain [x, y], 1000);
              3, 4: Inc (terrain [x, y], 5000);
              5..7: Inc (terrain [x, y], 5000 - (1000 * (line (x) - 4)))
            End;
            Case line (y) of
              2, 8..board_size: Inc (terrain [x, y], 1000);
              3, 4: Inc (terrain [x, y], 5000);
              5..7: Inc (terrain [x, y], 5000 - (1000 * (line (y) - 4)))
            End;
          End
    End;  {Set Terrain}


  PROCEDURE Load_Trickle_File (var board: board_type);
    {Tournament kludge--loads and plays saved moves}
    Var
      trickle: text;
      x, y: byte;
      move_number: integer;
      player: color_type;
    Begin  {Load Trickle File}
      Assign (trickle, trickle_filename);
      Reset (trickle);
      While not eof (trickle) do
        Begin
          ReadLn (trickle, move_number);
          ReadLn (trickle, x);
          ReadLn (trickle, y);
          If odd (move_number)
            Then player := black_stone
            Else player := white_stone;
          Play_At (x, y, player, board, maximum_search_depth)
        End;
      Close (trickle)
    End;  {Load Trickle File}


  PROCEDURE Initialize;
    {Initializes all of the variables at the beginning of a run}
    Var
      x, y: byte;
      answer: string;
      trickle: text;
    Begin  {Initialize}
      ClrScr;
      WriteLn ('FUMIKO -- An artificially intelligent Go program');
      WriteLn;
      WriteLn;
      WriteLn ('"A path is formed by walking on it."');
      WriteLn ('                          -Chuang Tsu');
      WriteLn;
      WriteLn;
      Write ('How many minutes will each player get? ');
      ReadLn (total_time);
      total_time := total_time * 60;
      time_left := total_time;
      Build_Neighbor_Array;
      Clear_Off (physical_board);
      board_index_line := '   ';
      For x := 1 to board_size do
        If x < 9
          Then board_index_line := board_index_line + Chr (x + Ord ('A') - 1) + ' '
          Else board_index_line := board_index_line + Chr (x + Ord ('A')) + ' ';
      For x := 1 to board_size do
        For y := 1 to board_size do
          Begin
            false_board [x, y] := false;
            zero_board [x, y] := 0
          End;
      Set_Terrain (terrain);
      Write ('Enter name of board drive (for example, "e:"): ');
      ReadLn (drive_name);
      last_board_filename := drive_name + 'PHYSICAL';
      trickle_filename := drive_name + 'TRICKLE.FUM';
      board_number := 1;
      WriteLN ('New game, saved board, or trickle (n/s/t)? ');
      ReadLn (answer);
      Case upcase (answer [1]) of
        'N': Begin
               Assign (trickle, trickle_filename);
               Rewrite (trickle);
               Close (trickle)
             End;
        'S': Load_Board (physical_board, last_board_filename);
        'T': Load_Trickle_File (physical_board);
      End;
      Write ('What color should I play (b/w)? ');
      ReadLn (answer);
      If upcase (answer [1]) = 'W'
        Then my_color := white_stone
        Else my_color := black_stone;
      If not odd (physical_board.turn)
        Then next_player := white_stone
        Else next_player := black_stone;
      Draw_Board (physical_board)
    End;  {Initialize}


END.  {Butler}

SHAR_EOF
fi
if test -f 'fumiko.doc'
then
	echo shar: "will not over-write existing file 'fumiko.doc'"
else
cat << \SHAR_EOF > 'fumiko.doc'
This is my first fully-functional go program, entitled Fumiko.  I am
abandoning it and beginning a new one, to be entitled "HellGo".  I wish
to make the .exe and .pas code available for everyone to browse, play with,
and learn from.

I don't know much about FTP-ing, so could you please copy the following files
up to the directory there?  They are in /home/jupiter/student/pdudey, and
should be readable.

SOURCE CODE
  globlctv.pas
  arcana.pas
  butler.pas
  groupmgr.pas
  brdhndlr.pas
  tactics.pas
  sequence.pas
  fumiko.pas  (The main program)

EXECUTABLE CODE  (IBM compatible)
  fumiko.exe

NECESSARY SPARE FILE
  trickle.fum

  That last one isn't actually -necessary-, but the program needs to have an
extant file of that name.

  A WORD OF WARNING:  When doing lookahead, Fumiko writes to disk quite often.
If you can install a ramdrive of 1 meg or bigger, do it!

  The program is written in Turbo Pascal 6.0.

------------------------------------------------------------------------------
When you run the program, she (black = yin = female) will ask how much time
each player gets.  She's supposed to panic when she gets low on time;  that
feature doesn't work perfectly, but I'm starting on a new program and don't
want to mess with it;  if you care, you've got the source code!  (Look in
arcana and sequence, I think).

A saved game must consist of two files:  physical.brd and physical.grp.
Fumiko saves the game every turn.

Loading from the trickle file replays all of the moves that have been played.
You can edit this file (to correct typos &c)--each move is a turn number
(preceded by a '+'), and row, and a column.

At the 'to move' prompt, type coordinates--e.g., 'b13', or 'pass'.

You can also type '<' to back up.  THIS WILL MESS UP THE TRICKLE FILE.

Typing @b6 will give you the status of the group at b6.  Typing !b6 will
suggest points to attack it.  Typing ? will suggest a strategy.

I think that does it.  Email me at pdudey@willamette.edu if you have any
questions, comments, or suggestions.  I plan to make HellGo public when it
is done, too;  I'm working on a grant, so I don't have to keep my stuff
secret!

SHAR_EOF
fi
if test -f 'fumiko.pas'
then
	echo shar: "will not over-write existing file 'fumiko.pas'"
else
cat << \SHAR_EOF > 'fumiko.pas'
PROGRAM Fumiko;
  {Artificially intelligent, learning Go program}


USES
  GloblCTV, Arcana, Butler, GroupMgr, BrdHndlr, Sequence;


BEGIN  {Fumiko}
  Initialize;
  Repeat
    If next_player = my_color
      Then Play_Move (physical_board, my_color, 0)
      Else Begin
             Get_User_Move (next_player, physical_board);
             Draw_Board (physical_board)
           End;
    next_player := opposite (next_player)
  Until physical_board.passes >= 2;
  WriteLn;
  Write ('(Return to remove dead groups)');
  ReadLn;
  Show_Final_Score (physical_board);
  WriteLn;
  Delete_Board (last_board_filename);
  Write ('(Return to go back to DOS)');
  ReadLn
END.  {Fumiko}

SHAR_EOF
fi
if test -f 'globlctv.pas'
then
	echo shar: "will not over-write existing file 'globlctv.pas'"
else
cat << \SHAR_EOF > 'globlctv.pas'
UNIT GloblCTV;
  {Global constants, types, and variables for Fumiko}


INTERFACE


  CONST

    board_size = 19;

    board_size_squared = board_size * board_size;

    maximum_search_depth = 4;

    search_breadth = 3;

    max_groups = 180;


  TYPE

    direction_type = (north, south, east, west, nw, sw, se, ne);

    color_type = (black_stone, white_stone, empty, off_board);  {'black' and 'white' are used by the CRT unit}

    location_type = record
                      x, y: byte
                    end;

    location_list = ^location_list_node;

    location_list_node = record
                           value: integer;
                           location: location_type;
                           next: location_list
                         end;

    boolean_board = array [1..board_size, 1..board_size] of boolean;

    integer_board = array [1..board_size, 1..board_size] of integer;

    point_interpretation = record
                             too_distant, member, adjacent, connected: boolean;
                             distance: byte
                           end;

    board_interpretation = array [1..board_size, 1..board_size] of point_interpretation;

    group_pointer = ^group_type;

    group_type = record
                   size, liberties, aliveness, room: byte;
                   last_update: integer;
                   owner: color_type;
                   interpretation: board_interpretation;
                   to_kill, to_save: location_type
                 end;

    board_space = record
                    color: color_type;
                    group: byte
                  end;

    board_type = record
                   data: array [1..board_size, 1..board_size] of board_space;  {0, 0 is an off_board space}
                   ko: location_type;
                   passes: byte;
                   turn: integer;
                   groups: array [1..max_groups] of group_pointer
                 end;

    board_file_type = file of board_type;

    group_file_type = file of group_type;


  VAR
    physical_board: board_type;
    neighbors: array [0..board_size, 0..board_size, direction_type] of location_type;
    false_board: boolean_board;
    board_index_line: string;
    board_number: longint;
    my_color: color_type;
    zero_board, terrain: integer_board;
    total_time, time_left: word;
    last_board_filename, drive_name, trickle_filename: string;
    next_player: color_type;


IMPLEMENTATION

END.  {GloblCTV}

SHAR_EOF
fi
if test -f 'groupmgr.pas'
then
	echo shar: "will not over-write existing file 'groupmgr.pas'"
else
cat << \SHAR_EOF > 'groupmgr.pas'
UNIT GroupMgr;
  {Group manager for Fumiko}


INTERFACE


  USES
    CRT, GloblCTV, Arcana, Butler;


  PROCEDURE Make_New_Group (var x, y: byte; var board: board_type);
    {Builds a new group containing one stone, in terms of size and members}
    {only, and adds it to the group list}


  PROCEDURE Merge_Groups (a, b: byte; var board: board_type);
    {Merges two groups, in terms of size and members only,}
    {and removes group b from the group list}


  FUNCTION Maneuvering_Room (var x, y: byte; var owner: color_type; var board: board_type): boolean;
    {Returns true if the point is not controlled by the other player}


  PROCEDURE Update_Interpretation (var group: byte; var board: board_type; depth: byte);
    {Updates a group's interpretation of its surroundings}


  PROCEDURE Capture  (group: byte; var board: board_type; var changes: boolean_board; depth: byte);
    {Removes the offending stones from the board and their group from the list}
    {Also updates the liberties of nearby groups}


IMPLEMENTATION


  USES
    Sequence;


  PROCEDURE Make_New_Group (var x, y: byte; var board: board_type);
    {Builds a new group containing one stone, in terms of size and members}
    {only, and adds it to the group list}
    Var
      a, b: byte;
    Begin  {Make New Group}
      board.data [x, y].group := 1;
      While board.groups [board.data [x, y].group]^.size <> 0 do
        Inc (board.data [x, y].group);
      With board.groups [board.data [x, y].group]^ do
        Begin
          size := 1;
          aliveness := 4;
          room := 0;
          last_update := 0;
          owner := board.data [x, y].color;
          to_kill.x := 0;
          to_kill.y := 0;
          For a := 1 to board_size do  {List members in minimal interpretation}
            For b := 1 to board_size do
              interpretation [a, b].member := false;
          interpretation [x, y].member := true
        End;
      board.groups [board.data [x, y].group + 1]^.size := 0
    End;  {Make New Group}


  PROCEDURE Merge_Groups (a, b: byte; var board: board_type);
    {Merges two groups, in terms of size and members only,}
    {and removes group b from the group list}
    Var
      x, y: byte;
    Begin  {Merge Groups}
      board.groups [a]^.size := board.groups [a]^.size + board.groups [b]^.size;
      If board.groups [b]^.aliveness > board.groups [a]^.aliveness  {The new group has the stronger of the two lives}
        Then board.groups [a]^.aliveness := board.groups [b]^.aliveness;
      If board.groups [b]^.last_update < board.groups [a]^.last_update  {The new group has the least recent update}
        Then board.groups [a]^.last_update := board.groups [b]^.last_update;
      For x := 1 to board_size do
        For y := 1 to board_size do
          If board.groups [a]^.interpretation [x, y].member or board.groups [b]^.interpretation [x, y].member
            Then Begin
                   board.groups [a]^.interpretation [x, y].member := true;
                   board.data [x, y].group := a
                 End;
      Compress (b, board)
    End;  {Merge Groups}


  FUNCTION Maneuvering_Room (var x, y: byte; var owner: color_type; var board: board_type): boolean;
    {Returns true if the point is not controlled by the other player}
    Var
      direction: direction_type;
      theirs: byte;
      neighbor: location_type;
    Begin  {Maneuvering Room}
      If (board.data [x, y].color = empty) or
           ((board.data [x, y].color = opposite (owner)) and (board.groups [board.data [x, y].group]^.aliveness < 3))
        Then Begin
               theirs := 0;
               For direction := north to ne do
                 Begin
                   neighbor := neighbors [x, y, direction];
                   If on_board (neighbor.x, neighbor.y)
                     Then With board.data [neighbors [x, y, direction].x, neighbors [x, y, direction].y] do
                            If (color = opposite (owner)) and (board.groups [group]^.aliveness > 2)
                              Then If direction in [north..west]
                                     Then Inc (theirs, 2)
                                     Else Inc (theirs, 1)
                 End;
               maneuvering_room := (theirs = 0) or ((theirs = 1) and (not on_edge (x)) and (not on_edge (y)))
             End
        Else maneuvering_room := false
    End;  {Maneuvering Room}


  PROCEDURE Consider_Point (var x, y: byte; var group: group_type; var interpretation: point_interpretation;
                             var board: board_type; var distance: byte);
    {The ugly part of look_around.  Updates the interpretation of a}
    {particular point.  LOCAL}
    Begin  {Consider Point}
      If interpretation.too_distant
        Then Begin  {The group has influence here--analyze it}
               interpretation.too_distant := false;
               interpretation.distance := distance;
               If board.data [x, y].color = empty
                 Then Begin
                        If maneuvering_room (x, y, group.owner, board)
                          Then Begin
                                 interpretation.connected := true;
                                 Inc (group.room);
                               End
                      End
                 Else If board.data [x, y].color = group.owner
                        Then Begin
                               interpretation.connected := true;
                               If board.groups [board.data [x, y].group]^.aliveness > group.aliveness
                                 Then group.aliveness := board.groups [board.data [x, y].group]^.aliveness
                                 Else board.groups [board.data [x, y].group]^.aliveness := group.aliveness
                             End
                        Else If maneuvering_room (x, y, group.owner, board)
                               Then Begin
                                      interpretation.connected := true;
                                      Inc (group.room)
                                    End
               End
    End;  {Consider Point}


  PROCEDURE Look_Around (var group: group_type; distance, previous_differences: byte; var board: board_type);
    {The recursive part of update_interpretation.  Looks at points}
    {around those not labelled too_distant.  LOCAL}
    Var
      x, y, differences: byte;
      new_interpretation: ^board_interpretation;
      direction: direction_type;
      point: location_type;
    Begin  {Look Around}
      New (new_interpretation);
      new_interpretation^ := group.interpretation;
      For x := 1 to board_size do
        For y := 1 to board_size do
          If (group.interpretation [x, y].connected) and not
               ((board.data [x, y].group <> 0) and (board.groups [board.data [x, y].group]^.aliveness > 3)
                  and (board.data [x, y].color = opposite (group.owner)))
            Then For direction := north to west do
                   Begin
                     point := neighbors [x, y, direction];
                     If on_board (point.x, point.y)
                       Then Consider_Point (point.x, point.y, group, new_interpretation^ [point.x, point.y], board, distance);
                   End;
      differences := 0;
      For x := 1 to board_size do
        For y := 1 to board_size do
          Begin
            If not equal (group.interpretation [x, y], new_interpretation^ [x, y], point_interpretation_size)
              Then Inc (differences);
            group.interpretation [x, y] := new_interpretation^ [x, y]
          End;
      Dispose (new_interpretation);
      If (distance < group.aliveness) and (differences > previous_differences)
        Then Look_Around (group, distance + 1, differences, board)
    End;  {Look Around}


  PROCEDURE Clear_Out (var interpretation: board_interpretation);
    {Sets all interpretation bits to false, except member (which is}
    {unaffected) and too_distant (which is := not member).  LOCAL}
    Var
      x, y: byte;
    Begin  {Clear Out}
      For x := 1 to board_size do
        For y := 1 to board_size do
          With interpretation [x, y] do
            Begin
              too_distant := not member;
              adjacent := false;
              connected := false;
              distance := 255
            End
    End;  {Clear Out}


  PROCEDURE Consider_Adjacent_Point (var x, y: byte; var group: group_type; var board: board_type);
    {The ugly part of update_interpretation.  Updates the interpretation of}
    {a particular adjacent point.  LOCAL}
    Begin  {Consider Adjacent Point}
      If (on_board (x, y)) and (group.interpretation [x, y].too_distant)
        Then With group.interpretation [x, y] do
               Begin
                 too_distant := false;
                 adjacent := true;
                 group.interpretation [x, y].distance := 1;
                 If board.data [x, y].color = empty
                   Then Begin
                          If maneuvering_room (x, y, group.owner, board)
                            Then Begin
                                   Inc (group.room);
                                   connected := true
                                 End;
                          Inc (group.liberties)
                        End
                   Else If board.data [x, y].color in [black_stone, white_stone]
                             {It must be occupied by the opponent}
                          Then Begin
                                 If maneuvering_room (x, y, group.owner, board)
                                   Then Begin
                                          connected := true;
                                          Inc (group.room)
                                        End
                               End
               End
    End;  {Consider Adjacent Point}


  PROCEDURE Update_Interpretation (var group: byte; var board: board_type; depth: byte);
    {Updates a group's interpretation of its surroundings}
    Var
      x, y: byte;
      direction: direction_type;
      neighbor: location_type;
    Begin  {Update Interpretation}
      With board.groups [group]^ do
        Begin
          liberties := 0;
          room := 0;
          last_update := board.turn;
          Clear_Out (interpretation);
          For x := 1 to board_size do
            For y := 1 to board_size do
              If interpretation [x, y].member
                Then Begin
                       interpretation [x, y].connected := true;
                       interpretation [x, y].distance := 0;
                       For direction := north to west do
                         Begin
                           neighbor := neighbors [x, y, direction];
                           Consider_Adjacent_Point (neighbor.x, neighbor.y, board.groups [group]^, board);
                         End
                     End;
          If depth <= maximum_search_depth
            Then Begin
                   Look_Around (board.groups [group]^, 2, 0, board);
                   Update_Aliveness (board.groups [group]^, board, depth)
                 End
        End
    End;  {Update Interpretation}


  PROCEDURE Capture  (group: byte; var board: board_type; var changes: boolean_board; depth: byte);
    {Removes the offending stones from the board and their group from the list}
    {Also updates the liberties of nearby groups}
    Var
      x, y: byte;
    Begin  {Capture}
      For x := 1 to board_size do
        For y := 1 to board_size do
          If board.groups [group]^.interpretation [x, y].member
            Then Begin
                   changes [x, y] := true;
                   board.data [x, y].color := empty;
                   board.data [x, y].group := 0
                 End;
      Compress (group, board)
    End;  {Capture}


END.  {GroupMgr}

SHAR_EOF
fi
if test -f 'sequence.pas'
then
	echo shar: "will not over-write existing file 'sequence.pas'"
else
cat << \SHAR_EOF > 'sequence.pas'
UNIT Sequence;
  {Life and death and sequencing routines for Fumiko}


INTERFACE


  USES
    Dos, CRT, GloblCTV, Arcana, Butler;


  FUNCTION Alive (var group: group_type; var board: board_type; depth: byte): boolean;
    {Returns true if the group in question is unconditionally alive}


  FUNCTION Life (group: group_type; first_player: color_type; var board: board_type;
                  depth: byte; var crucial_move: location_type): byte;
    {The big one, folks!  Reads out sequences to see if the group in question}
    {can live.}


  PROCEDURE Update_Aliveness (var group: group_type; var board: board_type; var depth: byte);
    {Rates the group's life from 1 (hosed) to 7 (2 eyes)}


  FUNCTION Critical_Points_For (var group: group_type; var board: board_type): location_list;
    {Generates a list of moves to consider in attacking/defending the group}


  FUNCTION Strategy (var board: board_type; var player: color_type): string;
    {Returns a string suggesting territory, defense, or offense}


  PROCEDURE Pick_Move (var board: board_type; player: color_type; var move: location_list; depth: byte);
    {Picks a strategy and calls the appropriate tacticians}


  PROCEDURE Play_Move (var board: board_type; player: color_type; depth: byte);
    {Calls pick_move and plays it}


IMPLEMENTATION


  USES
    GroupMgr, BrdHndlr, Tactics;


  PROCEDURE Fill_Liberties (var x, y: byte; var liberty: boolean_board; var board: board_type;
                             var new_move_made: boolean; var depth: byte);
    {An ugly part of alive.  Plays opposing stones in liberties of the group}
    {in question.  LOCAL}
    Var
      a, b: byte;
      attacker: color_type;
    Begin  {Fill Liberties}
      new_move_made := false;
      attacker := opposite (board.groups [board.data [x, y].group]^.owner);
      For a := 1 to board_size do
        For b := 1 to board_size do
          If board.data [x, y].group <> 0
            Then Begin
                   If liberty [a, b]
                     Then If legal_move (a, b, attacker, board)
                               and not suicidal (a, b, attacker, board)
                            Then Begin
                                   liberty [a, b] := false;
                                   new_move_made := true;
                                   Play_At (a, b, attacker, board, 1 + maximum_search_depth)
                                 End
                 End
    End;  {Fill Liberties}


  PROCEDURE Add_Helpful_Groups (var group: byte; var liberty: boolean_board; var board: board_type; var addition: boolean);
    {Another ugly part of alive.  Finds groups that share liberties with the}
    {group in question, and add their liberties to the liberty array.  LOCAL}
    Var
      x, y, a, b: byte;
      direction: direction_type;
      neighbor: location_type;
    Begin  {Add Helpful Groups}
      addition := false;
      For x := 1 to board_size do
        For y := 1 to board_size do
          If liberty [x, y]
            Then Begin  {See if any other groups border on this liberty}
                   For direction := north to west do
                     Begin
                       neighbor := neighbors [x, y, direction];
                       If (on_board (neighbor.x, neighbor.y))
                            and (board.data [neighbor.x, neighbor.y].color = board.groups [group]^.owner)
                            and not (board.data [neighbor.x, neighbor.y].group = group)
                         Then Begin
                                For a := 1 to board_size do
                                  For b := 1 to board_size do
                                    If board.groups [board.data [neighbor.x, neighbor.y].group]^
                                        .interpretation [a, b].adjacent and
                                        (board.data [neighbor.x, neighbor.y].color = empty)
                                      Then If not liberty [a, b]
                                             Then Begin
                                                    liberty [a, b] := true;
                                                    addition := true
                                                  End
                              End
                     End
                 End
    End;  {Add Helpful Groups}


  FUNCTION Alive (var group: group_type; var board: board_type; depth: byte): boolean;
    {Returns true if the group in question is unconditionally alive}
    Var
      x, y: byte;
      member: location_type;
      liberty: boolean_board;
      new_move_made, another_group_added: boolean;
      previous_board_file: string;
    Begin  {Alive}
      Write (depth, ' ');
      Save_Board (board, previous_board_file);
      For x := 1 to board_size do
        For y := 1 to board_size do
          liberty [x, y] := group.interpretation [x, y].adjacent
                             and (board.data [x, y].color = empty);
      Find_Member (group, member);
      Repeat
        Repeat
          Fill_Liberties (member.x, member.y, liberty, board, new_move_made, depth)
        Until (not new_move_made)
                or (board.data [member.x, member.y].group = 0)
                or (board.groups [board.data [member.x, member.y].group]^.liberties = 1);
        If (not new_move_made)
             and (board.data [member.x, member.y].group <> 0)
             and (board.groups [board.data [member.x, member.y].group]^.liberties > 1)
          Then Add_Helpful_Groups (board.data [member.x, member.y].group, liberty, board, another_group_added)
          Else another_group_added := false
      Until not another_group_added;
      alive := (board.data [member.x, member.y].group <> 0)
                 and (board.groups [board.data [member.x, member.y].group]^.liberties > 1);
      Load_Board (board, previous_board_file);
      Delete_Board (previous_board_file)
    End;  {Alive}


  FUNCTION Aji  (var group: group_type; var board: board_type): byte;
    {Performs a static analysis on a group's life.  LOCAL}
    Var
      x, y: byte;
      neighbor_in_atari: boolean;
    Begin  {Aji}
      If group.liberties = 1
        Then Begin
               neighbor_in_atari := false;
               For x := 1 to board_size do
                 For y := 1 to board_size do
                   With group.interpretation [x, y] do
                     If adjacent and (board.data [x, y].color = opposite (group.owner))
                         and (board.groups [board.data [x, y].group]^.liberties = 1)
                       Then neighbor_in_atari := true;
               If neighbor_in_atari
                 Then aji := 4  {Unknown}
                 Else aji := 1  {Hosed}
             End
        Else Case group.room of
               0..1: aji := 2;  {Probably hosed}
               10..255: aji := 6  {Probably alive}
               Else aji := 4  {Unknown}
             End
    End;  {Aji}


  FUNCTION Life (group: group_type; first_player: color_type; var board: board_type;
                  depth: byte; var crucial_move: location_type): byte;
    {The big one, folks!  Reads out sequences to see if the group in question}
    {can live.}
    Var
      points_to_consider, move: location_list;
      where: location_type;
      best: byte;
      defending, found_one: boolean;
      previous_board_file: string;
    Begin  {Life}
      If (depth >= maximum_search_depth) or (group.aliveness in [1, 6, 7])
        Then life := group.aliveness
        Else Begin
               defending := first_player = group.owner;
               found_one := false;
               points_to_consider := critical_points_for (group, board);
               Find_Member (group, where);
               move := points_to_consider;
               Save_Board (board, previous_board_file);
               While move <> nil do
                 If found_one or suicidal (move^.location.x, move^.location.y, first_player, board)
                   Then Begin
                          If defending
                            Then move^.value := 0
                            Else move^.value := 8;
                          move := move^.next
                        End
                   Else Begin
                          Play_At (move^.location.x, move^.location.y, first_player, board, depth + 1);
                          If board.data [where.x, where.y].group = 0
                            Then move^.value := 0
                            Else If plenty_of_time (physical_board.turn)
                                   Then move^.value := life (board.groups [board.data [where.x, where.y].group]^,
                                                              opposite (first_player), board, depth + 1, crucial_move)
                                   Else move^.value := life (board.groups [board.data [where.x, where.y].group]^,
                                                              opposite (first_player), board, depth + 2, crucial_move);
                          If (defending and (move^.value = 7))
                               or ((not defending) and (move^.value = 1))
                            Then found_one := true;
                          move := move^.next;
                          Load_Board (board, previous_board_file)
                        End;
               Delete_Board (previous_board_file);
               If defending
                 Then best := 0
                 Else best := 8;
               move := points_to_consider;
               While move <> nil do
                 Begin
                   If defending
                     Then Begin
                            If move^.value > best
                              Then Begin
                                     best := move^.value;
                                     crucial_move := move^.location
                                   End
                          End
                     Else If move^.value < best
                            Then Begin
                                   best := move^.value;
                                   crucial_move := move^.location
                                 End;
                   move := move^.next
                 End;
               Clean_Out_Location_List (points_to_consider);
               life := best
             End
    End;  {Life}


  PROCEDURE Update_Aliveness (var group: group_type; var board: board_type; var depth: byte);
    {Rates the group's life from 1 (hosed) to 7 (2 eyes)}
    Var
      opponent_first, self_first: byte;
    Begin  {Update Aliveness}
      With group do
        If aliveness < 7  {It's not already unconditionally alive}
          Then Begin
                 aliveness := aji (group, board);
                 If aliveness = 4  {Unsettled}
                   Then If alive (group, board, depth)
                          Then aliveness := 7  {Alive}
                          Else If (depth = 0) and (plenty_of_time (board.turn) or (group.last_update = 0))
                                   {Only read sequences from physical board, and when there's time}
                                 Then Begin
                                        opponent_first := life (group, opposite (group.owner), board, depth, group.to_kill);
                                        If opponent_first > 5
                                          Then aliveness := 5  {Alive if opponent moves first}
                                          Else Begin
                                                 self_first := life (group, group.owner, board, depth, group.to_save);
                                                 If opponent_first < 3
                                                   Then aliveness := aliveness - 1;
                                                 If self_first > 5
                                                   Then aliveness := aliveness + 1
                                               End
                                      End
               End
    End;  {Update Aliveness}


  FUNCTION Attack_Potential_At (var x, y: byte; var group: group_type; var board: board_type): integer;
    {Returns an integer rating of the point as a place to attack the group}
    {LOCAL}
    Var
      direction: direction_type;
      neighbor: location_type;
      importance, tally: integer;
    Begin  {Attack Potential At}
      If legal_move (x, y, opposite (group.owner), board)
        Then Begin
               If (group.interpretation [x, y].adjacent) and (board.data [x, y].color = empty)
                 Then tally := 5000
                 Else tally := 0;
               For direction := north to ne do
                 Begin
                   neighbor := neighbors [x, y, direction];
                   If on_board (neighbor.x, neighbor.y)
                     Then Begin
                            importance := 0;
                            With group.interpretation [neighbor.x, neighbor.y] do
                              If connected or adjacent
                                Then If board.data [neighbor.x, neighbor.y].color = group.owner
                                       Then importance := 10
                                       Else If adjacent and (board.data [neighbor.x, neighbor.y].color = empty)
                                              Then importance := 5
                                              Else importance := 2;
                            If direction in [north..west]
                              Then importance := importance * 141
                              Else importance := importance * 100;
                            tally := tally + importance
                          End
                 End;
               attack_potential_at := tally
             End
        Else attack_potential_at := 0
    End;  {Attack Potential At}


  FUNCTION Critical_Points_For (var group: group_type; var board: board_type): location_list;
    {Generates a list of points to consider in attacking/defending the group}
    Var
      x, y: byte;
      rating: integer_board;
    Begin  {Critical Points For}
      For x := 1 to board_size do
        For y := 1 to board_size do
          rating [x, y] := Attack_Potential_At (x, y, group, board);
      critical_points_for := highest_rated (rating)
    End;  {Critical Points For}


  FUNCTION Strategy (var board: board_type; var player: color_type): string;
    {Returns a string suggesting territory, defense, or offense}
    Var
      b, w: integer;
      count: byte;
      points: integer_board;
      i_have_a_weak_group, he_has_a_weak_group: boolean;
    Begin  {Strategy}
      Estimate_Score (board, b, w, points);
      If b + w < board_size_squared div 2
        Then strategy := 'Grab for some of that empty territory'
        Else Begin
               i_have_a_weak_group := false;
               he_has_a_weak_group := false;
               count := 1;
               Repeat
                 If board.groups [count]^.aliveness in [3..5]
                   Then If board.groups [count]^.owner = player
                          Then i_have_a_weak_group := true
                          Else he_has_a_weak_group := true;
                 Inc (count)
               Until (board.groups [count]^.size = 0) or (i_have_a_weak_group and he_has_a_weak_group);
               If abs (w - b) < board_size
                 Then If i_have_a_weak_group
                        Then strategy := 'Defend your groups'
                        Else If he_has_a_weak_group
                               Then strategy := 'Attack the opponent''s groups'
                               Else strategy := 'It''s close--push'
                 Else If ((w > b) and (player = black_stone)) or ((b > w) and (player = white_stone))
                        Then If he_has_a_weak_group
                               Then strategy := 'You need to kill something'
                               Else strategy := 'You''re in trouble--invade'
                        Else If i_have_a_weak_group
                               Then strategy := 'Keep your lead--defend your groups'
                               Else strategy := 'You''re winning--seal it up'
             End
    End;  {Strategy}


  PROCEDURE Pick_Move (var board: board_type; player: color_type; var move: location_list; depth: byte);
    {Picks a strategy and calls the appropriate tacticians}
    Var
      count: byte;
      b, w: integer;
      points: integer_board;
      i_have_a_weak_group, he_has_a_weak_group: boolean;
    Begin  {Pick Move}
      move := nil;
      Estimate_Score (board, b, w, points);
      If b + w < board_size_squared div 2
        Then Grab_Territory (board, player, move, points, depth)
        Else Begin
               i_have_a_weak_group := false;
               he_has_a_weak_group := false;
               count := 1;
               Repeat
                 If board.groups [count]^.aliveness in [4..5]
                   Then If board.groups [count]^.owner = player
                          Then i_have_a_weak_group := true
                          Else he_has_a_weak_group := true;
                 Inc (count)
               Until (board.groups [count]^.size = 0) or (i_have_a_weak_group and he_has_a_weak_group);
               If abs (w - b) < board_size
                 Then If i_have_a_weak_group
                        Then Defend_Groups (board, player, move, depth)
                        Else If he_has_a_weak_group
                               Then Attack_Opponent (board, player, move, depth)
                               Else Push (board, player, move, points, depth)
                 Else If ((w > b) and (player = black_stone)) or ((b > w) and (player = white_stone))
                        Then If he_has_a_weak_group
                               Then Attack_Opponent (board, player, move, depth)
                               Else Invade (board, player, move, points, depth)
                        Else If i_have_a_weak_group
                               Then Defend_Groups (board, player, move, depth)
             End;
      If move = nil
        Then Seal_Up (board, player, move, points, depth);
      If move = nil
        Then Push (board, player, move, points, depth);
      If (move = nil) and (i_have_a_weak_group)
        Then Defend_Groups (board, player, move, depth);
      If (move = nil) and (he_has_a_weak_group)
        Then Attack_Opponent (board, player, move, depth);
      If move = nil
        Then Invade (board, player, move, points, depth);
      If move = nil
        Then Begin
               New (move);
               move^.location.x := 0;
               move^.location.y := 0;
               move^.next := nil
             End
    End;  {Pick Move}


  PROCEDURE Play_Move (var board: board_type; player: color_type; depth: byte);
    {Calls pick_move and plays it}
    Var
      move, good_moves: location_list;
      start_hour, start_minute, start_second, start_hundredths, hour, minute, second, hundredths: word;
      move_coordinates: string;
    Begin  {Play Move}
      WriteLn ('I have ', time_left div 60, ' minutes left');
      GetTime (start_hour, start_minute, start_second, start_hundredths);
      Pick_Move (board, player, good_moves, depth);
      move := good_moves;
      While move^.next <> nil do
        move := move^.next;
      If on_board (move^.location.x, move^.location.y)
        Then Begin
               Str (move^.location.y, move_coordinates);
               Case move^.location.x of
                 1..8: move_coordinates := chr (move^.location.x + ord ('a') - 1) + move_coordinates;
                 9..19: move_coordinates := chr (move^.location.x + ord ('a')) + move_coordinates
               End;
               WriteLn;
               WriteLn ('*** I move at ', move_coordinates, ' ***');
               Play_At (move^.location.x, move^.location.y, player, physical_board, 0)
             End
        Else If move^.location.y = 0
               Then Begin
                      WriteLn;
                      WriteLn ('*** I Pass ***');
                      Pass (board)
                    End
               Else Get_User_Move (player, board);
      WriteLn ('(Return to continue)');
      ReadLn;
      Clean_Out_Location_List (good_moves);
      GetTime (hour, minute, second, hundredths);
      If hour > start_hour
        Then Dec (time_left, (hour - start_hour) * 3600)
        Else If start_hour > hour
               Then Dec (time_left, (24 + hour - start_hour) * 3600);
      If minute > start_minute
        Then Dec (time_left, (minute - start_minute) * 60)
        Else If start_minute > minute
               Then Inc (time_left, (start_minute - minute) * 60);
      If second > start_second
        Then Dec (time_left, second - start_second)
        Else If start_second > second
               Then Inc (time_left, start_second - second);
      If (time_left > total_time) or (time_left = 0)
        Then time_left := 1
    End;  {Play Move}


END.  {Sequence}

SHAR_EOF
fi
if test -f 'tactics.pas'
then
	echo shar: "will not over-write existing file 'tactics.pas'"
else
cat << \SHAR_EOF > 'tactics.pas'
UNIT Tactics;
  {Tactical routines for Fumiko}


INTERFACE


  USES
    GloblCTV, Arcana, GroupMgr, BrdHndlr;


  PROCEDURE Grab_Territory (var board: board_type; player: color_type; var move: location_list;
                              var weights: integer_board; depth: byte);
    {Plays in wide-open areas}


  PROCEDURE Defend_Groups (var board: board_type; player: color_type; var move: location_list; depth: byte);
    {Defends weak groups}


  PROCEDURE Attack_Opponent (var board: board_type; player: color_type; var move: location_list; depth: byte);
    {Attacks weak groups}


  PROCEDURE Push (var board: board_type; player: color_type; var move: location_list; var weights: integer_board; depth: byte);
    {Plays on opponent's side of borders}


  PROCEDURE Invade (var board: board_type; player: color_type; var move: location_list;
                      var weights: integer_board; depth: byte);
    {Plays inside loose enemy territory}


  PROCEDURE Seal_Up (var board: board_type; player: color_type; var move: location_list;
                       var weights: integer_board; depth: byte);
    {Plays on own side of borders}


IMPLEMENTATION


  USES
    Sequence;


  FUNCTION Border (var x, y: byte; var weights: integer_board): boolean;
    {Returns true if the space is on the edge of an influence area.  LOCAL}
    Var
      direction: direction_type;
      neighbor: location_type;
      positive, negative: boolean;
    Begin  {Border}
      positive := false;
      negative := false;
      For direction := north to west do
        Begin
          neighbor := neighbors [x, y, direction];
          If on_board (neighbor.x, neighbor.y)
            Then Case weights [neighbor.x, neighbor.y] of
                   (-maxint - 1)..-1: negative := true;
                   0: Begin
                        positive := true;
                        negative := true
                      End;
                   1..maxint: positive := true
                 End
        End;
      border := negative and positive
    End;  {Border}


  PROCEDURE Grab_Territory (var board: board_type; player: color_type; var move: location_list;
                              var weights: integer_board; depth: byte);
    {Plays in wide-open areas}
    Var
      x, y: byte;
    Begin  {Grab Territory}
      WriteLn ('I''l take some of that territory...');
      For x := 1 to board_size do
        For y := 1 to board_size do
          If (weights [x, y] = 0) and legal_move (x, y, player, board) and not suicidal (x, y, player, board)
            Then weights [x, y] := 10000 + terrain [x, y]
            Else weights [x, y] := (10000 div abs (weights [x, y])) + terrain [x, y];
      move := highest_rated (weights)
    End;  {Grab Territory}

  PROCEDURE Defend_Groups (var board: board_type; player: color_type; var move: location_list; depth: byte);
    {Defends weak groups}
    Var
      value: integer_board;
      count, garbage: byte;
    Begin  {Defend Groups}
      WriteLn ('I''m a little nervous about these guys');
      value := zero_board;
      count := 1;
      While board.groups [count]^.size <> 0 do
        With board.groups [count]^ do
          Begin
            If (owner = player) and (aliveness in [3..5])
              Then If on_board (to_save.x, to_save.y) and legal_move (to_save.x, to_save.y, player, board)
                        and not suicidal (to_save.x, to_save.y, player, board)
                     Then Inc (value [to_save.x, to_save.y], size)
                     Else Begin
                            garbage := Life (board.groups [count]^, player, board, depth, to_save);
                            If on_board (to_save.x, to_save.y)
                              Then Inc (value [to_save.x, to_save.y], size)
                          End;
            Inc (count)
          End;
      move := highest_rated (value)
    End;  {Defend Groups}


  PROCEDURE Attack_Opponent (var board: board_type; player: color_type; var move: location_list; depth: byte);
    {Attacks weak groups}
    Var
      value: integer_board;
      count, garbage: byte;
    Begin  {Attack Opponent}
      WriteLn ('Die, Pink boy!');
      value := zero_board;
      count := 1;
      While board.groups [count]^.size <> 0 do
        With board.groups [count]^ do
          Begin
            If (owner = opposite (player)) and (aliveness in [3..5])
              Then If on_board (to_kill.x, to_kill.y) and legal_move (to_kill.x, to_kill.y, player, board)
                        and not suicidal (to_kill.x, to_kill.y, player, board)
                     Then Inc (value [to_kill.x, to_kill.y], size)
                     Else Begin
                            garbage := Life (board.groups [count]^, player, board, depth, to_kill);
                            If on_board (to_kill.x, to_kill.y)
                              Then Inc (value [to_kill.x, to_kill.y], size)
                          End;
            Inc (count)
          End;
      move := highest_rated (value)
    End;  {Attack Opponent}


  PROCEDURE Push (var board: board_type; player: color_type; var move: location_list; var weights: integer_board; depth: byte);
    {Plays on opponent's side of borders}
    Var
      x, y: byte;
      value: integer_board;
    Begin  {Push}
      WriteLn ('I just need a few more points...');
      If player = white_stone
        Then For x := 1 to board_size do
               For y := 1 to board_size do
                 weights [x, y] := - weights [x, y];
      For x := 1 to board_size do
        For y := 1 to board_size do
          If (weights [x, y] < 0) and border (x, y, weights)
               and legal_move (x, y, player, board) and not suicidal (x, y, player, board)
            Then value [x, y] := terrain [x, y] + weights [x, y]
            Else value [x, y] := 0;
      move := highest_rated (value)
    End;  {Push}


  PROCEDURE Invade (var board: board_type; player: color_type; var move: location_list;
                      var weights: integer_board; depth: byte);
    {Plays inside loose enemy territory}
    Var
      x, y: byte;
      value: integer_board;
    Begin  {Invade}
      WriteLn ('Geronimo!');
      If player = white_stone
        Then For x := 1 to board_size do
               For y := 1 to board_size do
                 weights [x, y] := - weights [x, y];
      For x := 1 to board_size do
        For y := 1 to board_size do
          If (weights [x, y] < 0) and maneuvering_room (x, y, player, board)
               and legal_move (x, y, player, board) and not suicidal (x, y, player, board)
            Then value [x, y] := terrain [x, y] + weights [x, y]
            Else value [x, y] := 0;
      move := highest_rated (value)
    End;  {Invade}


  PROCEDURE Seal_Up (var board: board_type; player: color_type; var move: location_list;
                       var weights: integer_board; depth: byte);
    {Plays on own side of borders}
    Var
      x, y: byte;
      value: integer_board;
    Begin  {Seal Up}
      WriteLn ('I don''t have to be greedy.');
      If player = white_stone
        Then For x := 1 to board_size do
               For y := 1 to board_size do
                 weights [x, y] := - weights [x, y];
      For x := 1 to board_size do
        For y := 1 to board_size do
          If (weights [x, y] > 0) and border (x, y, weights)
               and legal_move (x, y, player, board) and not suicidal (x, y, player, board)
            Then value [x, y] := (maxint - weights [x, y]) + terrain [x, y]
            Else value [x, y] := 0;
      move := highest_rated (value)
    End;  {Seal Up}


END.  {Tactics}
SHAR_EOF
fi
if test -f 'trickle.fum'
then
	echo shar: "will not over-write existing file 'trickle.fum'"
else
cat << \SHAR_EOF > 'trickle.fum'
+1
3
3

SHAR_EOF
fi
exit 0
#	End of shell archive
