36
\$\begingroup\$

You are an enterprising dot who wants to increase the land under its control. This is quite simple - travel outside your current land and loop back into your land and everything in that loop is now owned by you. But there is a catch. If some other dot somehow finds your loop and crosses it, you die.

If you haven't already tried it, go to Splix.io and try a game. Use the arrow keys to control your movement.

GIF

enter image description here

Credit: http://splix.io/

Specifics

All players start at random positions on a 200x200 board. (I reserve the right to change this :). You will have a certain amount of moves to amass the greatest number of points possible. Points are tallied by:

  • The number of players you have killed times 300
  • The amount of land you own at the end of the round

This brings up the point that others can steal your land. If they start a loop that intersects some of your land, they can claim it. If you die during the round, you lose all points for that round.

Each round has a randomly selected group of players (max 5 unique players) (subject to change). Every player participates in an equal number of rounds. Your bot's final score is determined by its average per-game score. Each game consists of 2000 turns (also subject to change). All bots make their moves at the same time.

Death Cases

Head Butt

head butt

Both players die when they head butt each other. This is still true even when both players are at the edge of their space.

head butt

However, when only one of the players is in his land, the other player dies.

enter image description here

Line Cross

enter image description here

In this case, only the purple player dies.

You can't cross your own line.

enter image description here

Exiting the board

player going off the board

If a player attempts to exit the board, he will die and lose all points.

Capturing area

A player will capture area when he has a trail and he enters his own land again.

enter image description here

Red fills in between the two red lines. The only case where a player does not fill in is when another player is inside the loop. To be clear, this only applies when the other player himself is in the loop, not just land owned by him. A player can capture land from another person. If a player cannot fill in the area surrounded by his trail, the trail is converted directly to normal land. If the player inside another players land loop dies, the area in that loop is filled. Every time a player dies, the board is reexamined for an area that can be filled in.

Controller details

The controller is here. It is very similar to the original game, but small changes have been made to make this a better fit for KotH and for technical reasons. It is built with @NathanMerrill's KotHComm library, and with substantial help from @NathanMerrill as well. Please let me know of any bugs you find in the controller in the chat room. To be consistent with KotHComm, I have used Eclipse collections throughout the controller, but bots can be written using only the Java Collections library.

