15
\$\begingroup\$

Background

You awake on the ground. Above you is a giant stepped pyramid-like structure. Based on your ancient history lessons, you recognize it as a ziggurat. You have been transported by Dr. Sargostein back into ancient Mesopotamia to play a game.

In this King of the Hill Ziggurat challenge, you will write a bot that tries to stay as far up the ziggurat as possible. You may use Java, or any language that can interface with it (i.e. Scala, Groovy, Kotlin). I have implemented a socket protocol as well, if you wish to use other languages. The ziggurat (and the map that you will be playing on) is shaped as follows:

11111111111
12222222221
12333333321
12344444321
12345554321
12345654321
12345554321
12344444321
12333333321
12222222221
11111111111

Where the number indicates the elevation of that area. Coordinates start at the top left corner and increase down and right.

There will be five rounds, each consisting of 50, 100, 250, 500, and 1000 turns. Each turn will proceed as follows: each bot will tick, moving in one of 8 directions (see here), as well as increasing its "strength" stat depending on the bot's level: 4 if on levels 1 and 2, 2 on levels 3-5, and 0 on 6. Once all moves have been queued, the map will be updated for all bots simultaneously. Then, if any 2 bots are in the same tile, they will decide whether or not to fight. If neither want to fight, nothing happens. If one wants to fight and not the other, the one that does not want to fight steps a level down. If both want to fight, each bot generates a random number between its "strength" value and floor(strength / 2). The bot with a higher number wins and loses 10% of its strength. The loser goes down a level and loses 20% of its strength. If there is a tie, nothing happens. If there are 3 or more bots in the same location, they will fight in an order determined by Java's HashMap (I don't know the order, maybe overriding hashCode has an effect. If it does, feel free to do that). If the bots are on the bottom level, they will still fight, but they won't move down. After all fights had happened, each bot's score is incremented by the level of the ziggurat it is standing on top of. After the turns are finished, the bot's score is set to score / numOfTurns. After all rounds are done, the bot's score is averaged for each round, and the result is the final score of the bot.

API

The controller can be downloaded here. To run your bot, place the bot in the io.github.seggan.kingoftheziggurat.bots package. There are a few example bots in there already. Then edit this array to include your bot. You may remove the 3 examples in there.

Your bot will take the following form:

public class YourBot extends io.github.seggan.kingoftheziggurat.Bot {

    @Override
    protected boolean fight(Bot opponent) {
        // return true whether you want to fight the other bot, false otherwise
    }

    @Override
    protected void tick() {
        // do your stuff
    }
}

The Bot class has some useful methods. They can be accessed in your bot subclass.

public class Bot {

/** Gets the x and y position of the bot */
public Point getPosition() {
}

/** Gets the bot's elevation */
public int getElevation() {
}

/** Gets the strength stat of the bot. You cannot get the strength of any bot other than yours */
public int getStrength() {
}

/** Gets all the players playing */
public Set<Bot> getPlayers() {
}

/** Queues the bot to move in the specified direction */
public void move(MoveDirection direction) {
}

/** Gets the height of the point in the specified direction */
public int getElevationRelative(MoveDirection direction) {
}

/** Gets the score of the bot */
public int getPoints() {
}

/** Moves the bot one level up if possible */
public void moveUp() {
}
}

To run your bot, simply execute ./gradlew runShadow in the project's directory. The controller needs a Java version of at least 16.

Movement

All directions of movement are absolute. There is a moveUp() method available in the Bot class that automagically moves you up one level.

  • NORTH decreases your Y
  • SOUTH increases your Y
  • WEST decreases your X
  • EAST increases your X

And so on for NORTH_EAST, SOUTH_WEST, etc.

Using Non-Java Languages

I have made a simple wrapper bot class that allows you to use any language you like with the challenge. It uses a socket-based protocol. To add your bot to the controller, simply edit the array to include new io.github.seggan.kingoftheziggurat.bots.BotWrapper(<arguments>). The <arguments> is a list of double-quoted strings that run your program on the command line. For example, in Python it would be new io.github.seggan.kingoftheziggurat.bots.BotWrapper("python", "your_bot.py"). It is recommended in your program to wait a few milliseconds for the socket server to start up.

The Socket Protocol

First, connect to localhost. But wait, you say, what port do I connect to? The port to connect to is the very first argument given to your program. Then, listen on the socket. All messages are delimited by \n. The first message received will be either "tick" or "fight". The next message is a JSON object that depends on the first message. If the first message is "tick", do your ticking logic and once done, send the direction you want to go in through the socket. The direction can be NORTH, SOUTH, EAST, WEST, NORTH_EAST, NORTH_WEST, SOUTH_EAST, or SOUTH_WEST. If you do not want to move, send NONE. If the first message is "fight", send either true or false back to the controller to indicate whether you want to fight or now. If the first message is "tick", the JSON in the second message will look like this. If it is "fight", it will look like this. These two messages will always be sent in a row, and after sending, the controller will always wait for a reply.

Restrictions

  • Standard loopholes apply
  • Attempts to mess with the controller or other bots will be disqualified
  • Answers using reflection will be disqualified
  • Modifying the BotWrapper class to give yourself more information than is given will not be tolerated either
  • I reserve the right to disqualify any submissions taking excessive time or memory to run
  • Discussions about bots should happen here

All submissions will be tested on my 8 GB RAM laptop using an Intel Core i5-4310 processor.

Scores

  • Round 1: Surprisingly, RealisticPacifist turned out first.
  • Round 2: JazzJock is acting strangely. Sometimes it languishes with a score of ~4.2, other times it pulls ahead beating RealisticPacifist by ~0.1 points. The scores shown are the typical case.
  • Round 3: Seems chaos does not like crabbiness
  • Round 4: Rebuilding bots from the ground up seems to work
Round 1 (50 turns): 
    RealisticPacifist (#1): 249, Averaged: 4.980
    Crab (#2): 247, Averaged: 4.940
    GreedyBot (#3): 235, Averaged: 4.700
    CrabberInTraining (#4): 249, Averaged: 4.980
    WeightCrab (#5): 255, Averaged: 5.100
    GroundUpBot (#6): 258, Averaged: 5.160
    ChaoticWalkerIII (#7): 255, Averaged: 5.100
    JazzJock (#8): 186, Averaged: 3.720

Round 2 (100 turns): 
    RealisticPacifist (#1): 501, Averaged: 5.010
    Crab (#2): 495, Averaged: 4.950
    GreedyBot (#3): 463, Averaged: 4.630
    CrabberInTraining (#4): 513, Averaged: 5.130
    WeightCrab (#5): 506, Averaged: 5.060
    GroundUpBot (#6): 521, Averaged: 5.210
    ChaoticWalkerIII (#7): 495, Averaged: 4.950
    JazzJock (#8): 434, Averaged: 4.340

Round 3 (250 turns): 
    RealisticPacifist (#1): 1227, Averaged: 4.908
    Crab (#2): 1261, Averaged: 5.044
    GreedyBot (#3): 1202, Averaged: 4.808
    CrabberInTraining (#4): 1313, Averaged: 5.252
    WeightCrab (#5): 1292, Averaged: 5.168
    GroundUpBot (#6): 1299, Averaged: 5.196
    ChaoticWalkerIII (#7): 1305, Averaged: 5.220
    JazzJock (#8): 982, Averaged: 3.928

Round 4 (500 turns): 
    RealisticPacifist (#1): 2433, Averaged: 4.866
    Crab (#2): 2601, Averaged: 5.202
    GreedyBot (#3): 2405, Averaged: 4.810
    CrabberInTraining (#4): 2601, Averaged: 5.202
    WeightCrab (#5): 2599, Averaged: 5.198
    GroundUpBot (#6): 2635, Averaged: 5.270
    ChaoticWalkerIII (#7): 2625, Averaged: 5.250
    JazzJock (#8): 2002, Averaged: 4.004

Round 5 (1000 turns): 
    RealisticPacifist (#1): 4819, Averaged: 4.819
    Crab (#2): 5177, Averaged: 5.177
    GreedyBot (#3): 4804, Averaged: 4.804
    CrabberInTraining (#4): 5276, Averaged: 5.276
    WeightCrab (#5): 5189, Averaged: 5.189
    GroundUpBot (#6): 5257, Averaged: 5.257
    ChaoticWalkerIII (#7): 5227, Averaged: 5.227
    JazzJock (#8): 4003, Averaged: 4.003

Final Scores:
    RealisticPacifist (#1): 4.917
    Crab (#2): 5.063
    GreedyBot (#3): 4.750
    CrabberInTraining (#4): 5.168
    WeightCrab (#5): 5.143
    GroundUpBot (#6): 5.219
    ChaoticWalkerIII (#7): 5.149
    JazzJock (#8): 3.999
\$\endgroup\$
37
  • 2
    \$\begingroup\$ Sandbox \$\endgroup\$
    – Seggan
    Feb 17 at 17:35
  • 3
    \$\begingroup\$ With 3 or more bots, they shouldn't fight in a fixed order - the bot that goes last has a big advantage there. \$\endgroup\$ Feb 17 at 18:22
  • 2
    \$\begingroup\$ Perhaps make the other Bot methods besides the abstract fight/tick final so they cannot be overwritten. And perhaps disallow reflection in general to prevent hacky Java answers. :) \$\endgroup\$ Feb 18 at 7:41
  • 3
    \$\begingroup\$ To make it more clear that the direction enums are absolute, it might make more sense to change them from being UP/DOWN/LEFT/RIGHT to NORTH/SOUTH/EAST/WEST \$\endgroup\$
    – des54321
    Apr 20 at 1:09
  • 3
    \$\begingroup\$ I meant that if both bots decide to not fight, the loop will hit the pair again the other way around. For the falling part - I'd calculate the groups of overlapping bots beforehand and then run all the detected fights, so all falls would effectively happen "between rounds". \$\endgroup\$ Apr 20 at 14:53

9 Answers 9

7
\$\begingroup\$

JazzJock

A rather more complicated bot, this bot is a rather macho jock, who will attempt to push their way to the top unless they are feeling very weak. However, due to their exposure to jazz music, they move in a random and unpredictable way, moving sideways along the pyramid as they climb.

import java.awt.Point;
import java.util.concurrent.ThreadLocalRandom;

public class JazzJock extends io.github.seggan.kingoftheziggurat.Bot {
    @Override
    protected boolean fight(Bot opponent) {return getStrength()>15;}

    @Override
    protected void tick() {
        Point pos = getPosition();
        if (getStrength()>10) /* if we have a little bit of strength, try to move up the pyramid */{
            if(getElevation() == 6) /* center, don't move */ {move(NONE);}
            else if (pos.x==pos.y) /* one diagonal, move up along it */ {move(pos.x<5 ? SOUTH_EAST : NORTH_WEST);}
            else if (pos.x+pos.y==10) /* other diagonal, move up along it */ {move(pos.x-pos.y<0 ? NORTH_EAST : SOUTH_WEST);}
            else if (getElevation() == 5) /* one step from the top, move directly there */ {
                if (pos.x+pos.y < 10) {
                    if(pos.x-pos.y < 0) /* west side */ {move(EAST);}
                    else /* north side */ {move(SOUTH);}
                }
                else {
                    if(pos.x-pos.y < 0) /* south side */ {move(NORTH);}
                    else /* east side */ {move(WEST);}
                }
            }
            else if (pos.x+pos.y < 10) {
                if(pos.x-pos.y < 0) /* west side */ {moveRand3(EAST, SOUTH_EAST, NORTH_EAST);}
                else /* north side */ {moveRand3(SOUTH, SOUTH_EAST, SOUTH_WEST);}
            }
            else {
                if (pos.x - pos.y < 0) /* south side */ {moveRand3(NORTH, NORTH_EAST, NORTH_WEST);}
                else /* east side */ {moveRand3(WEST,NORTH_WEST, SOUTH_WEST);}
            }
        }
        else /* too weak, circle on current level */ {
            if (pos.x==pos.y) /* one diagonal, move either corner */ {
                if(pos.x+pos.y < 10) /* north-west corner */ {moveRand2(EAST,SOUTH);}
                else /* south-east corner */ {moveRand2(NORTH,WEST);}
            }
            else if (pos.x+pos.y==10)  /* other diagonal, move either corner */ {
                if(pos.x-pos.y>0) /* north-east corner */ {moveRand2(SOUTH, WEST);}
                else /* south-west corner */ {moveRand2(NORTH, EAST);}
            }
            else if (pos.x+pos.y < 10) {
                if(pos.x-pos.y < 0) /* west side */ {moveRand2(NORTH, SOUTH);}
                else /* north side */ {moveRand2(EAST, WEST);}
            }
            else {
                if (pos.x - pos.y < 0) /* south side */ {moveRand2(EAST, WEST);}
                else /* east side */ {moveRand2(NORTH, SOUTH);}
            }
        }
    }

    private void moveRand2(MoveDirection A, MoveDirection B) {
        switch (ThreadLocalRandom.current().nextInt(2)) {
            case 0:move(A);
            case 1:move(B);
        }
    }

    private void moveRand3(MoveDirection A, MoveDirection B, MoveDirection C) {
        switch (ThreadLocalRandom.current().nextInt(3)) {
            case 0:move(A);
            case 1:move(B);
            case 2:move(C);
        }
    }
}
\$\endgroup\$
8
  • 2
    \$\begingroup\$ +1 purely for the meme reference \$\endgroup\$
    – jezza_99
    Apr 20 at 2:51
  • \$\begingroup\$ Wow, that is a very aggressive bot! Awfully trigger happy, even when it is rather weak. How did you figure 15 to be the optimal fighting threshold? \$\endgroup\$
    – Tuxysta
    Apr 20 at 3:06
  • 1
    \$\begingroup\$ I'll be honest @Pyautogui, I didn't really optimize it that much, I picked 15 as a pretty random value, it might well perform better at higher values, but I didn't feel like testing that \$\endgroup\$
    – des54321
    Apr 20 at 3:11
  • 1
    \$\begingroup\$ @Pyautogui however, I did run a few simple tests just now, changing the limits from 10 to 30, and 15 did seem to work a bit better than any other multiple of 5 \$\endgroup\$
    – des54321
    Apr 20 at 3:25
  • 1
    \$\begingroup\$ You can replace the entire content of moveRand2 with move(ThreadLocalRandom.current().nextBoolean() ? A : B) unless you want the case of the random number being 0 not moving you \$\endgroup\$
    – Seggan
    Apr 20 at 16:54
7
\$\begingroup\$

Weight Crab

Possesses the ordinary Crab's singleminded drive to reach the top and refusal to back down from a fight, but tries a bit harder not to pick fights by de-ranking its ascentful options by how likely another bot is to land there (though not differentiating between other bots by age, gender or weight), making it hard to eat while going about relieving its competitors of their scores.

package io.github.seggan.kingoftheziggurat.bots;

import io.github.seggan.kingoftheziggurat.Bot;
import static io.github.seggan.kingoftheziggurat.MoveDirection.*;
import io.github.seggan.kingoftheziggurat.MoveDirection;

import java.util.Map;
import java.util.HashMap;
import java.util.stream.Collectors;
import java.awt.Point;
import java.lang.Math;

public class WeightCrab extends Bot {

    @Override
    protected boolean fight(Bot opponent) {
        return getElevation() > 1;
    }

    @Override
    protected void tick() {

        if (getElevation() == 6) {
            move(NONE);
            return;
        }

        Map<MoveDirection, Integer> weights = new HashMap<>();

        Map<Point, Integer> botPositionCounts = new HashMap<>();

        for (Bot bot : getPlayers()) {
            Point position = bot.getPosition();
            botPositionCounts.put(position, botPositionCounts.getOrDefault(position, 0) + 1);
        }

        for (MoveDirection direction : MoveDirection.values()) {

            int weight = (getElevationRelative(direction) - getElevation()) * 89;
            Point candidate = relativePositionFrom(direction, getPosition());

            for (Map.Entry<Point, Integer> entry : botPositionCounts.entrySet()) {

                Point position = entry.getKey();
                int count = entry.getValue();

                if (defaultUpFrom(position).equals(candidate)) {
                    weight -= 2 * count;
                }

                if (elevationAt(candidate) > elevationAt(position) && withinReachOf(candidate, position)) {
                    weight -= count;
                }

            }

            weights.put(direction, weight);

        }

        MoveDirection chosen = weights.entrySet().stream().collect(
            Collectors.maxBy((a, b) ->
                a.getValue().compareTo(b.getValue())
            )).get().getKey();

        move(chosen);
    }

    private static Point relativePositionFrom(MoveDirection direction, Point from) {
        int x = from.x;
        int y = from.y;
        switch(direction) {
            case NORTH:
                return new Point(x, y - 1);
            case SOUTH:
                return new Point(x, y + 1);
            case EAST:
                return new Point(x + 1, y);
            case WEST:
                return new Point(x - 1, y);
            case NORTH_EAST:
                return new Point(x + 1, y - 1);
            case NORTH_WEST:
                return new Point(x - 1, y - 1);
            case SOUTH_EAST:
                return new Point(x + 1, y + 1);
            case SOUTH_WEST:
                return new Point(x - 1, y + 1);
            default: // why won't it let me just do an exhaustive enum match ;(
                return from;
        }
    }

    private static int elevationAt(Point point) {
        int fromCorner = Math.min(point.x, point.y) + 1;
        return Math.min(fromCorner, 12 - fromCorner);
    }

    private static Point defaultUpFrom(Point from) {
        for (MoveDirection direction : MoveDirection.values()) {
            if (elevationAt(relativePositionFrom(direction, from)) > elevationAt(from)) {
                return relativePositionFrom(direction, from);
            }
        }
        return from;
    }

    private static boolean withinReachOf(Point a, Point b) {
        return Math.abs(a.x - b.x) <= 1 && Math.abs(a.y - b.y) <= 1;
    }

}
\$\endgroup\$
2
  • \$\begingroup\$ It won't let you do an exhaustive match because you forgot the constant NONE \$\endgroup\$
    – Seggan
    Apr 21 at 22:21
  • \$\begingroup\$ @Seggan That case was NONE but the compiler didn't seem to like it \$\endgroup\$ Apr 21 at 22:26
5
\$\begingroup\$

ChaoticWalkerIII (The bot formerly known as MazeRunner)

New and improved Chaotic Walker. Now wins over 50% of the games. Bad midgame, but best bot early and late in general. Code based on both other entries at various points (but with my terrible implementation).

package io.github.seggan.kingoftheziggurat;
import java.util.Random;
import static io.github.seggan.kingoftheziggurat.MoveDirection.*;
import java.util.concurrent.ThreadLocalRandom;
        class ChaoticWalkerIII extends Bot {
        Random random = new Random();
        int ticks = 0;
            @Override
            protected boolean fight(Bot opponent) {
                if(random.nextDouble()>.9)
              {return ticks > 6;}
              else{
              return true;}  
            
              }
            private MoveDirection whichWayUp() {
                int relEvUp = getElevationRelative(NORTH)-getElevation();
                int relEvDown = getElevationRelative(SOUTH)-getElevation();
                int relEvLeft = getElevationRelative(WEST)-getElevation();
                if(relEvUp>0) {return NORTH;} else if(relEvDown>0) {return SOUTH;} else if(relEvLeft>0) {return WEST;} else {return EAST;}
            }
            private MoveDirection diagonalMove(MoveDirection m)
            {
                if(m==NORTH)
                {
                    if(random.nextBoolean())
                        {return NORTH_EAST;}
                    return NORTH_WEST;}
                else if(m==SOUTH)
                {
                    if(random.nextBoolean())
                        {return SOUTH_EAST;}
                    return SOUTH_WEST;}
                else if(m==EAST)
                {
                    if(random.nextBoolean())
                        {return NORTH_EAST;}
                    return SOUTH_EAST;
                }
                else
                {
                    if(random.nextBoolean())
                    {
                        return NORTH_WEST;
                    }
                    return SOUTH_WEST;
                }
            }
            @Override
            protected void tick() {
                ticks++;
                if (ticks<10)
                {
                    move(diagonalMove(whichWayUp()));
                }
                else if (getElevation() < 6) {
                    if(random.nextDouble()>.1||(getStrength()>15))
                        {moveUp();}
                    else if (random.nextDouble()>.05) {move(diagonalMove(whichWayUp()));}
                    else if (random.nextDouble()>.8) {move(MoveDirection.values()[ThreadLocalRandom.current().nextInt(MoveDirection.values().length)]);}
                    else {move(MoveDirection.NONE);}}
                else if (random.nextDouble()>.3)
                {
                    move(MoveDirection.values()[ThreadLocalRandom.current().nextInt(MoveDirection.values().length)]);
                }
                else
                {
                    move(MoveDirection.NONE);
                                   }}}
\$\endgroup\$
0
5
+50
\$\begingroup\$

RealisticPacifist

A rather weak bot with more complexity than it is worth. It tries to avoid fights unless it has a fair amount of strength relative to the number of rounds played so far. It then tries to fight when it can. It tries to stay around an elevation of 4 or 5, so as to avoid the strong bots at the center (6). It occasionally moves to the east or to the west. It won rounds #1 and #2 through luck and randomness. Full disclosure: I have not used Java before today.

import java.util.Random;

class RealisticPacifist extends io.github.seggan.kingoftheziggurat.impl.Bot {
    Random random = new Random(); 
    int ticks = 0;
    @Override
    protected boolean fight(Bot opponent) {
      return (random.nextDouble()<0.4) || (getStrength() > (ticks/7));
    }
    @Override
    protected void tick() {
      ticks += 1;
         if (random.nextDouble() > 0.025) {
            moveUp();
         } else {
            if (getPosition().x > 7) { 
              move(WEST); 
            } 
            else if (getPosition().y > 7)  {
              move(NORTH);
            }
            else if (getPosition().x < 4) {
              move(EAST);
            } 
            else if (getPosition().y < 4) {
              move(SOUTH); 
            } else 
            {
              switch (random.nextInt(4)) {
                case 0: move(EAST);break;
                case 1: move(WEST);break;
                case 2:  move(NORTH);break;
                case 3: move(SOUTH);break;
              }
            }
         }
       }
    
}

Note: This is my first time participating in a KOTH challenge. If I have done something incorrectly, I apologize and will attempt to rectify my mistake as soon as I am able.

\$\endgroup\$
5
  • 1
    \$\begingroup\$ Note that Random has a nextBoolean method. \$\endgroup\$ Apr 19 at 23:41
  • 1
    \$\begingroup\$ Also note that the score of the bot depends on the level it is standing on, this one would eventually get to the edges and only get 1 per turn. Maybe add a bit more variation to its movements? (ultimately it is your bot, feel free to do as you please) \$\endgroup\$
    – Seggan
    Apr 19 at 23:45
  • \$\begingroup\$ Got it. I shall do that @Seggan. Also, thanks for the tip Unmitigated. \$\endgroup\$
    – Tuxysta
    Apr 20 at 0:09
  • 1
    \$\begingroup\$ See my updated challenge. UP and DOWN are absolute. There is a moveUp() function \$\endgroup\$
    – Seggan
    Apr 20 at 1:04
  • \$\begingroup\$ Thanks for pointing that out! I have fixed it, I believe. \$\endgroup\$
    – Tuxysta
    Apr 20 at 1:10
4
\$\begingroup\$

Crab

public class Crab extends io.github.seggan.kingoftheziggurat.Bot {

    @Override
    protected boolean fight(Bot opponent) {
        return true;
    }

    @Override
    protected void tick () {
        if (getElevation() == 6) {
            move(NONE);
        } else {
            moveUp();
        }
    }
}

It's because of Crab that we can't have nice things.

\$\endgroup\$
3
  • \$\begingroup\$ I would expect a crab to at least move sideways, downvoted :p \$\endgroup\$
    – des54321
    Apr 20 at 20:32
  • 1
    \$\begingroup\$ @des54321 There's a long history \$\endgroup\$
    – allxy
    Apr 20 at 20:35
  • \$\begingroup\$ Surprised that there's no EmoWolf yet... \$\endgroup\$
    – Romanp
    Apr 20 at 22:09
4
\$\begingroup\$

Greedy Bot

import java.util.Random;

public class GreedyBot extends io.github.seggan.kingoftheziggurat.Bot {
    private Random random = new Random();
    private int roundcount = 0;
    private int prevheight = 0; //doesn't really matter at first
    private int prevheight2 = -1; //doesn't really matter at first
    private int height = -1; //doesn't really matter at first
    
    
    @Override
    protected boolean fight(Bot opponent) {
        if (roundcount<10) {
            return false;
        } 
        if (getElevation()>=5) {
            if (random.nextDouble()>=0.7) {
                return true;
            }
            return false;
        } else if (getElevation()==4) {
            if (random.nextDouble()>=0.95) {
                return true;
            }
            return false;
        } else {
            return false;
        }
    }


    @Override
    protected void tick() {
        roundcount++;
        if (getElevation()>prevheight) {
            moveUp();
        } else if (getElevation()==prevheight && prevheight == prevheight2){
            moveUp();
        } else if (getElevation()==prevheight) {
            if (getElevationRelative(move(NORTH)) <getElevation()) {
                move(SOUTH);
            } else {
                move(NORTH);
            }
        } else {
            if (getElevationRelative(move(NORTH)) <getElevation()) {
                if (getElevationRelative(move(East)) <getElevation()) {
                    move(SOUTH_WEST);
                } else {
                    move(SOUTH_EAST);
                }

            } else {
                if (getElevationRelative(move(East)) < getElevation()) {
                    move(NORTH_WEST);
                } else {
                    move(NORTH_EAST);
                }
            }
        }
        prevheight2 = prevheight;
        prevheight = height;
        height = getElevation();
        
        
    }
    
    
}

This is my first time doing something like this, so this bot may be a bit nieve.

It tries to climb to the top, until it gets stopped by something, where it usually yields to build up a higher strength. When it gets to a position more worth defending, it has a higher chance of defending it. Climbing is pretty deterministic, but when it has climbed and failed a few times because something is in the way, it tries to evade it. This also serves to avoid more aggressive bots from reducing it's strength by too much when it's at the top.

It was also made to be disgusting to the eyes of code golfers. :D

\$\endgroup\$
3
\$\begingroup\$

Ssammbot

Waits on level 3 until it builds up enough strength then attacks for the top. I plan to later make it actually take other bots into account instead of just sitting still while waiting. I may also tweak the numbers depending on what round we are in.

Written in tcl using the socket api and requires tcllib for the json parser. Can be added with with new BotWrapper("tclsh", "bot.tcl")

package require json

#reset the bot for a new round

set round 0
proc init {} {
    variable ::map {}
    variable ::centerX 0
    variable ::centerY 0
    variable ::waitLevel 3
    variable ::fightTarget 30
    variable ::retreatTarget 12
    variable ::attacking 0
    variable ::enemies [dict create]
    variable ::turn 0
    incr $::round
}

proc parseMap {mapdata} {
    foreach row [split $mapdata \n] {
        lappend ::map [split $row {}]
    }
    set ::centerX [expr [llength $::map] /2]
    set ::centerY [expr [llength [lindex $::map 0]] /2]
}

proc moveTowardsCerter {cx cy} {
    set ns {}
    set ew {}
    if {$cx < $::centerX} {
        set ew "EAST"
    } elseif {$cx > $::centerX} {
        set ew "WEST"
    }

    if {$cy < $::centerY} {
        set ns "SOUTH"
    } elseif {$cy > $::centerY} {
        set ns "NORTH"
    }

    set res [string trim [string cat $ns _ $ew] _]
    if {$res eq {}} { return "NONE" }

    return $res
}

proc tick {botState} {
    if {[dict get $botState strength] == 0} {
        init
    }

    if {$::map eq {}} {
        parseMap [dict get $botState map]
    }

    set targetLevel $::waitLevel
    if {[dict get $botState strength] > $::fightTarget} {
        set ::attacking 1
    }

    if {$::attacking && [dict get $botState strength] < $::retreatTarget} {
        set ::attacking 0
    }

    set targetLevel $::waitLevel
    if {$::attacking} {
        set targetLevel 6
    }

    set movement "NONE"
    if {[dict get $botState elevation] < $targetLevel} {
        set movement [moveTowardsCerter [dict get $botState x] [dict get $botState y]]
    }

    incr $::turn
    return $movement
}

proc fight {botState} {
    if {[dict get $botState elevation] == 6 && $::attacking} {
        return "true"
    }
    return "false"
}

proc processCommand {serverSock} {
    yield
    if {[chan eof $serverSock]} {
        chan close $serverSock
        exit 0
    }

    set command [gets $serverSock]
    yield

    if {[chan eof $serverSock]} {
        chan close $serverSock
        exit 0
    }

    set serverData [gets $serverSock]
    set args [dict create]
    if {[catch {set args [::json::json2dict $serverData]}]} {
        puts stderr "Faild to parse json"
        puts stderr $::errorInfo
    }

    set response {}
    if {$command eq "tick"}  {
        if {[catch {set response [tick $args]} err]} {
            puts stderr $::errorInfo
            set response "NONE"
        }
    } else {
        if {[catch {set response [fight $args]} err]} {
            puts stderr $::errorInfo
            set response "false"
        }
    }

    puts $serverSock $response
    flush $serverSock

    tailcall processCommand $serverSock
}

proc main {argc argv} {

    if {$argc != 1} {
        puts "Usage: bot <port>"
        exit -1
    }

    after 300

    set serverSock [socket localhost [lindex $argv 0]]
    chan configure $serverSock -blocking 0

    coroutine processLine processCommand $serverSock

    chan event $serverSock readable processLine
}

main $argc $argv
vwait closeBot
```
\$\endgroup\$
2
  • 2
    \$\begingroup\$ I cannot test this right now as it can't find the json package even with it being installed \$\endgroup\$
    – Seggan
    Apr 22 at 0:06
  • \$\begingroup\$ Hmm, it usually just works when you install tcllib, I might rewrite in in java. \$\endgroup\$
    – Samuel
    Apr 22 at 16:21
1
\$\begingroup\$

CrabberInTraining

The bot's name came from the original code, which would only fight Crab and WeightCrab unless absolutely necessary, but I removed that functionality because I didn't know if it was allowed. It's pretty much a less aggressive WeightCrab with different weights, but with a built-in movement plan for the first ten turns of each round based on ChaoticWalker and a small chance of moving randomly while on top of the Ziggurat. Generally beats all the other bots, but not by a huge margin. Will continue to improve it over time.

package io.github.seggan.kingoftheziggurat.bots;

import static io.github.seggan.kingoftheziggurat.MoveDirection.*;

import java.awt.Point;
import java.util.HashMap;
import java.util.Map;
import java.util.Random;
import java.util.concurrent.ThreadLocalRandom;
import java.util.stream.Collectors;

import io.github.seggan.kingoftheziggurat.Bot;
import io.github.seggan.kingoftheziggurat.MoveDirection;

public class CrabberInTraining extends Bot {
    Random random = new Random();
    int ticks = 0;
    protected boolean fight(Bot opponent) {
        return getElevation() > 4  || getStrength()>18;
    }
    private MoveDirection diagonalMove(MoveDirection m) {
        if (m == NORTH) {
            if (random.nextBoolean()) {
                return NORTH_EAST;
            }
            return NORTH_WEST;
        } else if (m == SOUTH) {
            if (random.nextBoolean()) {
                return SOUTH_EAST;
            }
            return SOUTH_WEST;
        } else if (m == EAST) {
            if (random.nextBoolean()) {
                return NORTH_EAST;
            }
            return SOUTH_EAST;
        } else {
            if (random.nextBoolean()) {
                return NORTH_WEST;
            }
            return SOUTH_WEST;
        }
    }
    private static Point relativePositionFrom(MoveDirection direction, Point from) {
        int x = from.x;
        int y = from.y;
        switch(direction) {
            case NORTH:
                return new Point(x, y - 1);
            case SOUTH:
                return new Point(x, y + 1);
            case EAST:
                return new Point(x + 1, y);
            case WEST:
                return new Point(x - 1, y);
            case NORTH_EAST:
                return new Point(x + 1, y - 1);
            case NORTH_WEST:
                return new Point(x - 1, y - 1);
            case SOUTH_EAST:
                return new Point(x + 1, y + 1);
            case SOUTH_WEST:
                return new Point(x - 1, y + 1);
            default:
                return from;
        }
    }
    private MoveDirection whichWayUp() {
        int relEvUp = getElevationRelative(NORTH) - getElevation();
        int relEvDown = getElevationRelative(SOUTH) - getElevation();
        int relEvLeft = getElevationRelative(WEST) - getElevation();
        if (relEvUp > 0) {
            return NORTH;
        } else if (relEvDown > 0) {
            return SOUTH;
        } else if (relEvLeft > 0) {
            return WEST;
        } else {
            return EAST;
        }
    }
    private static int elevationAt(Point point) {
        int fromCorner = Math.min(point.x, point.y) + 1;
        return Math.min(fromCorner, 12 - fromCorner);
    }
    private static boolean withinReachOf(Point a, Point b) {
        return Math.abs(a.x - b.x) <= 1 && Math.abs(a.y - b.y) <= 1;
    }
    private static Point defaultUpFrom(Point from) {
        for (MoveDirection direction : MoveDirection.values()) {
            if (elevationAt(relativePositionFrom(direction, from)) > elevationAt(from)) {
                return relativePositionFrom(direction, from);
            }
        }
        return from;
    }
    @Override
    protected void tick() {
        if(ticks<10||(50<ticks&&ticks<60)||(150<ticks&&ticks<160)||(300<ticks&&ticks<310)||(800<ticks&&ticks<810))
        {
            move(diagonalMove(whichWayUp()));
        }
        if (getElevation() == 6) {
            if(random.nextDouble()<.9) {move(NONE);}{move(MoveDirection.values()[ThreadLocalRandom.current().nextInt(MoveDirection.values().length)]);}
            return;
        }
        Map<MoveDirection, Double> weights = new HashMap<>();

        Map<Point, Integer> botPositionCounts = new HashMap<>();
        
        for (Bot bot : getPlayers()) {
            Point position = bot.getPosition();
            botPositionCounts.put(position, botPositionCounts.getOrDefault(position, 0) + 1);}
        
    
    for (MoveDirection direction : MoveDirection.values()) {

        double weight = (getElevationRelative(direction) - 2*getElevation()) * 200;
        Point candidate = relativePositionFrom(direction, getPosition());

        for (Map.Entry<Point, Integer> entry : botPositionCounts.entrySet()) {

            Point position = entry.getKey();
            int count = entry.getValue();

            if (defaultUpFrom(position).equals(candidate)) {
                weight -= 3 * count;
            }

            if (elevationAt(candidate) > elevationAt(position) && withinReachOf(candidate, position)) {
                weight -= count;
            }            weights.put(direction, weight);

        }

        MoveDirection chosen = weights.entrySet().stream().collect(
            Collectors.maxBy((a, b) ->
                a.getValue().compareTo(b.getValue())
            )).get().getKey();

        move(chosen);

        }}}
```
\$\endgroup\$
1
\$\begingroup\$

GroundUpBot

NOTE: This bot uses pretty much everyone's helper function and a version of their algorithm, so credit to them for creating those bots.

Unlike the ChaoticWalker family, which started as a modified RealisticPacifist, or CrabberInTraining, which is WeightCrab with bits of ChaoticWalkerIII attached to it, GroundUpBot was created from scratch. It re-implements the algorithms of all existing bots and assigns a probability to each location on the Ziggurat for each bot to move. It combines that with a weighting system to decide how important it is to gain strength vs. move up and gain score to reach the best decision it can about where to move. Currently, it cannot approximate the strength of other bots, nor does it take it into account, but a variant of the bot did attempt to guess how strong Crab was by finding when it lost a fight (and therefore, moved down).

Code (now commented):

package io.github.seggan.kingoftheziggurat.bots;

import static io.github.seggan.kingoftheziggurat.MoveDirection.EAST;
import static io.github.seggan.kingoftheziggurat.MoveDirection.NONE;
import static io.github.seggan.kingoftheziggurat.MoveDirection.NORTH;
import static io.github.seggan.kingoftheziggurat.MoveDirection.NORTH_EAST;
import static io.github.seggan.kingoftheziggurat.MoveDirection.NORTH_WEST;
import static io.github.seggan.kingoftheziggurat.MoveDirection.SOUTH;
import static io.github.seggan.kingoftheziggurat.MoveDirection.SOUTH_EAST;
import static io.github.seggan.kingoftheziggurat.MoveDirection.SOUTH_WEST;
import static io.github.seggan.kingoftheziggurat.MoveDirection.WEST;

import java.awt.Point;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Random;

import io.github.seggan.kingoftheziggurat.Bot;
import io.github.seggan.kingoftheziggurat.MoveDirection;

public class GroundUpBot extends Bot {
    Random random = new Random();
    int ticks = 0;
    @Override
    protected boolean fight(Bot opponent) {
        if (ticks<10||(50<ticks&&ticks<60)||(150<ticks&&ticks<160)||(300<ticks&&ticks<310)||(800<ticks&&ticks<810))
        {
            return getStrength()>2; //Fight if not helpless on first ten ticks of each round
        }
        else
        {
            return getStrength()>5; //Otherwise, fight once strong enough to stand up to some stuff
        }

    }

    private static Point relativePositionFrom(MoveDirection direction, Point from) {//For WeightCrab/CrabberInTraining
        int x = from.x; 
        int y = from.y;
        return switch (direction) {
            case NORTH -> new Point(x, y - 1);
            case SOUTH -> new Point(x, y + 1);
            case EAST -> new Point(x + 1, y);
            case WEST -> new Point(x - 1, y);
            case NORTH_EAST -> new Point(x + 1, y - 1);
            case NORTH_WEST -> new Point(x - 1, y - 1);
            case SOUTH_EAST -> new Point(x + 1, y + 1);
            case SOUTH_WEST -> new Point(x - 1, y + 1);
            default -> // why won't it let me just do an exhaustive enum match ;(
                from;
        };}
    private static int elevationAt(Point point) {//For WeightCrab/CrabberInTraining
        int fromCorner = Math.min(point.x, point.y) + 1;
        return Math.min(fromCorner, 12 - fromCorner);
    }

    private static Point defaultUpFrom(Point from) {//For WeightCrab/CrabberInTraining
        for (MoveDirection direction : MoveDirection.values()) {
            if (elevationAt(relativePositionFrom(direction, from)) > elevationAt(from)) {
                return relativePositionFrom(direction, from);
            }
        }
        return from;
    }

    private static boolean withinReachOf(Point a, Point b) {//For WeightCrab/CrabberInTraining
        return Math.abs(a.x - b.x) <= 1 && Math.abs(a.y - b.y) <= 1;
    }
    private MoveDirection whichWayUp(Bot bot) {//For ChaoticWalkerIII/CrabberInTraining. Can now take arbitrary bot instead of just current bot.
        int relEvUp = bot.getElevationRelative(NORTH) - bot.getElevation();
        int relEvDown = bot.getElevationRelative(SOUTH) -  bot.getElevation();
        int relEvLeft = bot.getElevationRelative(WEST) - bot.getElevation();
        if (relEvUp > 0) {
            return NORTH;
        } else if (relEvDown > 0) {
            return SOUTH;
        } else if (relEvLeft > 0) {
            return WEST;
        } else {
            return EAST;
        }
    }
    private static Point whereMoveTake(MoveDirection direction, Bot bot) {//Generally used by GroundUpBot
        int x = bot.getPosition().x;
        int y = bot.getPosition().y;
        return switch (direction) {
            case NORTH -> new Point(x, y - 1);
            case SOUTH -> new Point(x, y + 1);
            case EAST -> new Point(x + 1, y);
            case WEST -> new Point(x - 1, y);
            case NORTH_EAST -> new Point(x + 1, y - 1);
            case NORTH_WEST -> new Point(x - 1, y - 1);
            case SOUTH_EAST -> new Point(x + 1, y + 1);
            case SOUTH_WEST -> new Point(x - 1, y + 1);
            default -> bot.getPosition();
        };
    }
    private MoveDirection moveBotUp(Bot bot) {//Used by most bots as a replacement for MoveUp
        for (MoveDirection direction : MoveDirection.values()) {
            if (getElevationRelative(direction) > bot.getElevation()) {
                return direction;
            }
        }
        return MoveDirection.NONE;
    }
    protected HashMap<Point,Double> whatMove(Bot bot,HashMap<Point,Double> defaultGrid)//The main function
    {/*
    This predicts the movement of any bot it is given and saves the probabilities of motion to each point on the HashMap.
    */
        if(bot instanceof Crab)
        {
            Point p = whereMoveTake(moveBotUp(bot),bot);
            defaultGrid.put(p,defaultGrid.getOrDefault(p,0.)-1);//Crab is deterministic, it's easy to predict.
            return defaultGrid;
        }
        else if(bot instanceof RealisticPacifist)
        {
             if (bot.getElevation() > 4) {
                    defaultGrid.put(bot.getPosition(),defaultGrid.getOrDefault(bot.getPosition(),0.)-.7);
                    Point p = whereMoveTake(moveBotUp(bot),bot); //70-30 for RealisticPacifist when at high elevation.
                    defaultGrid.put(p, defaultGrid.getOrDefault(p,0.)-.3);
                    return defaultGrid;
                } 
             else
                 {
                 Point p = whereMoveTake(moveBotUp(bot),bot);
                 defaultGrid.put(p, defaultGrid.getOrDefault(p,0.)-.95); //95% it moves up in these cases
                        if (bot.getPosition().x > 7) { //Otherwise, if near an edge, move towards center
                            Point p2 = whereMoveTake(WEST,bot);
                            defaultGrid.put(p2, defaultGrid.getOrDefault(p2,0.)-.05);
                        } else if (bot.getPosition().y > 7) {
                            Point p2 = whereMoveTake(NORTH,bot);
                            defaultGrid.put(p2, defaultGrid.getOrDefault(p2,0.)-.05);
                        } else if (bot.getPosition().x < 4) {
                            Point p2 = whereMoveTake(EAST,bot);
                            defaultGrid.put(p2, defaultGrid.getOrDefault(p2,0.)-.05);
                        } else if (bot.getPosition().y < 4) {
                            Point p2 = whereMoveTake(SOUTH,bot);
                            defaultGrid.put(p2, defaultGrid.getOrDefault(p2,0.)-.05);
                        }
                        else
                            {//Otherwise move randomly on a cardinal direction
                            Point p2 = whereMoveTake(WEST,bot);
                            defaultGrid.put(p2, defaultGrid.getOrDefault(p2,0.)-.0125);
                            Point p3 = whereMoveTake(NORTH,bot);
                            defaultGrid.put(p3, defaultGrid.getOrDefault(p3,0.)-.0125);
                            Point p4 = whereMoveTake(EAST,bot);
                            defaultGrid.put(p4, defaultGrid.getOrDefault(p4,0.)-.0125);
                            Point p5 = whereMoveTake(SOUTH,bot);
                            defaultGrid.put(p5, defaultGrid.getOrDefault(p5,0.)-.0125);
                            }
                        return defaultGrid;}
             }
        else if (bot instanceof JazzJock)
        {
            Point pos = bot.getPosition();
            if(ticks<4||(50<ticks&&ticks<54)||(150<ticks&&ticks<154)||(300<ticks&&ticks<304)||(800<ticks&&ticks<804))
            {//Can't check its strength, so instead assume it spends ticks 0-3 preparing.
                if(pos.x==pos.y)//On northwest-southeast diagonal?
                {
                    if(pos.x+pos.y<10)//On the northwest side?
                    {
                        Point p = whereMoveTake(EAST,bot);//Move either east or south.
                        Point p2 = whereMoveTake(SOUTH,bot);
                        defaultGrid.put(p, defaultGrid.getOrDefault(p,0.)-.5);
                        defaultGrid.put(p2, defaultGrid.getOrDefault(p2,0.)-.5);
                    }
                    else {
                    Point p = whereMoveTake(NORTH,bot); //Otherwise, move north or west.
                    Point p2 = whereMoveTake(WEST,bot);
                    defaultGrid.put(p, defaultGrid.getOrDefault(p,0.)-.5);
                    defaultGrid.put(p2, defaultGrid.getOrDefault(p2,0.)-.5);
                    }
                }
                else if (pos.x+pos.y==10)//On the other diagonal?
                {
                    if(pos.x-pos.y>0)//Check which side of top you are on.
                    {
                        Point p = whereMoveTake(SOUTH,bot);//Move south or west.
                        Point p2 = whereMoveTake(WEST,bot);
                        defaultGrid.put(p, defaultGrid.getOrDefault(p,0.)-.5);
                        defaultGrid.put(p2, defaultGrid.getOrDefault(p2,0.)-.5);
                    }
                    else
                    {
                        Point p = whereMoveTake(EAST,bot);//Move north or east.
                        Point p2 = whereMoveTake(NORTH,bot);
                        defaultGrid.put(p, defaultGrid.getOrDefault(p,0.)-.5);
                        defaultGrid.put(p2, defaultGrid.getOrDefault(p2,0.)-.5);
                    }
                }
                else if (pos.x+pos.y<10)//North or west of center?
                {
                    if(pos.x-pos.y<0)//If west, move N/S
                    {
                        Point p = whereMoveTake(NORTH,bot);
                        Point p2 = whereMoveTake(SOUTH,bot);
                        defaultGrid.put(p, defaultGrid.getOrDefault(p,0.)-.5);
                        defaultGrid.put(p2, defaultGrid.getOrDefault(p2,0.)-.5);
                    }
                    else//If north, move E/W
                    {
                        Point p = whereMoveTake(EAST,bot);
                        Point p2 = whereMoveTake(WEST,bot);
                        defaultGrid.put(p, defaultGrid.getOrDefault(p,0.)-.5);
                        defaultGrid.put(p2, defaultGrid.getOrDefault(p2,0.)-.5);
                    }
                }
                else//Do the same for the southeastern side.
                {
                    if(pos.x-pos.y<0)
                    {
                        Point p = whereMoveTake(EAST,bot);
                        Point p2 = whereMoveTake(WEST,bot);
                        defaultGrid.put(p, defaultGrid.getOrDefault(p,0.)-.5);
                        defaultGrid.put(p2, defaultGrid.getOrDefault(p2,0.)-.5);
                    }
                    else
                    {
                        Point p = whereMoveTake(NORTH,bot);
                        Point p2 = whereMoveTake(SOUTH,bot);
                        defaultGrid.put(p, defaultGrid.getOrDefault(p,0.)-.5);
                        defaultGrid.put(p2, defaultGrid.getOrDefault(p2,0.)-.5);
                    }
                }
            }
            else//Strength high enough to move
            {
                if(bot.getElevation()==6)//Are you on top?
                {
                    defaultGrid.put(pos, defaultGrid.getOrDefault(pos,0.)-1);//If so, JazzJock won't move.
                }
                else if(pos.x==pos.y)//Are you on that diagonal?
                {
                    if(pos.x<5)//Figure out which side
                    {
                        //Move southeast if that's towards the top
                        defaultGrid.put(whereMoveTake(SOUTH_EAST,bot), defaultGrid.getOrDefault(whereMoveTake(SOUTH_EAST,bot),0.)-1);
                    }
                    else
                    {
                        //Otherwise, move northwest
                        defaultGrid.put(whereMoveTake(NORTH_WEST,bot), defaultGrid.getOrDefault(whereMoveTake(NORTH_WEST,bot),0.)-1);
                    }
                }
                else if (pos.x+pos.y==10)//Are you on the second diagonal?
                {
                    if(pos.x-pos.y<0)
                    {
                        //If so, move northeast or southwest, depending on which is towards the center.
                        defaultGrid.put(whereMoveTake(NORTH_EAST,bot), defaultGrid.getOrDefault(whereMoveTake(NORTH_EAST,bot),0.)-1);
                    }
                    else
                    {
                        defaultGrid.put(whereMoveTake(SOUTH_WEST,bot), defaultGrid.getOrDefault(whereMoveTake(SOUTH_WEST,bot),0.)-1);
                    }
                }
                else if (bot.getElevation()==5)//Are you on the second highest level?
                {
                    if(pos.x+pos.y<10)//If so, move directly to the top via cardinal directions.
                    {
                        if(pos.x-pos.y<0)
                        {
                            defaultGrid.put(whereMoveTake(EAST,bot), defaultGrid.getOrDefault(whereMoveTake(EAST,bot),0.)-1);
                        }
                    
                        else
                        {
                            defaultGrid.put(whereMoveTake(SOUTH,bot), defaultGrid.getOrDefault(whereMoveTake(SOUTH,bot),0.)-1);
                        }
                    }
                    else
                    {
                        if(pos.x-pos.y<0)
                        {
                            defaultGrid.put(whereMoveTake(NORTH,bot), defaultGrid.getOrDefault(whereMoveTake(NORTH,bot),0.)-1);
                        }
                        else
                        {
                            defaultGrid.put(whereMoveTake(WEST,bot), defaultGrid.getOrDefault(whereMoveTake(WEST,bot),0.)-1);
                        }
                    }
                }
                else//Otherwise, move somewhat randomly towards the top. Put a 1/3 probability for all 3 options.
                {
                    if(pos.x+pos.y<10)
                    {
                        if(pos.x-pos.y<0)//If on the west side
                        {
                            //Move east or east-adjacent.
                            Point p = whereMoveTake(EAST,bot);
                            Point p2 = whereMoveTake(SOUTH_EAST,bot);
                            Point p3 = whereMoveTake(NORTH_EAST,bot);
                            defaultGrid.put(p, defaultGrid.getOrDefault(p,0.)-1./3);
                            defaultGrid.put(p2, defaultGrid.getOrDefault(p2,0.)-1./3);
                            defaultGrid.put(p3, defaultGrid.getOrDefault(p3,0.)-1./3);
                        }
                        else//If on the east side
                        {
                            //Move west or diagonally west.
                            Point p = whereMoveTake(WEST,bot);
                            Point p2 = whereMoveTake(SOUTH_WEST,bot);
                            Point p3 = whereMoveTake(NORTH_WEST,bot);
                            defaultGrid.put(p, defaultGrid.getOrDefault(p,0.)-1./3);
                            defaultGrid.put(p2, defaultGrid.getOrDefault(p2,0.)-1./3);
                            defaultGrid.put(p3, defaultGrid.getOrDefault(p3,0.)-1./3);
                        }
                    }
                    else
                    {
                        if(pos.x-pos.y<0)//In the south?
                        {
                            //Move towards the north.
                            Point p = whereMoveTake(NORTH,bot);
                            Point p2 = whereMoveTake(NORTH_EAST,bot);
                            Point p3 = whereMoveTake(NORTH_WEST,bot);
                            defaultGrid.put(p, defaultGrid.getOrDefault(p,0.)-1./3);
                            defaultGrid.put(p2, defaultGrid.getOrDefault(p2,0.)-1./3);
                            defaultGrid.put(p3, defaultGrid.getOrDefault(p3,0.)-1./3);
                        }
                        else//Otherwise
                        {
                            //Move south.
                            Point p = whereMoveTake(SOUTH,bot);
                            Point p2 = whereMoveTake(SOUTH_EAST,bot);
                            Point p3 = whereMoveTake(SOUTH_WEST,bot);
                            defaultGrid.put(p, defaultGrid.getOrDefault(p,0.)-1./3);
                            defaultGrid.put(p2, defaultGrid.getOrDefault(p2,0.)-1./3);
                            defaultGrid.put(p3, defaultGrid.getOrDefault(p3,0.)-1./3);
                        }
                    }
                }
            }
        return defaultGrid;}
        else if (bot instanceof ChaoticWalkerIII)//Is it a ChaoticWalker?
        {
            if(ticks<10)//On the first few rounds, ChaoticWalker moves diagonally upwards. Can't guess which way, so assign 50% to each.
            {
                MoveDirection v = whichWayUp(bot);//Find which way the bot will head
                if(v==NORTH)//Then fill in the diagonal moves from that
                {
                    Point p = whereMoveTake(NORTH_EAST,bot);
                    Point p2 = whereMoveTake(NORTH_WEST,bot);
                    defaultGrid.put(p, defaultGrid.getOrDefault(p,0.)-.5);
                    defaultGrid.put(p2, defaultGrid.getOrDefault(p2,0.)-.5);
                }
                else if (v==SOUTH)
                {
                    Point p = whereMoveTake(SOUTH_EAST,bot);
                    Point p2 = whereMoveTake(SOUTH_WEST,bot);
                    defaultGrid.put(p, defaultGrid.getOrDefault(p,0.)-.5);
                    defaultGrid.put(p2, defaultGrid.getOrDefault(p2,0.)-.5);
                }
                else if (v==EAST)
                {
                    Point p = whereMoveTake(NORTH_EAST,bot);
                    Point p2 = whereMoveTake(SOUTH_EAST,bot);
                    defaultGrid.put(p, defaultGrid.getOrDefault(p,0.)-.5);
                    defaultGrid.put(p2, defaultGrid.getOrDefault(p2,0.)-.5);
                }
                else
                {
                    Point p = whereMoveTake(NORTH_WEST,bot);
                    Point p2 = whereMoveTake(SOUTH_WEST,bot);
                    defaultGrid.put(p, defaultGrid.getOrDefault(p,0.)-.5);
                    defaultGrid.put(p2, defaultGrid.getOrDefault(p2,0.)-.5);
                }
            }
            else if (bot.getElevation()<6)//Otherwise, 90% of the time you head straight up.
            {

                    Point p1 = whereMoveTake(moveBotUp(bot),bot);
                    defaultGrid.put(p1,defaultGrid.getOrDefault(p1,0.)-.9);
                    MoveDirection v = whichWayUp(bot);
                    if(v==NORTH)//It sometimes moves diagonally though, but quite rare.
                    {
                        Point p = whereMoveTake(NORTH_EAST,bot);
                        Point p2 = whereMoveTake(NORTH_WEST,bot);
                        defaultGrid.put(p, defaultGrid.getOrDefault(p,0.)-0.0475);
                        defaultGrid.put(p2, defaultGrid.getOrDefault(p2,0.)-0.0475);
                    }
                    else if (v==SOUTH)
                    {
                        Point p = whereMoveTake(SOUTH_EAST,bot);
                        Point p2 = whereMoveTake(SOUTH_WEST,bot);
                        defaultGrid.put(p, defaultGrid.getOrDefault(p,0.)-0.0475);
                        defaultGrid.put(p2, defaultGrid.getOrDefault(p2,0.)-0.0475);
                    }
                    else if (v==EAST)
                    {
                        Point p = whereMoveTake(NORTH_EAST,bot);
                        Point p2 = whereMoveTake(SOUTH_EAST,bot);
                        defaultGrid.put(p, defaultGrid.getOrDefault(p,0.)-0.0475);
                        defaultGrid.put(p2, defaultGrid.getOrDefault(p2,0.)-0.0475);
                    }
                    else
                    {
                        Point p = whereMoveTake(NORTH_WEST,bot);
                        Point p2 = whereMoveTake(SOUTH_WEST,bot);
                        defaultGrid.put(p, defaultGrid.getOrDefault(p,0.)-0.0475);
                        defaultGrid.put(p2, defaultGrid.getOrDefault(p2,0.)-0.0475);
                    }
                    //Even less often, it will move at random or stay still.
                    defaultGrid.put(whereMoveTake(NORTH,bot),defaultGrid.getOrDefault(whereMoveTake(NORTH,bot),0.)-0.000125);
                    defaultGrid.put(whereMoveTake(SOUTH,bot),defaultGrid.getOrDefault(whereMoveTake(SOUTH,bot),0.)-0.000125);
                    defaultGrid.put(whereMoveTake(EAST,bot),defaultGrid.getOrDefault(whereMoveTake(EAST,bot),0.)-0.000125);
                    defaultGrid.put(whereMoveTake(WEST,bot),defaultGrid.getOrDefault(whereMoveTake(WEST,bot),0.)-0.000125);
                    defaultGrid.put(whereMoveTake(NORTH_EAST,bot),defaultGrid.getOrDefault(whereMoveTake(NORTH_EAST,bot),0.)-0.000125);
                    defaultGrid.put(whereMoveTake(NORTH_WEST,bot),defaultGrid.getOrDefault(whereMoveTake(NORTH_WEST,bot),0.)-0.000125);
                    defaultGrid.put(whereMoveTake(SOUTH_EAST,bot),defaultGrid.getOrDefault(whereMoveTake(SOUTH_EAST,bot),0.)-0.000125);
                    defaultGrid.put(whereMoveTake(SOUTH_WEST,bot),defaultGrid.getOrDefault(whereMoveTake(SOUTH_WEST,bot),0.)-0.000125);
                    defaultGrid.put(bot.getPosition(),defaultGrid.getOrDefault(bot.getPosition(),0.)-0.004);

                }
            
            else
            {
                //If on top,it will stay 30% of the time, but move the other 70%.
                defaultGrid.put(bot.getPosition(),defaultGrid.getOrDefault(bot.getPosition(),0.)-0.3);
                defaultGrid.put(whereMoveTake(NORTH,bot),defaultGrid.getOrDefault(whereMoveTake(NORTH,bot),0.)-0.0875);
                defaultGrid.put(whereMoveTake(SOUTH,bot),defaultGrid.getOrDefault(whereMoveTake(SOUTH,bot),0.)-0.0875);
                defaultGrid.put(whereMoveTake(EAST,bot),defaultGrid.getOrDefault(whereMoveTake(EAST,bot),0.)-0.0875);
                defaultGrid.put(whereMoveTake(WEST,bot),defaultGrid.getOrDefault(whereMoveTake(WEST,bot),0.)-0.0875);
                defaultGrid.put(whereMoveTake(NORTH_EAST,bot),defaultGrid.getOrDefault(whereMoveTake(NORTH_EAST,bot),0.)-0.0875);
                defaultGrid.put(whereMoveTake(NORTH_WEST,bot),defaultGrid.getOrDefault(whereMoveTake(NORTH_WEST,bot),0.)-0.0875);
                defaultGrid.put(whereMoveTake(SOUTH_EAST,bot),defaultGrid.getOrDefault(whereMoveTake(SOUTH_EAST,bot),0.)-0.0875);
                defaultGrid.put(whereMoveTake(SOUTH_WEST,bot),defaultGrid.getOrDefault(whereMoveTake(SOUTH_WEST,bot),0.)-0.0875);
        }       return defaultGrid;}
        else if (bot instanceof WeightCrab)//Otherwise, is it WeightCrab?
        {
            if(bot.getElevation()==6)//WeightCrab never moves down when on the top.
            {
                defaultGrid.put(bot.getPosition(), defaultGrid.getOrDefault(bot.getPosition(),0.)-1);
            }
            else {
            Map<MoveDirection, Integer> weights = new HashMap<>();

            Map<Point, Integer> botPositionCounts = new HashMap<>();

            for (Bot bot2 : bot.getPlayers()) {//Otherwise, WeightCrab uses a weighting algorithm which assumes bots would rather go up or stay still.
                Point position = bot2.getPosition();
                botPositionCounts.put(position, botPositionCounts.getOrDefault(position, 0) + 1);//Mark each point with a bot
            }
            for (MoveDirection direction : MoveDirection.values()) {

                int weight = (bot.getElevationRelative(direction) - bot.getElevation()) * 89;//Assume going up is better
                Point candidate = relativePositionFrom(direction, bot.getPosition());

                for (Map.Entry<Point, Integer> entry : botPositionCounts.entrySet()) {

                    Point position = entry.getKey();
                    int count = entry.getValue();

                    if (defaultUpFrom(position).equals(candidate)) {
                        weight -= 2 * count;
                    }//If you would move somewhere straight up from a bot, WeightCrab avoids that move.

                    if (elevationAt(candidate) > elevationAt(position) && withinReachOf(candidate, position)) {
                        weight -= count;
                    }//If you would move next to a bot, WeightCrab prefers to avoid that move.

                }weights.put(direction, weight);

            }

            MoveDirection chosen = weights.entrySet().stream().max(Map.Entry.comparingByValue()).get().getKey();

            defaultGrid.put(whereMoveTake(chosen,bot),defaultGrid.get(whereMoveTake(chosen,bot))-1);//Find the best option.
            
        }
            return defaultGrid;}
        else if (bot instanceof CrabberInTraining)//Now, what if it's CrabberInTraining?
        {
            if(ticks<10||(50<ticks&&ticks<60)||(150<ticks&&ticks<160)||(300<ticks&&ticks<310)||(800<ticks&&ticks<810))
            {
                //On the first 10 ticks of a round, it moves diagonally upwards, so we can reuse the code from ChaoticWalkerIII.
                MoveDirection v = whichWayUp(bot);
                if(v==NORTH)
                {
                    Point p = whereMoveTake(NORTH_EAST,bot);
                    Point p2 = whereMoveTake(NORTH_WEST,bot);
                    defaultGrid.put(p, defaultGrid.getOrDefault(p,0.)-.5);
                    defaultGrid.put(p2, defaultGrid.getOrDefault(p2,0.)-.5);
                }
                else if (v==SOUTH)
                {
                    Point p = whereMoveTake(SOUTH_EAST,bot);
                    Point p2 = whereMoveTake(SOUTH_WEST,bot);
                    defaultGrid.put(p, defaultGrid.getOrDefault(p,0.)-.5);
                    defaultGrid.put(p2, defaultGrid.getOrDefault(p2,0.)-.5);
                }
                else if (v==EAST)
                {
                    Point p = whereMoveTake(NORTH_EAST,bot);
                    Point p2 = whereMoveTake(SOUTH_EAST,bot);
                    defaultGrid.put(p, defaultGrid.getOrDefault(p,0.)-.5);
                    defaultGrid.put(p2, defaultGrid.getOrDefault(p2,0.)-.5);
                }
                else
                {
                    Point p = whereMoveTake(NORTH_WEST,bot);
                    Point p2 = whereMoveTake(SOUTH_WEST,bot);
                    defaultGrid.put(p, defaultGrid.getOrDefault(p,0.)-.5);
                    defaultGrid.put(p2, defaultGrid.getOrDefault(p2,0.)-.5);
                }
            }
            else if (bot.getElevation()==6)
            {
                //If it's on top, it will usually stay still, but rarely will move to another point.
                defaultGrid.put(whereMoveTake(NORTH,bot),defaultGrid.get(whereMoveTake(NORTH,bot))-0.0125);
                defaultGrid.put(whereMoveTake(SOUTH,bot),defaultGrid.get(whereMoveTake(SOUTH,bot))-0.0125);
                defaultGrid.put(whereMoveTake(EAST,bot),defaultGrid.get(whereMoveTake(EAST,bot))-0.0125);
                defaultGrid.put(whereMoveTake(WEST,bot),defaultGrid.get(whereMoveTake(WEST,bot))-0.0125);
                defaultGrid.put(whereMoveTake(NORTH_EAST,bot),defaultGrid.get(whereMoveTake(NORTH_EAST,bot))-0.0125);
                defaultGrid.put(whereMoveTake(NORTH_WEST,bot),defaultGrid.get(whereMoveTake(NORTH_WEST,bot))-0.0125);
                defaultGrid.put(whereMoveTake(SOUTH_EAST,bot),defaultGrid.get(whereMoveTake(SOUTH_EAST,bot))-0.0125);
                defaultGrid.put(whereMoveTake(SOUTH_WEST,bot),defaultGrid.get(whereMoveTake(SOUTH_WEST,bot))-0.0125);
                defaultGrid.put(bot.getPosition(),defaultGrid.getOrDefault(bot.getPosition(),0.)-0.9);
            }
            else//Otherwise, CrabberInTraining will act like WeightCrab, but is generally more aggressive.
            {
                Map<MoveDirection, Double> weights = new HashMap<>();

                Map<Point, Integer> botPositionCounts = new HashMap<>();

                for (Bot bot2 : bot.getPlayers()) {
                    Point position = bot2.getPosition();
                    botPositionCounts.put(position, botPositionCounts.getOrDefault(position, 0) + 1);
                }
                for (MoveDirection direction : MoveDirection.values()) {

                    double weight = (bot.getElevationRelative(direction) - bot.getElevation()) * 20;
                    Point candidate = relativePositionFrom(direction, bot.getPosition());

                    for (Map.Entry<Point, Integer> entry : botPositionCounts.entrySet()) {

                        Point position = entry.getKey();
                        int count = entry.getValue();

                        if (defaultUpFrom(position).equals(candidate)) {
                            weight -= .3 * count;
                        }

                        if (elevationAt(candidate) > elevationAt(position) && withinReachOf(candidate, position)) {
                            weight -= count;
                        }

                    }weights.put(direction, weight);
                }

                MoveDirection chosen = weights.entrySet().stream().max(Map.Entry.comparingByValue()).get().getKey();

                defaultGrid.put(whereMoveTake(chosen,bot),defaultGrid.getOrDefault(whereMoveTake(chosen,bot),0.)-1);//Guess where it will move.

                            }
            return defaultGrid;
        }
        else//If it's an unknown bot, assume it acts like Crab. Most bots do a good chunk of the time.
        {
            Point p = whereMoveTake(moveBotUp(bot),bot);
            defaultGrid.put(p,defaultGrid.getOrDefault(p,0.)-1);
            return defaultGrid;
        }
                            }

    @Override
    protected void tick() {
        double scalingFactor = 2; //use to vary how much you care about going up
        ticks++;//Advance the turns.
        HashMap<Point,Double> weights = new HashMap<Point,Double>();
        for(int i=0;i<11;i++)
        {
            for(int j=0;j<11;j++)//For all the points on the grid:
            {
                double val;
                Point loc = new Point(i,j);
                if(getStrength()>8)//If you are strong enough
                {
                if(Math.min(10-Math.max(j, i), Math.min(j, i))+1==6) {val=12.5*scalingFactor;}//Assign (usually) 25 points to top level
                else if (Math.min(10-Math.max(j, i), Math.min(j, i))+1 == 5) {val = 7.5*scalingFactor;}//15 to level 5
                else {val =.1*Math.pow(((double)(Math.min(10-Math.max(j, i), Math.min(j, i))+1)),3);}}//6.4 to level 4, 2.7 to level 3, .8 to level 2, and .1 to level 1.
                //This means that it will always move up, except between levels 1 and 3, where a high chance of another bot interfering could cause it to stay.
                //Theoretically, if it also knows multiple bots will move to the same location, it won't move there, but on level 6, it would take 5 other bots to try, so it's unlikely to occur the one spot it could.
                else
                {
                    val = (Math.min(10-Math.max(j, i), Math.min(j, i))+1)*scalingFactor;//Otherwise, assign 12 points to level 6, 10 to level 5...
                }
                weights.put(loc,val/2.1);//Divide these weights by 2.1, so that a value of 1 (the standard if it knows a bot will move somewhere) is enough to make a cell not worth visiting relative to one a level lower if the strength is less than 9.
            }
        }
        for (Bot b : getPlayers())
        {
            weights = whatMove(b,weights);//Edit weights for each bot in the game using all that code above.
        }
        
        Point bestScoredLocation= getPosition();
        int x= getPosition().x;
        int y = getPosition().y;
        double bestScore = -1000;//Define some values we need to decide where to move.
        HashMap<Point,Double> movablePlaces = new HashMap<Point,Double>();//Create a list of places close enough to move to
        List<Point> highestScored = new ArrayList<Point>();//Create a list of which of those have the highest score
        for(int i = -1;i<2;i++)
        {
            for(int j=-1;j<2;j++)
            {
                movablePlaces.put(new Point(x+i,y+j), weights.getOrDefault(new Point(x+i,y+j),0.));
                //Fill in the list of places to move to and their weights
            }
        }
        for (Map.Entry<Point, Double> entry : movablePlaces.entrySet()) {//Find highest scored points
          if (bestScore==entry.getValue()) {
            bestScore = entry.getValue();
            highestScored.add(entry.getKey());
          } else if (entry.getValue() > bestScore) {
            bestScore = entry.getValue();
            highestScored.clear();
            highestScored.add(entry.getKey());
          }
        }
        bestScoredLocation = highestScored.get(random.nextInt(highestScored.size()));//Choose one at random (to stop other bots from using its own technique against it very well)
        if(bestScoredLocation.x==x)//Decide where to move.
        {if(bestScoredLocation.y<y){move(NORTH);}
        else if(bestScoredLocation.y>y) {move(SOUTH);}
        else{move(NONE);}}
        else if (bestScoredLocation.x>x)
        {
            if(bestScoredLocation.y<y) {move(NORTH_EAST);}
            else if(bestScoredLocation.y>y) {move(SOUTH_EAST);}
            else {move(EAST);}
        }
        else
        {
            if(bestScoredLocation.y<y) {move(NORTH_WEST);}
            else if(bestScoredLocation.y>y) {move(SOUTH_WEST);}
            else {move(WEST);}
        }
        return;
        

    }

}

Example of decision process:

Assume that GroundUpBot has reasonably high strength and is currently at level 4. In that case, it will rank all points at level 5 as having a score of 7.5, all points at level 4 as having a score of 0.05*4^3=3.2, and all points at level 3 as having a score of 1.35. However, say that another bot has a 90% chance of moving onto one of the level 5 cells and a 10% chance of moving onto a level 4 cell. That means that it will susbtract 0.9 from that cell at level 5 and .1 from the one at level 4, leaving two cells with a score of 7.5, which it will choose between at random. In this way, it will avoid cells which would lead to an unncesesary fight.

If it's strength is 8 or less, it will instead rank cells far more evenly, and will not move up a level if it is sure another bot will occupy its route of upward movement.

Generally, it scores somewhere from 5.22 to 5.28, winning against the other bots, while CrabberInTraining and ChaoticWalkerIII are usually in second and third place in some order. WeightCrab still does well, but it's usually in fourth. I don't have things set up to test with Ssammbot, so there's no code for it right now, but it behaves similarly enough to a Crab that my backup code for unidentified bots should handle things.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.