
(********************************************************************)
(*                                                                  *)
(*  dnafight.sd7  Bacterial dna fight programming game              *)
(*  Copyright (C) 1985  Johannes Gritsch                            *)
(*                                                                  *)
(*  This program is free software; you can redistribute it and/or   *)
(*  modify it under the terms of the GNU General Public License as  *)
(*  published by the Free Software Foundation; either version 2 of  *)
(*  the License, or (at your option) any later version.             *)
(*                                                                  *)
(*  This program is distributed in the hope that it will be useful, *)
(*  but WITHOUT ANY WARRANTY; without even the implied warranty of  *)
(*  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the   *)
(*  GNU General Public License for more details.                    *)
(*                                                                  *)
(*  You should have received a copy of the GNU General Public       *)
(*  License along with this program; if not, write to the           *)
(*  Free Software Foundation, Inc., 51 Franklin Street,             *)
(*  Fifth Floor, Boston, MA  02110-1301, USA.                       *)
(*                                                                  *)
(********************************************************************)


const type: v_ifield is array [direction] integer;


const proc: dna (VIOLET) is func

  local
    const bactColor: SELF is VIOLET;
    const power: MINSPLITSIZE is 4;

    var direction: bestDie is HERE;
    var direction: bestEat is HERE;
    var direction: bestSplit is HERE;
    var direction: maxDir is HERE;
    var v_ifield: splitPrior is direction times 0;
    var v_ifield: eatPrior is direction times 0;
    var v_ifield: diePrior is direction times 0;
    var power: needed is 0;
    var power: ownSize is 0;
    var boolean: splitable is FALSE;
    var boolean: enemies is FALSE;
    var integer: splitSize is 0;


    const proc: findMax (inout direction: dmax, in v_ifield: field) is func

      local
        var directSet: maxDir is directSet.EMPTY_SET;
        var direction: fd is HERE;
        var integer: xmax is 0;

      begin (* findMax *)
        maxDir := {HERE};
        xmax := field[HERE];
        for fd range HERE to EAST do
          if field[fd] > xmax then
            maxDir := {fd};
            xmax := field[fd];
          elsif field[fd] = xmax then
            incl(maxDir, fd);
          end if;
        end for;
        dmax := ranDir(maxDir);
      end func; (* findMax *)


    const proc: getMax (inout direction: maxDir) is func

      local
        var power: gmax is 0;
        var direction: dir is HERE;
        var directSet: maxDirSet is directSet.EMPTY_SET;

      begin (* getMax *)
        maxDirSet := {NORTH};
        gmax := food(NORTH);
        for dir range SOUTH to SE do
          if food(dir) > gmax then
            maxDirSet := {dir};
            gmax := food(dir);
          elsif food(dir) = gmax then
            incl(maxDirSet, dir);
          end if;
        end for;
        maxDir := ranDir(maxDirSet);
      end func; (* getMax *)


    const proc: init is func

      local
        var v_ifield: next is direction times 0;
        var v_ifield: next2 is direction times 0;
        var direction: dir is HERE;
        var power: ownSize2 is 0;
        var power: needed2 is 0;
        var power: splitSub is 0;


        const proc: getPar (inout integer: eatPrior,
            inout integer: splitPrior, inout integer: next,
            inout integer: next2, in power: fodder) is func

          begin (* getPar *)
            if hunger = 0 then
              if fodder < needed2 then
                next2 := 0;
                splitPrior := 0;
              else
                next2 := ownSize2 - needed2 + min(fodder, ownSize2);
                splitPrior := fodder;
              end if;
              if fodder < needed then
                next := 0;
                eatPrior := fodder - foodReserve;
              else
                next := ownSize - needed + min(fodder, ownSize);
                eatPrior := fodder;
              end if;
            else
              next2 := ownSize2 - needed2 + min(fodder, ownSize2);
              splitPrior := fodder + 1;
              next := ownSize - needed + min(fodder, ownSize);
              eatPrior := fodder;
            end if;
          end func; (* getPar *)


      begin (* init *)
        enemies := FALSE;
        ownSize := strength(HERE);
        splitSize := 2 * food(HERE);
        splitSub := 0;
        needed := max(0, shrinkSize(ownSize));
        ownSize2 := (ownSize + 1) div 2;
        needed2 := max(0, shrinkSize(ownSize2));
        getPar(eatPrior[HERE], splitPrior[HERE], next[HERE], next2[HERE],
            food(HERE));
        ownSize2 := ownSize div 2;
        needed2 := max(0, shrinkSize(ownSize2));
        diePrior[HERE] := food(HERE);

        for dir range NORTH to EAST do

          case view(dir) of

            when {EDGE}:
              eatPrior[dir] := -initSize;
              splitPrior[dir] := 0;
              next[dir] := 0;
              next2[dir] := 0;
              splitSub +:= 2;
              diePrior[dir] := -3 * foodReserve;

            when {CLEAR}:
               getPar(eatPrior[dir], splitPrior[dir],
                      next[dir], next2[dir], food(dir));
               diePrior[dir] := food(dir);
               if food(dir) = foodReserve then
                 splitSize +:= max(5, foodReserve);
               else
                 splitSize +:= 2 * food(dir);
               end if;

            when {SELF}:
              diePrior[dir] := 0;
              diePrior[HERE] +:= 1;
              eatPrior[HERE] -:= 1;
              eatPrior[dir] := - 2 * foodReserve + 1;
              if hunger = 0 and strength(dir) < needed then
                next[dir] := 0;
              else
                if strength(dir) <= ownSize then
                  next[dir] := ownSize - needed + strength(dir);
                else
                  next[dir] := 0;
                end if;
              end if;
              splitPrior[dir] := 0;
              next2[dir] := 0;
              splitSize +:= food(dir);

            otherwise:
              diePrior[HERE] -:= 2 * strength(dir);
              enemies := TRUE;
              if hunger = 0 and strength(dir) < needed then
                diePrior[dir] := foodReserve;
                eatPrior[dir] := strength(dir) - 1;
                next[dir] := 0;
              else
                if strength(dir) <= ownSize then
                  diePrior[dir] := foodReserve + 1;
                  eatPrior[dir] := strength(dir) + 1;
                  next[dir] := ownSize - needed + strength(dir);
                else
                  diePrior[dir] := - 2 * foodReserve;
                  if next[HERE] < strength(dir) and
                      shrinkSize(strength(dir)) <= next[HERE] then
                    eatPrior[HERE] := eatPrior[HERE] - 2 * foodReserve;
                  end if;
                  eatPrior[dir] := -2 * foodReserve;
                  next[dir] := 0;
                end if;
              end if;
              splitPrior[dir] := 0;
              next2[dir] := 0;
              if next2[HERE] < strength(dir) then
                splitPrior[HERE] := 0;
              end if;
              splitSize +:= food(dir);

          end case;
        end for;

        for dir range NW to SE do

          case view(dir) of

            when {EDGE}:
              splitSub +:= 1;

            when {CLEAR}:
               if hunger > 0 and eatPrior[HERE] < needed then
                 if view(left[dir]) = CLEAR then
                   eatPrior[left[dir]] +:= (food(dir) + 1) div 2;
                   if splitPrior[left[dir]] > 0 then
                     splitPrior[left[dir]] +:= (food(dir) + 1) div 2;
                   end if;
                 end if;
                 if view(right[dir]) = CLEAR then
                   eatPrior[right[dir]] +:= (food(dir) + 1) div 2;
                   if splitPrior[right[dir]] > 0 then
                     splitPrior[right[dir]] +:= (food(dir) + 1) div 2;
                   end if;
                 end if;
               end if;
               if food(dir) = foodReserve then
                 splitSize +:= max(5, foodReserve);
               else
                 splitSize +:= food(dir);
               end if;

            when {SELF}:
              splitSize +:= (food(dir) + 1) div 2;
              if view(left[dir]) in {CLEAR, SELF} then
                diePrior[left[dir]] +:= 1;
              end if;
              if view(right[dir]) in {CLEAR, SELF} then
                diePrior[right[dir]] +:= 1;
              end if;

            otherwise:
              enemies := TRUE;
              if strength(dir) > ownSize2 then
                splitPrior[left[dir]] := 0;
                splitPrior[right[dir]] := 0;
              end if;
              if next[left[dir]] <= strength(dir) then
                eatPrior[left[dir]] -:= foodReserve;
              else
                if hunger > 1 and view(left[dir]) = CLEAR then
                  eatPrior[left[dir]] +:= strength(dir) div 2;
                end if;
              end if;
              if next[right[dir]] <= strength(dir) then
                eatPrior[right[dir]] -:= foodReserve;
              else
                if hunger > 1 and view(right[dir]) = CLEAR then
                  eatPrior[right[dir]] +:= strength(dir) div 2;
                end if;
              end if;
              splitSize +:= (food(dir) + 1) div 2;
              diePrior[left[dir]] -:= 2 * foodReserve;
              diePrior[right[dir]] -:= 2 * foodReserve;

          end case;
        end for;

        splitSize := max(MINSPLITSIZE, splitSize div (10 - splitSub));
      end func; (* init *)


  begin (* dna (VIOLET) *)
    init;
    splitable := splitPrior[HERE] > 0;
    splitPrior[HERE] := 0;
    findMax(bestSplit, splitPrior);
    if bestSplit <> HERE and splitable and ownSize > splitSize and view(bestSplit) = CLEAR then
      split(bestSplit, (ownSize + 1) div 2, ownSize div 2);
    else
      getMax(maxDir);
      if hunger = 0 and food(maxDir) < needed and food(HERE) < needed then
        findMax(bestDie, diePrior);
        if view(bestDie) in {CLEAR, EDGE, SELF} or
            strength(bestDie) > ownSize then
          eat(HERE, ownSize);
        else
          kill(bestDie);
        end if;
      else
        if food(maxDir) < needed and food(HERE) > needed then
          if hunger > 0 then
            eat(HERE, 1);
          else
            eat(HERE, needed);
          end if;
        else
          findMax(bestEat, eatPrior);
          if view(bestEat) > CLEAR and bestEat <> HERE then
            kill(bestEat);
          else
            if view(bestEat) <> EDGE then
              eat(bestEat, ownSize);
            else
              eat(HERE, ownSize);
            end if;
          end if;
        end if;
      end if;
    end if;
  end func; (* dna (VIOLET) *)