Everything is packaged in an uberjar on the github releases page. To use it, download it and attach it to your project so you can use it for auto-compleat (instructions for IntelliJ, Eclipse). To test your submissions, you run the jar with java -jar SplixKoTH-all.jar -d path\to\submissions\folder. Ensure that path\to\submissions\folder has a subfoler named java, and to place all your files there. Do not use package names in your bots (although it may be possible with KotHComm, it's just a bit more trouble). To see all options, use --help. To load all the bots, use --question-id 126815.

Writing a bot

To start writing a bot, you must extend SplixPlayer.

  • Direction makeMove(ReadOnlyGame game, ReadOnlyBoard board)
    • This is where you decide which move you want your bot to make. Must not return null.
  • HiddenPlayer getThisHidden()
    • Get the HiddenPlayer version of this. Useful for comparing your bot to the board.

enum Direction

  • Values
    • East (x = 1; y = 0)
    • West (x = -1; y = 0)
    • North (x = 0; y = 1)
    • South (x = 0; y = -1)
  • Direction leftTurn()
    • Get the Direction that you would get if you made a left turn.
  • Direction RightTurn()
    • Get the Direction that you would get if you made a right turn.

ReadOnlyBoard

This is the class where you access the board. You can either get a local view (20x20) of the board with the player positions shown, or a global view (the entire board) with only the information of who owns and claims positions on the board. This is also where you get your position.

  • SquareRegion getBounds()
    • Retrieve the size of the board.
  • MutableMap<com.nmerrill.kothcomm.game.maps.Point2D,ReadOnlySplixPoint> getGlobal()
    • Get a global map of the board.
  • MutableMap<com.nmerrill.kothcomm.game.maps.Point2D,ReadOnlySplixPoint> getView()
    • Same as getGlobal(), except that it is limited to a 20x20 area around your player, and that it shows player positions.
  • Point2D getPosition(SplixPlayer me)
    • Get the position of your player. Use as board.getPosition(this).
  • Point2D getSelfPosition(ReadOnlyBoard)
    • Get your position on the board. Usage: Point2D mypos = getSelfPosition(board)

ReadOnlyGame

ReadOnlyGame only provides access to the number of turns left in the game through int getRemainingIterations().

ReadOnlySplixPoint

  • HiddenPlayer getClaimer()
    • Get the HiddenPlayer version of who is claiming a point - claiming = a trail.
  • HiddenPlayer getOwner()
    • Get who owns a point.
  • HiddenPlayer getWhosOnSpot()
    • If the player is positioned on this point, return the hidden version of it. Only works in getLocal().

Point2D

Unlike the other classes here, Point2D is contained in the KotHComm library. com.nmerrill.kothcomm.game.maps.Point2D

  • Point2D(int x, int y)
  • int getX()
  • int getY()
  • Point2D moveX(int x)
  • Point2D moveY(int y)
  • Point2D wrapX(int maxX)
    • Wrap the x value to be within the range of maxX.
  • Point2D wrapY(int maxY)
    • Wrap the y value to be within the range of maxY.
  • int cartesianDistance(Point2D other)
    • This translates to how many turns it would take for a player to move from point a to point b.

Clojure support

The Clojure compiler is bundled with the SplixKoTH-all.jar, so you can use Clojure for your bot! Refer to my random_bot to see how to use it.

Debugging a bot

The controller comes with a debugger to help test strategies. To start it, run the jar with the --gui option.

To attach the debugger to your jar, follow these instructions for IntelliJ, or these instructions for Eclipse (Eclipse version untested).

enter image description here

If you are using a debugger with your code, you can use this to help visualize what your bot is seeing. Set a breakpoint at the beginning of makeMove on your bot, and ensure that it only pauses the current thread. Next, click the start button on the UI and step through your code.

enter image description here

Now, to put it all together:

Running bots

To run your bots with others, you need to run the jar on the releases page. Here is a list of flags:

  • --iterations (-i) <= int (default 500)
    • Specify the number of games to run.
  • --test-bot (-t) <= String
    • Run only the games that the bot is included in.
  • --directory (-d) <= Path
    • The directory to run submissions from. Use this to run your bots. Ensure that your bots are a in a subfolder of the path named java.
  • --question-id (-q) <= int (only use 126815)
    • Download and compile the other submissions from the site.
  • --random-seed (-r) <= int (defaults to a random number)
    • Give a seed to the runner so that bots that use random can have results reproduced.
  • --gui (-g)
    • Run the debugger ui instead of running a tournament. Best used with --test-bot.
  • --multi-thread (-m) <= boolean (default true)
    • Run a tournoment in multi-thread mode. This enables a faster result if your computer has multiple cores.
  • --thread-count (-c) <= int (default 4)
    • Number of threads to run if multi-thread is allowed.
  • --help (-h)
    • Print a help message similar to this.

To run all the submissions on this page, use java -jar SplixKoTH-all.jar -q 126815.

Formatting your post

To ensure that the controller can download all the bots, you should follow this format.

[BotName], Java                     // this is a header
                                    // any explanation you want
[BotName].java                      // filename, in the codeblock
[code]

Also, do not use a package declaration.


Scoreboard

+------+--------------+-----------+
| Rank | Name         |     Score |
+------+--------------+-----------+
|    1 | ImNotACoward | 8940444.0 |
|    2 | TrapBot      |  257328.0 |
|    3 | HunterBot    |  218382.0 |
+------+--------------+-----------+

Please let me know if any portion of the rules are unclear, or if you find any errors in the controller in the chat room.

Have fun!

\$\endgroup\$
  • \$\begingroup\$ Hey, this finally got posted! I was wondering :D \$\endgroup\$ – MD XF Jun 16 '17 at 2:33
  • \$\begingroup\$ How long have you been waiting? ;) Do you plan on submitting? \$\endgroup\$ – J Atkin Jun 16 '17 at 2:44
  • \$\begingroup\$ I don't know whether or not I will be able to solve a challenge like this, as I mainly write programs in esolangs. But I saw it in the sandbox and it looked like a great challenge! \$\endgroup\$ – MD XF Jun 16 '17 at 2:45
  • \$\begingroup\$ @hyperneutrino I saw the edit, does it really bother you? Political correctness is nowhere in the purview of this post, and it is perfectly correct English grammar... \$\endgroup\$ – J Atkin Jun 16 '17 at 2:47
  • 2
    \$\begingroup\$ 0.o small world? I know (of) the developer of splix.io. (Tweeted this @ him) \$\endgroup\$ – CAD97 Jun 16 '17 at 9:57
