title: King of the Ziggurat

text:
# Question
Title: King of the Ziggurat
## 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

```

# Answer
> 7 votes
# 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);
        }
    }
}

```

# Answer
> 7 votes
# 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;
    }

}

```

# Answer
> 6 votes
# 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.

# Answer
> 5 votes
# 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);
                                   }}}

```

# Answer
> 4 votes
# 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.

# Answer
> 4 votes
## 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

# Answer
> 3 votes
# 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")`

<pre><code>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
```
</code></pre>

# Answer
> 1 votes
# 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.

<pre><code>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);

        }}}
```
</code></pre>

# Answer
> 1 votes
# 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.

---
Tags: king-of-the-hill
---


title: Bounce-modulo two numbers

text:
# Question
Title: Bounce-modulo two numbers
The graph of the modulo operation (\\$y = x \mod k\\$) looks like this:

This is a very useful function, as it allows us to create "wrapping" behavior. However, it is very cumbersome when I want to use it to create an appearance of "bouncing" between two walls. The graph of the "bounce" function (\\$y = \text{bounce} (x, k)\\$) looks like this:

The period of the graph of \\$y = x \mod k\\$ is \\$k\\$. The period of the graph of \\$y = \text{bounce} (x, k)\\$ is \\$2k\\$, because it moves upwards for \\$k\\$ units, and then moves downwards for another \\$k\\$ units, before returning to where it started. For both functions, the minimum value for \\$y\\$ is 0, and the maximum is \\$k\\$ (Actually, for the modulus function with integral inputs, it's \\$k-1\\$). In addition, for both functions, the value where \\$x=0\\$ is 0.

## The challenge

Given an integer \\$x\\$ and a positive integer \\$k\\$, return an integer or floating-point approximation of \\$y = \text{bounce} (x, k)\\$.

This is , so the shortest valid submission (counted in bytes) wins.

## Test Cases

```
  x,  k -> bounce(x, k)
  0, 14 ->            0
  3,  7 ->            3
 14, 14 ->           14
 15, 14 ->           13
-13, 14 ->           13 (12.999997 etc would be an acceptable answer)
-14, 14 ->           14
191,  8 ->            1
192,  8 ->            0

```

Bonus points for a Fourier-based approach in Fourier.

# Answer
> 8 votes
# x86-64 Machine Code, 18 bytes

```
97
99
31 D0
29 D0
99
F7 FE
29 D6
A8 01
0F 45 D6
92
C3

```

This code defines a function in x86-64 machine language that computes `bounce(x, k`). Following the System V AMD64 calling convention used on Gnu/Unix systems, the `x` parameter is passed in the `EDI` register, while the `k` parameter is passed in the `ESI` register. As with all x86 calling conventions, the result is returned in the `EAX` register.

To call this from C, you would prototype it as follows:

```
int Bounce(int x, int k);

```

**Try it online!**

Ungolfed assembly mnemonics:

```
; Take absolute value of input 'x' (passed in EDI register).
; (Compensates for the fact that IDIV on x86 returns a remainder with the dividend's sign,
; whereas we want 'modulo' behavior---the result should be positive.)
xchg   eax, edi      ; swap EDI and EAX (put 'x' in EAX)
cdq                  ; sign-extend EAX to EDX:EAX, effectively putting sign bit in EDX
xor    eax, edx      ; EAX ^= EDX
sub    eax, edx      ; EAX -= EDX

; Divide EDX:EAX by 'k' (passed in ESI register).
; The quotient will be in EAX, and the remainder will be in EDX.
; (We know that EAX is positive here, so we'd normally just zero EDX before division,
; but XOR is 2 bytes whereas CDQ is 1 byte, so it wins out.)
cdq
idiv   esi

; Pre-emptively subtract the remainder (EDX) from 'k' (ESI),
; leaving result in ESI. We'll either use this below, or ignore it.
sub    esi, edx

; Test the LSB of the quotient to see if it is an even number (i.e., divisible by 2).
; If not (quotient is odd), then we want to use ESI, so put it in EDX.
; Otherwise (quotient is even), leave EDX alone.
test   al, 1
cmovnz edx, esi

; Finally, swap EDX and EAX to get the return value in EAX.
xchg   eax, edx
ret

```

