
(********************************************************************)
(*                                                                  *)
(*  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: iField is array [direction] integer; (* array [HERE..EAST] of integer; *)


const proc: dna (BLUE) is func

  local
    const bactColor: SELF is BLUE;
    const integer: MINSPLITSIZE is 7;

    var direction: bestDie is HERE;
    var direction: bestEat is HERE;
    var direction: bestSplit is HERE;
    var direction: maxDir is HERE;
    var iField: eatMass is direction times 0;
    var iField: splitPrior is direction times 0;
    var iField: eatPrior is direction times 0;
    var 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 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 MAIN_DIRECTIONS do
          if field[fd] > xmax then
            xmax := field[fd];
            maxDir := directSet.EMPTY_SET;
            incl(maxDir, fd);
          else
            if field[fd] = xmax then
              incl(maxDir, fd);
            end if;
          end if;
        end for;
        dmax := ranDir(maxDir);
      end func; (* findMax *)


    const proc: getMax (inout direction: maxDir) is func
      local
        var direction: dir is HERE;
        var directSet: maxDirSet is directSet.EMPTY_SET;
        var power: gmax is 0;

      begin (* getMax *)
        maxDirSet := {NORTH};
        gmax := food(NORTH);
        for dir range MAIN_DIRECTIONS do
          if food(dir) > gmax then
            gmax := food(dir);
            maxDirSet := directSet.EMPTY_SET;
            incl(maxDirSet, dir);
          else
            if food(dir) = gmax then
              incl(maxDirSet, dir);
            end if;
          end if;
        end for;
        maxDir := ranDir(maxDirSet);
      end func; (* getMax *)


    const proc: init is func

      local
        var iField: next is direction times 0;
        var iField: next2 is direction times 0;
        var direction: dir is HERE;
        var power: fNeeded is 0;
        var power: konst is 0;
        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 - konst;
              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;
        konst := initSize + foodReserve;
        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);
        if hunger = 0 then
          eatMass[HERE] := 1;
        else
          eatMass[HERE] := 0;
        end if;

        for dir range MAIN_DIRECTIONS do

          case view(dir) of

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

            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;
              if food(dir) > needed then
                eatMass[HERE] := ownSize;
              end if;
              eatMass[dir] := min(ownSize, food(dir));

            when {SELF}:
              eatMass[dir] := 0;
              diePrior[dir] := -konst;
              diePrior[HERE] := diePrior[HERE] + 1;
              eatPrior[HERE] := eatPrior[HERE] - 1;
              eatPrior[dir] := - 2 * konst + 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:
              eatMass[dir] := 0;
              eatMass[HERE] := ownSize;
              diePrior[HERE] := diePrior[HERE] - 2 * strength(dir);
              enemies := TRUE;
              if strength(dir) > ownSize then
                diePrior[dir] := - 2 * konst;
              else
                diePrior[dir] := 2 * konst;
              end if;
              if hunger = 0 and strength(dir) < needed then
                eatPrior[dir] := strength(dir) - 1;
                next[dir] := 0;
              else
                if strength(dir) <= ownSize then
                  eatPrior[dir] := strength(dir) + 1;
                  next[dir] := ownSize - needed + strength(dir);
                else
                  if next[HERE] <= strength(dir) then
                    fNeeded := shrinkSize(strength(dir));
                    if fNeeded > next[HERE] then
                      eatPrior[HERE] := eatPrior[HERE] - 2 * konst;
                    end if;
                  end if;
                  eatPrior[dir] := -2 * konst;
                  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 DIAGONAL_DIRECTIONS do

          case view(dir) of

            when {EDGE}:
              splitSub := splitSub + 1;

            when {CLEAR}:
              if hunger > 0 then
                if view(left[dir]) = CLEAR then
                  eatPrior[left[dir]] +:= (food(dir) + 1) div 2;
                end if;
                if splitPrior[left[dir]] > 0 then
                  splitPrior[left[dir]] +:= (food(dir) + 1) div 2;
                end if;
                if view(right[dir]) = CLEAR then
                  eatPrior[right[dir]] +:= (food(dir) + 1) div 2;
                end if;
                if splitPrior[right[dir]] > 0 then
                  splitPrior[right[dir]] +:= (food(dir) + 1) div 2
                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 + konst * ord(view(left[dir]) = SELF);
              end if;
              if view(right[dir]) in {CLEAR, SELF} then
                diePrior[right[dir]] +:= 1 + konst * ord(view(right[dir]) = SELF);
              end if;

            otherwise:
              enemies := TRUE;
              if strength(dir) >= next2[left[dir]] then
                splitPrior[left[dir]] := 0;
              end if;
              if strength(dir) >= next2[right[dir]] then
                splitPrior[right[dir]] := 0;
              end if;
              if next[left[dir]] <= strength(dir) then
                eatPrior[left[dir]] -:= konst;
              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]] -:= konst;
              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 * konst;
              diePrior[right[dir]] -:= 2 * konst;
          end case;
        end for;

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


  begin (* dna (BLUE) *)
    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 bestDie = HERE or view(bestDie) = CLEAR then
          eat(bestDie, eatMass[bestDie]);
        else
          eat(HERE, eatMass[HERE]);
        end if;
      else
        if food(maxDir) < needed and not enemies then
          if food(HERE) > needed or not splitable or bestSplit = HERE or
              ownSize < 4 or view(bestSplit) <> CLEAR then
            eat(HERE, eatMass[HERE]);
          else
            split(bestSplit, (ownSize + 1) div 2, ownSize div 2);
          end if;
        else
          findMax(bestEat, eatPrior);
          if view(bestEat) = EDGE then
            eat(HERE, eatMass[HERE]);
          end if;
          if bestEat <> HERE and view(bestEat) <> CLEAR then
            if strength(bestEat) > ownSize then
              eat(HERE, eatMass[HERE]);
            else
              kill(bestEat);
            end if;
          else
            eat(bestEat, eatMass[bestEat]);
          end if;
        end if;
      end if;
    end if;
  end func; (* dna (BLUE) *)