2
\$\begingroup\$

ImNotACoward, Java

This bot is a coward survival expert. If there is no enemy nearby, he claims a part of the land. If the loop of another player can be reached safely, he stabs the other player in the back engages the other player in a duel. If the other player cannot be attacked safely, he flees performs a strategic retreat to his own land.

ImNotACoward.java
import org.eclipse.collections.api.map.MutableMap;
import org.eclipse.collections.api.set.MutableSet;
import org.eclipse.collections.impl.factory.Lists;
import org.eclipse.collections.impl.factory.Maps;
import org.eclipse.collections.impl.factory.Sets;

import com.jatkin.splixkoth.ppcg.game.Direction;
import com.jatkin.splixkoth.ppcg.game.SplixPlayer;
import com.jatkin.splixkoth.ppcg.game.readonly.HiddenPlayer;
import com.jatkin.splixkoth.ppcg.game.readonly.ReadOnlyBoard;
import com.jatkin.splixkoth.ppcg.game.readonly.ReadOnlyGame;
import com.jatkin.splixkoth.ppcg.game.readonly.ReadOnlySplixPoint;
import com.nmerrill.kothcomm.game.maps.Point2D;
import com.nmerrill.kothcomm.game.maps.graphmaps.bounds.point2D.SquareRegion;

public class ImNotACoward extends SplixPlayer {
    private static final MutableSet<Direction> DIRECTIONS = Sets.mutable.of(Direction.values());

    private static class Board {
        public MutableSet<Point2D> allPoints = null;
        private SquareRegion globalBounds = null;
        private SquareRegion viewBounds = null;
        private MutableMap<Point2D, ReadOnlySplixPoint> global = null;
        private MutableMap<Point2D, ReadOnlySplixPoint> view = null;

        public void update(ReadOnlyBoard readOnlyBoard) {
            if (this.allPoints == null) {
                this.allPoints = readOnlyBoard.getGlobal().keysView().toSet();
                this.globalBounds = readOnlyBoard.getBounds();
            }
            this.viewBounds = readOnlyBoard.viewingArea;
            this.global = readOnlyBoard.getGlobal();
            this.view = readOnlyBoard.getView();
        }

        public boolean inBounds(Point2D point) {
            return globalBounds.inBounds(point);
        }

        public boolean inView(Point2D point) {
            return viewBounds.inBounds(point);
        }

        public ReadOnlySplixPoint getSplixPoint(Point2D point) {
            return inView(point) ? view.get(point) : global.get(point);
        }

        public MutableSet<Point2D> getNeighbors(Point2D point) {
            return DIRECTIONS.collect(d -> point.move(d.vector.getX(), d.vector.getY())).select(this::inBounds);
        }

        public MutableSet<Point2D> getNeighbors(MutableSet<Point2D> points) {
            return points.flatCollect(this::getNeighbors);
        }

        public MutableSet<Point2D> getBorders(SquareRegion region) {
            return allPoints.select(p -> region.inBounds(p) &&
                    (p.getX() == region.getLeft() || p.getX() == region.getRight() ||
                    p.getY() == region.getTop() || p.getY() == region.getBottom() ||
                    p.getX() == globalBounds.getLeft() || p.getX() == globalBounds.getRight() ||
                    p.getY() == globalBounds.getTop() || p.getY() == globalBounds.getBottom()));
        }
    }

    private class Player {
        public final HiddenPlayer hiddenPlayer;
        public MutableSet<Point2D> owned = Sets.mutable.empty();
        private MutableSet<Point2D> unowned = null;
        private MutableSet<Point2D> oldClaimed = Sets.mutable.empty();
        public MutableSet<Point2D> claimed = Sets.mutable.empty();
        private MutableSet<Point2D> oldPos = Sets.mutable.empty();
        public MutableSet<Point2D> pos = Sets.mutable.empty();