Note that the first section (that takes the absolute value) could have equivalently been written:

```
; Alternative implementation of absolute value
xchg    eax, edi
neg     eax
cmovl   eax, edi

```

which is the exact same number of bytes (6). The performance should be similar, perhaps slightly faster (except on certain Intel chips, where conditional moves are slow).

`XCHG` is, of course, relatively slow and would not be preferred over `MOV` except in code golfing (since the `XCHG` is 1-byte when one of the operands is the accumulator, whereas a register-register `MOV` is always 2 bytes).

# Answer
> 6 votes
# Python 2, 29 27 bytes

```
lambda x,k:abs(x%k-x/k%2*k)

```

Try it online!

# Answer
> 6 votes
# Jelly, 3 bytes

```
æ%A

```

Try it online!

Built-ins ftw.

### Explanation

`æ%` is a useful built-in here. I don't know how to describe it, so I'll just provide the output for some inputs:

As `x` goes from `0` to infinity, `xæ%4` goes `0,1,2,3,4,(-3,-2,-1,0,1,2,3,4,)` where the part in parentheses is repeated to infinity both ways.

# Answer
> 5 votes
# Python 3, 27 bytes

```
lambda x,k:k-abs(k-x%(k*2))

```

Try it online!

# Answer
> 4 votes
## Ruby, 40 bytes 32 bytes

```
b=->(x,k){(x/k+1)%2>0?x%k:k-x%k}

```

Try it online!

### Explanation

Hi, this is my first answer on this site! This code is based on the observation that the bounce function behaves exactly like modulo when (*n*-1)*k* \<= *x* \< *nk* and n is odd, and behaves like a reversed modulo operation when *n* is even. `(x/k+1)` is the smallest integer greater than *x*/*k* (which is *x*/*k*+1 rounded down to an integer). Therefore, `(x/k+1)` finds the *n* mentioned above. `%2>0` checks to see if *n* is odd or even. If *n* mod 2 \> 0, then *n* is odd. If *n* mod 2 = 0, then *n* is even. If *n* is odd, then the bounce function should equal *x* mod *k*. If *n* is even, the bounce function should be the reverse, equal to *k* \- *x* mod *k*. The whole expression `(x/k+1)%2>0?x%k:k-x%k` finds *n*, then executes the *x* mod *k* if it is odd, and executes *k* \- *x* mod *k* otherwise.

The answer was improved based on a suggestion from Cyoce.

# Answer
> 3 votes
## JavaScript (ES6), ~~36~~ 32 bytes

```
k=>f=x=>x<0?f(-x):x>k?k-f(k-x):x

```

Recursively bounces `x` against `0` and `k`, so very much in the spirit of the challenge.

# Answer
> 2 votes
# Mathematica, 19 bytes

```
Abs@Mod[#,2#2,-#2]&

```

# Answer
> 2 votes
# Pyth, 5 bytes

```
aa%Ey

```

Verify all testcases.

Fork of my Python answer.

# Answer
> 1 votes
# J, 25 bytes

Hint:

> This is just regular modulo on the ladder numbers. For example, in the case of 5: `0 1 2 3 4 5 4 3 2 1`

Here is a (not yet well-golfed) solution in J. Will try to improve tomorrow:

```
[ ((|~ #) { ]) (i.@>:,}:@i.@-) @ ]

```

compressed: `[((|~#){])(i.@>:,}:@i.@-)@]`

compressed2: `[((|~#){])(<:|.|@}.@i:)@]`

Try it online!

# Answer
> 1 votes
# QBIC, ~~25~~ ~~30~~ 27 bytes

