Splix.io - King of the land

37

6

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!

J Atkin

Posted 2017-06-16T02:23:57.300

Reputation: 4 846

Hey, this finally got posted! I was wondering :D – MD XF – 2017-06-16T02:33:28.863

How long have you been waiting? ;) Do you plan on submitting? – J Atkin – 2017-06-16T02:44:44.910

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! – MD XF – 2017-06-16T02:45:34.177

@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... – J Atkin – 2017-06-16T02:47:40.483

@Phoenix Totally! I also use IntelliJ for my other projects (scala, groovy, haskell, clojure) and it works perfectly! – J Atkin – 2017-06-16T02:55:52.397

Well actually I'm not asleep @MDXF I just wanted to leave TNB for the night (as I said, sleep is overrated :P). It didn't actually bother me, but here's my rationale for why I edited it. First of all, given how the internet can be sometimes, that may attract negative comments or votes, though I doubt anyone would downvote for that given that higher rep users usually have more sense than that, but I would hate to see such a good challenge attract negative feedback for that, so unless unspecified bothers you, I would rather be safe than sorry. Thanks! – HyperNeutrino – 2017-06-16T03:13:25.503

@HyperNeutrino "leave TNB for the night" not a phrase I understand :P but good to know about the edit. Thanks – MD XF – 2017-06-16T03:14:04.987

@MDXF Eh you're right I know I'm going to end up going back someone soon though my phone cuts off connection to the router at midnight – HyperNeutrino – 2017-06-16T03:16:01.037

@HyperNeutrino Ah, ok. I don't mind, just curious since that seems really small... – J Atkin – 2017-06-16T03:26:55.830

20.o small world? I know (of) the developer of splix.io. (Tweeted this @ him) – CAD97 – 2017-06-16T09:57:37.537

Alright. Yeah, I mean it is a very small edit (that's why I just went ahead and did it instead of creating a comment thread about it) but I'd hate for this challenge to experience issues because of that. And I should make some interface to port these answers into actual Splix.io and see how well it performs against the actual players of Splix! :D – HyperNeutrino – 2017-06-16T22:49:56.853

That would be pretty cool! And even the perfect player here would probably not be unbeatable on the real site, because of lag. – J Atkin – 2017-06-16T23:05:51.320

Are we allowed to use any JVM language in this challenge? I might try it in Clojure (since it has support for class extension). – clismique – 2017-06-17T02:31:05.467

That would be really awesome, except the submissions are downloaded from this page in java and are compiled by KotHComm, which doesn't understand clojure yet. If you're willing to hold off for a bit though, I may be able to extend the support. KotHComm comes with the ability to support other languages. I'll see if I can extend it to other JVM languages. – J Atkin – 2017-06-17T14:18:28.060

@Qwerp-Derp Update. It is possible, and I think it can be done in the next 2-3 days, if you're willing to wait that long. Even if you don't plan on using it, I'll be working on it since it seems like a cool/useful project. – J Atkin – 2017-06-17T17:35:34.237

As usual, a cross-language API would be desirable. – CalculatorFeline – 2017-06-18T23:37:05.173

Of course, but I don't want to deal with that. KoTHComm supports an io based bot, but that requires making a text based format for transmitting board information -- every turn -- and supporting the languages on my machine. Not to mention the enormous performance hit. Clojure and other JVM languages have the benefit of very low performance overhead, and being able to (more or less) directly interface with my code. Plus the platform support issue goes away, since I can even include the Clojure compiler in my jar (hopefully this is true for others as well). – J Atkin – 2017-06-19T00:13:22.867

@Qwerp-Derp Clojure support is now available! It ain't pretty, but it works ;) – J Atkin – 2017-06-19T20:09:32.120

http://splix.io/#team-baMPy for the code-golf team. – PyRulez – 2017-06-20T00:00:13.010

The real question is, can we use these bots on splix :p – Timtech – 2017-06-20T01:04:23.653

Answers

2

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();
    }
}

Sleafar

Posted 2017-06-16T02:23:57.300

Reputation: 2 722

1

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.

J Atkin

Posted 2017-06-16T02:23:57.300

Reputation: 4 846

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. – Donald Raab – 2017-06-16T03:37:41.017

1

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])

J Atkin

Posted 2017-06-16T02:23:57.300

Reputation: 4 846

0

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.

J Atkin

Posted 2017-06-16T02:23:57.300

Reputation: 4 846