        public Player(HiddenPlayer hiddenPlayer) {
            super();
            this.hiddenPlayer = hiddenPlayer;
        }

        public void nextMove() {
            owned.clear();
            unowned = null;
            oldClaimed = claimed;
            claimed = Sets.mutable.empty();
            oldPos = pos;
            pos = Sets.mutable.empty();
        }

        public MutableSet<Point2D> getUnowned() {
            if (unowned == null) {
                unowned = board.allPoints.difference(owned);
            }
            return unowned;
        }

        public void addOwned(Point2D point) {
            owned.add(point);
        }

        public void addClaimed(Point2D point) {
            claimed.add(point);
        }

        public void setPos(Point2D point) {
            pos.clear();
            pos.add(point);
        }

        public void calcPos() {
            if (pos.isEmpty()) {
                MutableSet<Point2D> claimedDiff = claimed.difference(oldClaimed);
                if (claimedDiff.size() == 1) {
                    pos = board.getNeighbors(claimedDiff).select(p -> !claimed.contains(p) && !board.inView(p));
                } else if (!oldPos.isEmpty()) {
                    pos = board.getNeighbors(oldPos).select(p -> owned.contains(p) && !board.inView(p));
                } else {
                    pos = owned.select(p -> !board.inView(p));
                }
            }
        }
    }

    private Board board = new Board();
    private Point2D myPos = null;
    private final Player nobody = new Player(new HiddenPlayer(null));
    private final Player me = new Player(new HiddenPlayer(this));
    private MutableMap<HiddenPlayer, Player> enemies = Maps.mutable.empty();
    private MutableMap<HiddenPlayer, Player> players = Maps.mutable.of(nobody.hiddenPlayer, nobody, me.hiddenPlayer, me);
    private MutableSet<Point2D> path = Sets.mutable.empty();

    private Player getPlayer(HiddenPlayer hiddenPlayer) {
        Player player = players.get(hiddenPlayer);
        if (player == null) {
            player = new Player(hiddenPlayer);
            players.put(player.hiddenPlayer, player);
            enemies.put(player.hiddenPlayer, player);
        }
        return player;
    }

    private Direction moveToOwned() {
        MutableSet<Point2D> targets = me.owned.difference(me.pos);
        if (targets.isEmpty()) {
            return moveTo(myPos);
        } else {
            return moveTo(targets.minBy(myPos::cartesianDistance));
        }
    }

    private Direction moveTo(Point2D target) {
        return DIRECTIONS.minBy(d -> {
            Point2D p = myPos.move(d.vector.getX(), d.vector.getY());
            return !board.inBounds(p) || me.claimed.contains(p) ? Integer.MAX_VALUE : target.cartesianDistance(p);
        });
    }