```
g=abs(:%:)~a'\`b%2|?b-g\?g

```

Did a bit of restructuring...

## Explanation

```
g=abs(   )  let g be the absolute value of
       %    the (regular) modulo between
      : :   input a read from cmd line, and input b read from cmd line
~a \ b%2    IF the int division of A and B mod 2 (ie parity test) yields ODD
  ' `         (int divisions need to be passed to QBasic as code literals, or ELSE...)
|?b-g       THEN print bouncy mod
\?g         ELSE print regular mod

```

# Answer
> 1 votes
# PHP, ~~40~~ 50 bytes

damn dollars. damn import overhead. :)

integer version:

```
[,$x,$k]=$argv;$y=abs($x)%$k;echo$x/$k&1?$k-$y:$y;

```

or

```
[,$x,$k]=$argv;echo[$y=abs($x)%$k,$k-$y][$x/$k&1];

```

float version, 56 bytes:

Replace `abs($x)%$k` with `fmod(abs($x),$k)`.

---

edit: fixed results for negative `x`

# Answer
> 1 votes
# C89, 40 bytes

```
t;f(x,k){t=abs(x%k);return x/k%2?k-t:t;}

```

A C port of my x86 machine code answer, this defines a function, `f`, that calculates the bounce-modulo for the parameters `x` and `k`.

It uses C89's implicit-int rule, so that both parameters, the global variable `t`, and the function's return value are all implicitly of type `int`. The global variable `t` is just used to hold a temporary value, which ends up saving bytes, compared to repeating the computation on either side of the conditional operator.

The `abs` function (absolute value) is provided in the `<stdlib.h>` header, but we don't have to include it here, again thanks to C89's implicit-int rule (where the function is implicitly declared and assumed to return `int`).

**Try it online!**

**Ungolfed version:**

```
#include <stdlib.h>

int Bounce(int x, int k)
{
    int mod = abs(x % k);
    return (x/k % 2) ? k-mod : mod;
}

```

Looking at this in light of my hand-tuned machine code, compilers actually generate pretty good output for this. I mean, they should; it's a pretty simple function to optimize! I did uncover a minor bug in GCC's x86-64 optimizer, though, where it curiously produces *larger* code when you tell it to optimize for size and *smaller* code when you tell it to optimize for *speed*.

# Answer
> 1 votes
# Haskell, 37 Bytes

**Try it online!**

```
(!)=mod;x#k|odd$x`div`k=k-x!k|1<2=x!k

```

**How to use:**
Call as `15#14` for non-negative left arguments and as `(-13)#14` for negative left arguments, because Haskell would interpret `-13#14` as `-(13#14)` if you are using something like `ghci`. The TIO-link simply takes two command-line arguments.

**Explanation:**
First redefines the binary infix operator `!` to be the same as `mod`. Haskell's `mod` always outputs a non-negative value, so we don't need the `abs` that other solutions here do. It then checks whether `x/k` (integer division) is odd and if so, returns `k-x mod k` (ie the back-bounce) or else it returns `x mod k`.

# Answer
> 1 votes
# Vyxal, 5 bytes

```
d%ε¹ε

```

Try it Online!

## How?

```
d%ε¹ε
d     # Double the (implicit) first input
 %    # Modulo the (implicit) second input by this
  ε   # Take the absolute difference of this and the (implicit) first input
   ¹ε # Take the absolute difference of this and the first input again

```

# Answer
> 0 votes
# C (gcc), ~~43~~ 53 bytes

Edit: Fixed negative issue

```
int f(int x,int y){return x/y%2?abs(y-x%y):abs(x%y);}

```

Try it Online!

# Answer
> 0 votes
# Common Lisp, 41 bytes

```
(lambda(x k)(- k(abs(-(mod x(* k 2))k))))

```

Try it online!

# Answer
> 0 votes
# R, 28 bytes

```
pryr::f(abs((x-k)%%(2*k)-k))

```

Which evaluates to the function:

```
function (k, x)
abs((x - k)%%(2 * k) - k)

```

Which appears to be the method that most solutions use. I didn't look at them before making this.

# Answer
> 0 votes
# MMIX, 20 bytes (5 instrs)

"xxd --jelly"

```
00000000: 1c02 0001 fe00 0006 24ff 0100 6600 02ff  ÷£¡¢“¡¡©$”¢¡f¡£”
00000010: f801 0000                                ẏ¢¡¡

```

disassembly and commentary

```
bounce  DIV  $2,$0,$1       // f:rR = x /% k
        GET  $0,rR          // rv = rR
        SUB  $255,$1,$0     // t = k - rv
        CSOD $0,$2,$255     // if(f odd) rv = t
        POP  1,0            // return rv

```

Not entirely sure what this does if k is negative, but it's probably the negative of bounce(x,k).

# Answer
> 0 votes
# Haskell, 23 bytes

```
x#k=abs$mod(x-k)(2*k)-k

```

Try it online!

Boring port of everything else ~~just to spite the existing answer for the pointless `(!)=mod;` ;)~~.

---
Tags: code-golf, math, number, arithmetic
---


# Question
Title: Parse Ternary Lambda Calculus for correctness
Wheat Wizard has once tried to come up with a parsing problem unsuitable for regex, but it failed due to Anders Kaseorg's Perl regex answer. This is the second attempt. Now that we know that Perl regex is far more powerful than we've imagined, this challenge aims to be un-golfy for it instead.

## Introduction

A **lambda term** is recursively defined as follows:

* A *variable* is a lambda term.
* A *lambda abstraction* of a lambda term is a lambda term.
* A *function application* of two lambda terms is a lambda term.

This is concisely written as follows (where `e` is a term and `x` is any valid variable name):

```
e = x | λx. e | e e

```