    @Override
    protected Direction makeMove(ReadOnlyGame readOnlyGame, ReadOnlyBoard readOnlyBoard) {
        board.update(readOnlyBoard);
        myPos = readOnlyBoard.getPosition(this);
        path.remove(myPos);

        for (Player e : players.valuesView()) {
            e.nextMove();
        }
        for (Point2D point : board.allPoints) {
            ReadOnlySplixPoint splixPoint = board.getSplixPoint(point);
            getPlayer(splixPoint.getOwner()).addOwned(point);
            getPlayer(splixPoint.getClaimer()).addClaimed(point);
            getPlayer(splixPoint.getWhosOnSpot()).setPos(point);
        }
        for (Player e : players.valuesView()) {
            e.calcPos();
        }

        if (me.owned.contains(myPos) && path.allSatisfy(p -> me.owned.contains(p))) {
            path.clear();
        }

        if (path.isEmpty()) {
            MutableSet<Point2D> enemyPositions = enemies.valuesView().flatCollect(e -> e.pos).toSet();
            int enemyDistance = enemyPositions.isEmpty() ? Integer.MAX_VALUE :
                    enemyPositions.minBy(myPos::cartesianDistance).cartesianDistance(myPos);

            if (enemyDistance < 20) {
                MutableSet<Point2D> enemyClaimed = enemies.valuesView().flatCollect(e -> e.claimed).toSet();
                if (!enemyClaimed.isEmpty()) {
                    Point2D closestClaimed = enemyClaimed.minBy(myPos::cartesianDistance);
                    if (closestClaimed.cartesianDistance(myPos) < enemyDistance) {
                        return moveTo(closestClaimed);
                    } else if (enemyDistance < 10) {
                        return moveToOwned();
                    }
                }
            }

            if (me.owned.contains(myPos)) {
                if (!me.getUnowned().isEmpty()) {
                    Point2D target = me.getUnowned().minBy(myPos::cartesianDistance);
                    if (target.cartesianDistance(myPos) > 2) {
                        return moveTo(target);
                    }
                }

                int safeSize = Math.max(1, Math.min(enemyDistance / 6, readOnlyGame.getRemainingIterations() / 4));
                SquareRegion region = Lists.mutable
                        .of(new SquareRegion(myPos, myPos.move(safeSize, safeSize)),
                                new SquareRegion(myPos, myPos.move(safeSize, -safeSize)),
                                new SquareRegion(myPos, myPos.move(-safeSize, safeSize)),
                                new SquareRegion(myPos, myPos.move(-safeSize, -safeSize)))
                        .maxBy(r -> me.getUnowned().count(p -> r.inBounds(p)));
                path = board.getBorders(region);
            } else {
                return moveToOwned();
            }
        }

        if (!path.isEmpty()) {
            return moveTo(path.minBy(myPos::cartesianDistance));
        }

        return moveToOwned();
    }
}
\$\endgroup\$
  • \$\begingroup\$ Interesting. Very nice! I wonder how much better this could be made... \$\endgroup\$ – J Atkin Jun 18 '17 at 20:33
1
\$\begingroup\$

TrapBot, Java

TrapBot.java

import com.jatkin.splixkoth.ppcg.game.Direction;
import com.jatkin.splixkoth.ppcg.game.SplixPlayer;
import com.jatkin.splixkoth.ppcg.game.readonly.ReadOnlyBoard;
import com.jatkin.splixkoth.ppcg.game.readonly.ReadOnlyGame;
import com.jatkin.splixkoth.ppcg.game.readonly.ReadOnlySplixPoint;
import com.nmerrill.kothcomm.game.maps.Point2D;
import com.nmerrill.kothcomm.game.maps.graphmaps.bounds.point2D.SquareRegion;
import javafx.util.Pair;
import org.eclipse.collections.api.map.MutableMap;
import org.eclipse.collections.impl.factory.Lists;

import java.util.Comparator;

/**
 * Trap bot goes to the wall and traces the entirety around. Hopes that
 * the players in the middle die and that nobody challenges him. Nearly 
 * all turns are left turns.
 */
public class TrapBot extends SplixPlayer {

    /**
     * Mode when the bot is attempting to reach the wall from it's original spawn
     * location.
     */
    public static final int MODE_GOING_TO_WALL = 1;

    /**
     * Mode when we have reached the wall and are now going around the board.
     */
    public static final int MODE_FOLLOWING_WALL = 2;

    private int mode = MODE_GOING_TO_WALL;

    public static int WALL_EAST = 1;
    public static int WALL_NORTH = 2;
    public static int WALL_WEST = 3;
    public static int WALL_SOUTH = 4;


    /**
     * How long the bot would like to go before he turns around to go back home.
     */
    private static final int PREFERRED_LINE_DIST = 5;

    private int distToTravel = 0;

    private Direction lastMove = Direction.East;// could be anything that's not null
    private int lastTrailLength = 0;