A variable is often a string (as in Wheat Wizard's challenge), but it suffers from name shadowing problem. To avoid it and for an easier encoding, we use de Bruijn indices instead. In this notation, a variable is replaced with a positive integer, which is equal to "the number of binders that are in scope between that occurrence and its corresponding binder". The grammar is simplified to the following:

```
e = i | λ e | e e

```

Some examples in this notation:

* `λx. x` is written as `λ 1`, as the inner `x` binds to the first (and only) `λ` in scope.
* `λx. λy. x` is written as `λ λ 2`, as the inner `x` binds to the second `λ`, counting from the closest one.
* `λx. λy. x (λz. z y)` is written as `λ λ 2 (λ 1 2)`.

## Ternary lambda calculus

This is an encoding of a lambda term similar to Binary lambda calculus, but in ternary (a string of digits 0, 1, and 2). A lambda term written using de Bruijn indexes is converted to Ternary lambda calculus as follows:

* A variable of index `i` is converted to binary (using 0 and 1), and then a `2` is added at the end. Since `i` is positive, this always starts with a 1.
* A lambda abstraction `λ e` is recursively converted to `0<e>`, where `<e>` denotes the Ternary conversion of `e`.
* A function application `e1 e2` is recursively converted to `2<e1><e2>`.

Examples:

* `λ 1` is encoded to `012`. (`1` encodes to `12`, and a single lambda abstraction adds a `0`)
* `λ λ 2` is encoded to `00102`.
* `λ λ 2 (λ 1 2)` is encoded to `0021020212102`.

The original BLC requires that the lambda term is **closed**, i.e. all variables are bound to some lambda abstraction. This means the variable index `i` is limited by the current "lambda depth"; in particular, no variables may appear at the top level at all. Using the lambda depth (a non-negative integer) as the context, the grammar to match becomes the following:

```
TermAtDepth(d) =
    bin(i) "2" where 1 ≤ i ≤ d
  | "0" TermAtDepth(d+1)
  | "2" TermAtDepth(d) TermAtDepth(d)

e = TermAtDepth(0)

```

## Code

```
tlc :: Int -> Parser ()
tlc depth =
  do
    choice
      [ do
        char '1'
        idx <- many $ charBy (`elem` "01")
        guard $ foldl (\x y->x*2+y) 1 [fromEnum x-48|x<-idx] <= depth
        char '2'
        return ()
      , do
        char '0'
        tlc (depth + 1)
      , do
        char '2'
        tlc depth
        tlc depth
      ]
    return ()

```

Try it online!

## Challenge

Given a ternary string (a string that entirely consists of digits 0, 1, and 2), determine if it is a valid Ternary lambda calculus code.

You may take the input as a string or a list of digit values. The input may be empty, and your code should give a false output on it.

For output, you can choose to

* output truthy/falsy using your language's convention (swapping is allowed), or
* use two distinct, fixed values to represent true (affirmative) or false (negative) respectively.

Standard rules apply. The shortest code in bytes wins.

## Test cases

```
-- Truthy
012
00102
0021020212102
000000000010102
00002210022112122112102

-- Falsy
(empty)
2112
2112112
012012012

```

# Answer
> 4 votes
# Jelly, 32 bytes

```
4ŀ1Ẹ?‘}
1;œṡ2Ṫ0ḢḄ<ɗ?
ñ⁹ñ
Ḣ‘$ŀ
ç1

```

Try it online!

A full program taking a list of integers and returning an empty list (falsy) for valid and a non-empty list (truthy) for invalid.

## Explanation

```
   Ẹ?        | If the list is non-empty:
4ŀ   ‘}      | - Call helper link 4 with the depth increased by 1
  1          | - Else return 1

```

```
1;           | Prepend 1
  œṡ2        | Split at 2s
       ḢḄ<ɗ? | If the first part of the list converted from binary is less than the depth:
     Ṫ       | - Return the second part of the list
      0      | - Else return 0

```

```
ñ⁹ñ          | Call helper link 4 twice, preserving depth

```

```
Ḣ‘$ŀ         | Call the helper link indicated by the first list member plus 1, preserving the same depth

```

```
ç1           | Call helper link 4 with 1 as the right argument (depth)

```

# Answer
> 3 votes
# Haskell + hgl, 86 bytes

```
q x=cx[do χ 1;k<-my$kB(<2);gu$x>lF(pl.db)1 k;χ 2,χ 0*>q(x+1),χ 2*>q x*>q x]
x1$q 1

```

*Takes input as a list of integers*

hgl is a library I am making for golfing in Haskell. It has pretty decent parsing capabilities. It however does not yet have a function to convert from binary. Which is a bit of a problem for this answer. The code I came up with to do the task is `lF(pl.db)0`.

This code follows same structure of the parsing code given in the question. That's because both parsing libraries are written by me and use the same theoretic underpinning.

We use `cx` which is analygous to the `choice` in the question selecting between 3 options.

```
q x = cx
  [ do
    χ 1
    k<-my$kB(<2)
    gu$x>lF(pl.db)1 k
    χ 2
  , χ 0*>q(x+1)
  , χ 2*>q x*>q x
  ]

```

The last two don't use `do` notation since it would cost bytes, but they can be written with it.

```
q x = cx
  [ do
    χ 1
    k<-my$kB(<2)
    gu$x>lF(pl.db)1 k
    χ 2
  , do
    χ 0
    q(x+1)
  , do
    χ 2
    q x
    q x
  ]

```

As one might guess now `χ` parses a single integer. We have `gu` which is `guard`, `m` which is `fmap` and `kB` which is the `charBy` function.

The remainder is `lF(pl.db)1` which converts from binary. In regular haskell this is

```
foldl((+).(*2))1

```

# Answer
> 2 votes
# JavaScript, 121 bytes

```
f=d=>(s=s.slice(1),s[0]<1?f(d+1):s[0]>1?f(d)&f(d):(s=s.replace(/[01]+/,x=>parseInt(x,2)>d||''))[0]>1),a=>f(0,s=0+a)&!s[1]

```

Try it online!

The first function called, which is the unnamed arrow function at the end of the code, calls `f` while using an extra argument to initialise `s` to the provided string with `0` prepended.

`f` does the main parsing, removing characters from `s` as it goes, returning false on failure. It begins by unconditionally removing one character, which is the reason for the prepended `0`.

The first two cases are simple: `s[0]<1` (begins with `0`) leads to `f(d+1)`, a recursive call with depth increased, and `s[0]>1` (begins with `2`) leads to `f(d)&f(d)`, parsing two subterms.

The final case covers `s` beginning with `1`, and also `s` being empty (in which case `s[0]` is `undefined`, making both comparisons false). It uses the regexp `/^[01]+/` to pick out the binary number. The replacer function (`x=>parseInt(x,2)>d||''`) checks whether the number is too high, returning `true` if it is and an empty string otherwise. The function then checks whether the remaining string begins with a digit greater than 1; if the number was too high, it begins with `t` and that is false. In the case of `s` being empty, no replacement occurs, and `""[0]>1` is also false.

Finally, back in the unnamed arrow function, `!s[1]` checks whether the whole string was parsed, in which case all but one character will have been removed from it.

# Answer
> 2 votes
# Haskell, 106 bytes

```
(y%q)x|2:z<-q=[z|y<x]|w:z<-q=(2*y+w)%z$x|1>0=[]
x#(z:y)=[(1+x)#y,1%y$x,x#y>>=(x#)]!!z
x#_=[]
([[]]==).(1#)

```

Try it online!

Quite different from my Haskell + hgl answer, and still decently short, all things being considered. This uses a more bootleg parse than that, but if you squint you can see the similarities.

# Answer
> 2 votes
# Curry (PAKCS), 86 bytes

```
d#(0:x)=(d+1)#x
d#(1:i++[2])|all(<2)i&&foldl((+).(2*))1 i<=d=1
d#(2:x++y)=d#x*d#y
(0#)

```

Try it online!

Returns `1` for truth, and nothing otherwise.

---
Tags: code-golf, string, decision-problem, parsing, lambda-calculus
---


title: Build a nine-level word guessing game

text:
# Question
Title: Build a nine-level word guessing game
## The basics:

You'll need to provide a nine-level word guessing game in the fewest characters possible (in your language of choice).

## The metrics:

* Provide a word-list (one word per line, seperated by a newline) (e.g. `/usr/share/dict/words` or similar could do). It's fine to pipe a filename or the wordlist itself into your solution.
* Provide 9 levels with incrementing word length (words with 4 characters `->` 12 characters):

```
Level 1:  a random word from the wordlist containing 4 characters
Level 2:  a random word from the wordlist containing 5 characters
...                                                  ...
Level 8:  a random word from the wordlist containing 11 characters
Level 9:  a random word from the wordlist containing 12 characters

```
* In every level, obfuscate a randomly chosen word from the list (with a specific word length of course) and replace a certain number of characters by the asterisk (`*`). The *number* of characters to replace: `current_word_length / 3` (round down). Randomize *which* characters to replace.
* Let the player 'guess' the word (only one `try` per level), give feedback (`correct` or `wrong`) and give points accordingly. When correct, the player gains `number_of_obfuscated_characters * 10 points`.
* Print the current score at the end of each level.

## The format (& sample I/O):

Make sure you follow the following formatting scheme:

```
Level 1     # level header
=======     #
g*ek        # obfuscated word
geek        # user input
correct     # guess validation
score: 10   # print score
            #
Level 2
=======
l*nux
linux
correct
score: 20

Level 3
=======
ran**m
random
correct
score: 40

...

Level 9
=======
sem***act*ve
semiinactive
wrong
score: 90

```

## Winner:

Shortest solution (by code character count). Have fun golfing!

# Answer
## Perl, 180 chars

```
@w=<>;for$n(4..12){@x=grep/^.{$n}$/,@w;$_=$w=$x[rand@x];vec($_,rand$n,8)=42while($==$n/3)>y/*//;print"Level @{[$n-3]}
=======
$_";say<>eq$w?($@+=$=)&& correct:wrong,"
score: $@0
"}

```

Ruby beating Perl? That will not do! :-)

Like jsvnm's Ruby solution, but unlike Joel Berger's Perl code, this script takes the filename of a word list as a command line parameter. That is, you should run it like this:

```
perl -M5.010 guessword.pl /usr/share/dict/words

```

Here's a de-golfed version:

```
@w = <>;
for $n (4..12) {
    @x = grep /^.{$n}$/, @w;
    $_ = $w = $x[rand@x];
    vec($_, rand $n, 8) = 42 while ($= = $n/3) > y/\*//;
    print "Level @{[ $n-3 ]}\n=======\n$_";
    say <> eq $w ? ($@ += $=) && correct : wrong, "\nscore: $@0\n";
}

```

The statement `vec($_, rand $n, 8) = 42 while ($= = $n/3) > y/*//` contains a few interesting tricks. First, 42 is the ASCII code of an asterisk; it turns out that using `vec` to modify single chars in a string is shorter than doing it with `substr`. Second, the variable `$=` takes only integer values, so using it to store the number of hidden letters saves me an `int`. Finally, `y/*//` is a short way to count the number of asterisks in a string using the transliteration operator.

Edit: Saved 7 chars by using `$@` to store the score divided by 10 and appending a zero to it during output (which, come to think of it, would've been shorter than the previous version even if I'd used a normal variable).

Edit 2: Turns out embedding literal newlines in the output strings saves a char over messing with `$,`.
> 5 votes