    @Override
    protected Direction makeMove(ReadOnlyGame game, ReadOnlyBoard board) {
        Direction ret = null;
        MutableMap<Point2D, ReadOnlySplixPoint> view = board.getView();
        int trailLength = getTrailLength(board, view);

        if (trailLength == 0) {

            int closestWall = getClosestWall(board);
            Direction directionToWall = getDirectionToWall(closestWall);

            if (lastTrailLength != 0) {
                ret = lastMove.leftTurn();
                // move to the other half of 2 width line so we can start without shifting to the left
            }

            if (mode == MODE_GOING_TO_WALL && ret == null) {
                int distCanTravel = getDistCanTravel(
                        getSelfPosition(board), board.getBounds(), directionToWall);
                if (distCanTravel == 0) mode = MODE_FOLLOWING_WALL;
                else ret = directionToWall;
                distToTravel = distCanTravel;

            }

            if (mode == MODE_FOLLOWING_WALL && ret == null) {
                int distCanTravel = 0;
                ret = directionToWall;
                while (distCanTravel == 0) {// keep turning left until we can get somewhere
                    ret = ret.leftTurn();
                    distCanTravel = getDistCanTravel(
                            getSelfPosition(board), board.getBounds(), ret);
                }

                distToTravel = distCanTravel;
            }
        }

        // once we have started we are on auto pilot (can't run after the before block)
        else if (trailLength == distToTravel || trailLength == (distToTravel + 1))
            ret = lastMove.leftTurn();

        if (ret == null)// if we don't have a move otherwise, we must be on our trail. ret same as last time
            ret = lastMove;

        lastTrailLength = trailLength;
        lastMove = ret;
        return ret;
    }

    int getClosestWall(ReadOnlyBoard board) {
        Point2D thisPos = getSelfPosition(board);
        return Lists.mutable.of(
                new Pair<>(WALL_NORTH, board.getBounds().getTop() - thisPos.getY()),
                new Pair<>(WALL_SOUTH, thisPos.getY()), 
                new Pair<>(WALL_EAST, board.getBounds().getRight() - thisPos.getX()),
                new Pair<>(WALL_WEST, thisPos.getX())
        ).min(Comparator.comparingInt(Pair::getValue)).getKey();
    }

    /**
     * This goes around some intended behavior in the controller to get the correct result. When a player goes outside
     * his territory the land under him is converted to a trail -- on the next step of the game. So a trail length may
     * be the count of the trail locations plus one. That is what this function calculates. Depends on the whole trail
     * being contained inside the view passed to it.
     * @return
     */
    int getTrailLength(ReadOnlyBoard board, MutableMap<Point2D, ReadOnlySplixPoint> view) {
        boolean isPlayerOutsideHome = !view.get(getSelfPosition(board)).getOwner().equals(getThisHidden());
        int trailLength = view.count(rop -> rop.getClaimer().equals(getThisHidden()));
        return trailLength + (isPlayerOutsideHome? 1 : 0);
    }

    /**
     * Calculate how far we can travel in the direction before we hit the wall.
     * @return
     */
    int getDistCanTravel(Point2D currPos, SquareRegion bounds, Direction direction) {
        for (int i = 1; i <= PREFERRED_LINE_DIST; i++) {
            if (!bounds.inBounds(currPos.move(direction.vector.getX()*i, direction.vector.getY()*i)))
                return i-1;
        }
        return PREFERRED_LINE_DIST;
    }

    /**
     * Get which direction needs to be traveled to reach the specified wall.
     * Requires that neither Direction nor the values of `WALL_...` change.
     * @param targetWall
     * @return
     */
    Direction getDirectionToWall(int targetWall) {
        return Direction.values()[targetWall-1];
    }
}

This is perhaps the most simple bot. All it does is trace out the edge of the board, doubling back on itself to reduce risk of being killed.

\$\endgroup\$
  • \$\begingroup\$ Cool to see you used Eclipse Collections. There is a Pair interface in EC. You can use Tuples.pair() to get a Pair instance. There is also a PrimitiveTuples class if either or both of the values in the pair are primitives. \$\endgroup\$ – Donald Raab Jun 16 '17 at 3:37
1
\$\begingroup\$

random_bot, Clojure

This is RandomBot, but I had to stick to naming conventions, and some issues prevent me from using the hyphen in the name, so underscores reign! The make-move fn returns a vec with the first item being the Direction you want to move in, and the second being the state you want to be passed back to you on the next turn. Do not use any external atoms, since this code may be running multiple games in parallel.

 random_bot.clj
 (ns random-bot
     (:import
      [com.jatkin.splixkoth.ppcg.game Direction]))

 (defn make-move [game board state]
       [(rand-nth [Direction/East
                   Direction/West
                   Direction/North
                   Direction/South])
        nil])
\$\endgroup\$
0
\$\begingroup\$

HunterBot, Java

HunterBot.java