# Answer
# Ruby (188)

takes the filename to read words from as argument.

```
q=*$<
s=0
4.upto(12){|n|o=''+w=q.grep(/^#{?.*n}$/).sample
[*0..n-1].sample(c=n/3).map{|i|o[i]=?*}
puts"Level #{n-3}",?=*7,o
puts STDIN.gets==w ?(s+=c;"correct"):"wrong","score: #{s}0",""}

```
> 5 votes

# Answer
# Bash, 350 chars

```
S=0
for L in {4..12}
do
echo -e Level $(($L-3))\\n=======
W=$(grep -E ^.{$L}$ /usr/share/dict/words|shuf|tail -1)
G=$W
while [ `sed 's/[^*]//g'<<<$G|wc -c` -le $(($L/3)) ]
do
P=$(bc<<<$RANDOM*$L/32767)
G=$(sed "s/\(.\{$P\}\)./\1*/"<<<$G)
done
echo $G
read U
if [ x$U == x$W ]
then
echo correct
S=$(($S+$L/3*10))
else
echo wrong
fi
echo score: $S
done

```
> 3 votes

# Answer
# Perl: 266

```
@ARGV='/usr/share/dict/words';@w=<>;$"='';while($l<9){$o=1+int++$l/3;@s=grep{$l+4==length}@w;@g=split//,$t=$s[rand$#s+1];my%r;$r{rand$#g}++while keys%r<$o;$g[$_]='*'for keys%r;print"Level $l\n=======\n@g";print<>eq$t?do{$p+=$o*10;"Correct"}:"Wrong","\nScore: $p\n"}

```

or with a little more white space

```
@ARGV='/usr/share/dict/words';
@w=<>;
$"='';
while($l<9){
  $o=1+int++$l/3;
  @s=grep{$l+4==length}@w;
  @g=split//,$t=$s[rand$#s+1];
  my%r;
  $r{rand$#g}++while keys%r<$o;
  $g[$_]='*'for keys%r;
  print"Level $l\n=======\n@g";
  print<>eq$t?do{$p+=$o*10;"Correct"}:"Wrong","\nScore: $p\n"
}

```

and I think with a little work it could get even better!
> 2 votes

# Answer
## R, 363 chars

```
w=tolower(scan("/usr/share/dict/words",what="c"));l=nchar(w);score=0;for(i in 1:9){mw=sample(w[l==i+3],1);cat("Level",i,"\n=======\n",replace(strsplit(mw,"")[[1]],sample(nchar(mw),floor(nchar(mw)/3)),"*"),"\n");v=scan(what="c",n=1,quiet=T);if(length(v)!=0&&v==mw){score=score+10*floor(nchar(mw)/3);cat("correct\n")} else cat("wrong\n");cat("score:",score,"\n\n")}

```
> 2 votes

# Answer
# Python 335

I know I'm a bit late to the party, but python's not represented, so I figured what the heck:

```
import sys
import random
D=open(sys.argv[1]).read().split()
random.shuffle(D)
z=0
for L in range(1,10):
 M=L+3;N=M/3;w=[c for c in D if len(c)==M][0];U=list(w)
 for i in[random.randint(0,M-1)for i in range(N)]:U[i]='*'
 print"\nLevel %d\n=======\n"%L+''.join(U);k=raw_input()==w;z+=[0,N*10][k];print["wrong","correct"][k]+"\nscore:",z

```

And semi-ungolfed:

```
import sys
import random
words = open(sys.argv[1]).read().split()
random.shuffle(words)
score=0
for L in range(1,10):
   M=L+3
   N=M/3
   w=[c for c in words if len(c)==M][0]
   obfus=list(w)
   for i in [random.randint(0,M-1) for i in range(N)]: obfus[i]='*'
   obfus=''.join(obfus)
   print"\nLevel %d\n=======\n"%L+obfus
   correct=raw_input()==w
   score+=[0,N*10][correct]
   print["wrong","correct"][correct]+"\nscore:",score

```
> 2 votes

# Answer
# K, 198

Assumes a dictionary d in the current working directory.

```
{O:{@[x;(-_c%3)?c:#x;:;"*"]}',/W:{1?x@&y=#:'x}[_0:`d]'4+!9;i:1+S:0;while[#O;-1"Level ",$i;-1"=======";-1@*O;$[(**W)~0:0;[-1"correct";S+:10*+/"*"=*O];-1"wrong"];-1"score: ",$S;-1"";W:1_W;O:1_O;i+:1]}

```

Ungolfed:

```
{
        /W = wordlist; O = obfuscated
        O:{@[x;(-_c%3)?c:#x;:;"*"]}',/W:{1?x@&y=#:'x}[_0:`d]'4+!9;
        i:1+S:0;
        while[#O;
                -1"Level ",$i;
                -1"=======";
                -1@*O;
                $[(**W)~0:0;              /Read user input and compare to the first word
                        [-1"correct";
                        S+:10*+/"*"=*O];  /if correct, increment score
                        -1"wrong"];
                -1"score: ",$S;
                -1"";
                W:1_W;                    /knock one off the top of both word lists
                O:1_O;
                i+:1]
}

```
> 2 votes

# Answer
# Python 3.8 (pre-release), 283 bytes

```
from random import*
def f(w,s=0):
 for i in range(4,13):W=choice([t for t in w if len(t)==i]);C=sample([*range(i)],T:=i//3);print(f'''Level {i-3}
=======
'''+''.join(I in C and'*'or c for I,c in enumerate(W)));print(['wrong','correct'][(Z:=input()==W)]+f'\nscore: {(s:=s+Z*10*T)}\n')

```

Try it online!

I'm on Windows so it's hard to test. But there is a version here on a different interpreter site that uses a list of words compiled from here. (albeit only about 8000 words).
> 1 votes

---
Tags: code-golf, game
---