import com.jatkin.splixkoth.ppcg.game.Direction;
import com.jatkin.splixkoth.ppcg.game.SplixPlayer;
import com.jatkin.splixkoth.ppcg.game.readonly.HiddenPlayer;
import com.jatkin.splixkoth.ppcg.game.readonly.ReadOnlyBoard;
import com.jatkin.splixkoth.ppcg.game.readonly.ReadOnlyGame;
import com.jatkin.splixkoth.ppcg.game.readonly.ReadOnlySplixPoint;
import com.nmerrill.kothcomm.game.maps.Point2D;
import org.eclipse.collections.api.map.MutableMap;
import org.eclipse.collections.api.set.ImmutableSet;
import org.eclipse.collections.impl.factory.Sets;

import java.util.Comparator;

/**
 * This bot looks for any trail points left behind by another player and sets that as his target. If the target ever
 * disappears, it will continue on in hopes that the player will return soon, or if another target appears, it will
 * go towards that one. Works best when the other player repeatedly goes in the same general direction.
 */
public class HunterBot extends SplixPlayer {

    private Point2D lastTarget;

    private Direction lastMove = Direction.East;

    @Override
    protected Direction makeMove(ReadOnlyGame game, ReadOnlyBoard board) {
        Point2D thisPos = getSelfPosition(board);
        MutableMap<Point2D, ReadOnlySplixPoint> global = board.getGlobal();
        MutableMap<Point2D, ReadOnlySplixPoint> targets = global.select((pt, rosp) ->
                !rosp.getClaimer().equals(getThisHidden()) 
                        && !rosp.getClaimer().equals(new HiddenPlayer(null)));

        if (targets.size() == 0 && lastTarget == null) {
            lastMove = lastMove.leftTurn();
            return lastMove;
        }

        Point2D target = null;
        if (targets.size() == 0) target = lastTarget;
        else target = targets.keysView().min(Comparator.comparingInt(thisPos::cartesianDistance));
        if (target.equals(thisPos)) {
            lastTarget = null;
            if (global.get(thisPos).getOwner().equals(getThisHidden())) {
                lastMove = lastMove.leftTurn();
                return lastMove;
            } else 
            // time to go home
            target = global.select((z_, x) -> getThisHidden().equals(x.getOwner())).keySet().iterator().next();

        }

        lastTarget = target;
        lastMove = makeSafeMove(target, global, board, thisPos);
        return lastMove;
    }

    private Direction makeSafeMove(Point2D targetLocation, MutableMap<Point2D, ReadOnlySplixPoint> map, ReadOnlyBoard board, Point2D currLoc) {
        Point2D dist = targetLocation.move(-currLoc.getX(), -currLoc.getY());
        ImmutableSet<Direction> possibleMoves = Sets.immutable.of(Direction.values())
                .select(x -> {
                    Point2D pos = currLoc.move(x.vector.getX(), x.vector.getY());
                    return !board.getBounds().outOfBounds(pos) && !getThisHidden().equals(map.get(pos).getClaimer());
                });
        Direction prefMove;
        if (Math.abs(dist.getX()) > Math.abs(dist.getY()))
            prefMove = getDirectionFroPoint(new Point2D(normalizeNum(dist.getX()), 0));
        else
            prefMove = getDirectionFroPoint(new Point2D(0, normalizeNum(dist.getY())));

        if (possibleMoves.contains(prefMove)) return prefMove;
        if (possibleMoves.contains(prefMove.leftTurn())) return prefMove.leftTurn();
        if (possibleMoves.contains(prefMove.rightTurn())) return prefMove.rightTurn();
        return prefMove.leftTurn().leftTurn();
    }

    private Direction getDirectionFroPoint(Point2D dir) {
        return Sets.immutable.of(Direction.values()).select(d -> d.vector.equals(dir)).getOnly();
    }

    private int normalizeNum(int n) { if (n < -1) return -1; if (n > 1) return 1; else return n;}

}

One of the most basic bots. It searches the board for spots to kill others, and it will follow the shortest path possible to get to a killing position. If it is outside its territory, it will make random moves until it has another opening to kill another player. It has some logic to prevent it fomr running over itself, and when all other players are dead it goes back to its home. Once home it just goes in a little square.

\$\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.