12
\$\begingroup\$

Introduction

For my 5th KOTH, I present to you a challenge based on the well-known game Battleship with a few twists. You'll only command one ship, whose type you'll be able to select between the 5 "traditionnal" classes, but you'll be able to undertake multiple actions each turn, including moving! This is played as a FFA (Free For All) and your goal will be to be the last ship standing.

Principle

The game is turn-based. At the start of the game, you'll have to choose the class of your ship. Then each turn, players will be able to execute several actions depending on their ship.

The game takes place on a 2D grid (X, Y) whose side is defined this way :
X = 30 + numberOfPlayer
Y = 30 + numberOfPlayer
The starting position of each ship is random.

Play order is randomized each turn, and you won't know your position in the "queue" nor the number of players. The game lasts for 100 turns or until there's only one ship left alive.

Each time you hit an enemy ship or get hit, you'll earn or lose points. The player with the highest score wins. A bounty will be awarded to the winner (value depending on the number of participants).

The controller provides you with input via command arguments, and your program has to output via stdout.

Syntax

First Turn

Your program will be called once without any argument. You'll have to ouput an integer between 1 and 5 (inclusive) to select your ship :

1 : Destroyer [length: 2, moves/turn: 3, shots/turn: 1, range: 9, mines: 4]
Skill : Free ship rotations (no cooldown)

2 : Submarine [length: 3, moves/turn: 2, shots/turn: 1, range: 5, mines: 4]
Skill : Can Plunge/Surface (see outputs). While underwater, you can only use "Movement" actions and can be seen only with a scan. You can't be hit by a shot, but can take damage from mines.

3 : Cruiser [length: 3, moves/turn: 1, shots/turn: 2, range: 9, mines: 2]
Skill : Can Repair (see outputs)

4 : Battleship [length: 4, moves/turn: 1, shots/turn: 3, range: 7, mines: 1]
Skill : Can Shield (see outputs)

5 : Carrier [length: 5, moves/turn: 1, shots/turn: 1, range: 7, mines: 3]
Skill : Shots deal AOE (Area Of Effect) damage to target (1 range splash damage). If the target is hit with the shot, up to 2 cells of this ship will also be damaged.

Turns

Input

Each time your program is called, it will receive arguments in this format:

Round;YourPlayerId;X,Y,Direction;Hull;Moves,Shots,Mines,Cooldown;Hits,Sunken,Damage;Underwater,Shield,Scan;Map

Rounds are 1-indexed.

Example input

1;8;1,12,0;111;1,2,2,0;0,0,0;0,0,0;UUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUXXXX.......UUUUUUUUXXXX.......UUUUUUUUXXXX.......UUUUUUUUXXXX.......UUUUUUUUXXXX.......UUUUUUUUXXXX.O.....UUUUUUUUXXXX.O.....UUUUUUUUXXXX.O.....UUUUUUUUXXXX.......UUUUUUUUXXXX.......UUUUUUUUXXXX.......UUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUU

Here, it's the 1st round, you're player 8.
Your ship is positioned on (X = 1, Y = 12) and your direction is toward the top (0 = Top, 1 = Right, 2 = Bottom, 3 = Left).
Your hull is not damaged (your ship has a length of 3, and each bit is true [1 = OK, 0 = Damaged]). You can move 1 time, shoot 2 times, have 2 mines left and your "skill" is available (cooldown = 0).
You didn't hit anything, nor have sunken any ship and you didn't get hit either.
You're not underwater, your shields (if any) are not activated and your scan isn't either.
More on the map later...

Output

You have to output a String describing what actions you'll execute this turn. The order of the characters in your output String will define the orders of the actions. You can output the same actions multiple times if it doesn't exceed the limits of your ship. If one or multiple actions is invalid, each one will separately be considered as W. Here's the list of available actions :

M : Move 1 cell in the direction you're facing (consume 1 move)
B : Back 1 cell from the direction you're facing (consume 1 move)
C : Rotate your ship clockwise (consume 1 move / free for Destroyers)
K : Rotate your ship counterclockwise (consume 1 move / free for Destroyers)
A : Ram your ship in the direction you're facing (works only if another ship is occupying the cell in the direction you're facing / doesn't move your ship / consume all moves)
F : Fire 1 shot to a cell in range (consume 1 shot). Must be followed by the targetted cell in this format ([+-]X[+-])Y / example : F+2-3)
N : Place 1 mine to a cell adjacent to your ship (consume all shots and 1 mine). Must be followed by the targetted cell in this format ([+-]X[+-])Y / example : N+0+1)
S : Activate your scan for the next turn (consume all shots)
R : Repair the damaged hull the closest to the "head" of your ship (consume all shots, cooldown = 3 turns / Cruiser only)
P : Plunge/Surface (consume all shots, cooldown = 3 turns, max duration = 5 turns / Submarine only)
D : Activate your shield preventing the next damage during your next turn (consume all shots, cooldown = 3 / Battleship only)
W : Wait (does nothing)

Clarification: "Consume all moves/shots" means you can only use this action if you have not used any of your moves/shots before during this turn.

Example output

MF+9-8CM : Moves 1 cell, then fires on the cell whose relative position to the "head" of your ship is (targetX = X + 9, targetY = Y - 8), turns clockwise and finally moves 1 cell again.

Gameplay

The Grid

Here's an example grid (33 x 13) where 3 players are placed :

███████████████████████████████████
█                                 █
█       00                        █
█   2                             █
█   2                             █
█   2                             █
█                                 █
█       11111                     █
█        M                        █
█                                 █
█                                 █
█                                 █
█                                 █
█                                 █
███████████████████████████████████

As we can see, there's also a Mine M right next to the player 1.

Let's take player 2 to understand positioning and direction :

Player 2's position is X = 3, Y = 4, Direction = 3. Since its direction is "Bottom", the rest of his "ship cells" are positioned "over" its "head" (X = 3, Y = 3) & (X = 3, Y = 2)

Player's map

The last argument each players receive is their "own" map. By default a ship detects everything in a range of 5 cells, but it can activate a Scan to increase this range to 9.

The argument is always 361 (19 x 19) characters long. It represents the square centered around the "head" of your ship, where each character corresponds to an element defined this way :

. : Empty cell
O : Your ship
M : Mines
X : Wall (cells out of the map)
U : Unknown (will be revealed by a scan)
A : Enemy ship undamaged cell
B : Enemy ship damaged cell
C : Enemy ship underwater undamaged cell (only seen with a scan)
D : Enemy ship underwater damaged cell (only seen with a scan)
W : Wreckage (dead ship)

The string is composed of 19 characters of the first line, followed by 19 charcters of the second lines ... until the 19th line.

Lets take a look at what player 2 receives with and without a scan (line breaks for better understanding, but not send to players) :

XXXXXXXXXXXXXXXXXXX
XXXXXXXXXXXXXXXXXXX
XXXXXXXXXXXXXXXXXXX
XXXXXXXXXXXXXXXXXXX
XXXXXXXXXXXXXXXXXXX
XXXXXX.............
XXXXXX.......AA....
XXXXXX...O.........
XXXXXX...O.........
XXXXXX...O.........
XXXXXX.............
XXXXXX.......AAAAA.
XXXXXX........M....
XXXXXX.............
XXXXXX.............
XXXXXX.............
XXXXXX.............
XXXXXX.............
XXXXXXXXXXXXXXXXXXX

UUUUUUUUUUUUUUUUUUU
UUUUUUUUUUUUUUUUUUU
UUUUUUUUUUUUUUUUUUU
UUUUUUUUUUUUUUUUUUU
UUUUXXXXXXXXXXXUUUU
UUUUXX.........UUUU
UUUUXX.......AAUUUU
UUUUXX...O.....UUUU
UUUUXX...O.....UUUU
UUUUXX...O.....UUUU
UUUUXX.........UUUU
UUUUXX.......AAUUUU
UUUUXX........MUUUU
UUUUXX.........UUUU
UUUUXX.........UUUU
UUUUUUUUUUUUUUUUUUU
UUUUUUUUUUUUUUUUUUU
UUUUUUUUUUUUUUUUUUU

Mines

The mines are triggered when a ship moves to a cell occupied by a mine or when a shot is fired on the mine. Mines can't be triggered with the action "Ram".

Mines deal AOE damage (1 range splash damage) to everyone, even to the person who placed the mine. Mines can trigger "chain" explosions if another mine is in the radius of the explosion.

Rotations

The rotations are central symmetries centered on the ship's "head". Rotations will only trigger a mine if it is placed on the "destination position" (you won't trigger mines in an arc.

Area of effect

1 range splash damage (for mines and Carrier's shots) is defined by a 3x3 (9 cells) square centered on the initial shot/explosion (x, y). It hits those coordinates : [x - 1; y - 1],[x - 1; y],[x - 1; y + 1],[x; y - 1],[x; y],[x; y + 1],[x + 1; y - 1],[x + 1; y],[x + 1; y + 1]

Scoring

The scoring is defined by this formula :
Score = Hits + (Sunken x 5) - Damage taken - (Alive ? 0 : 10)

where :
hits : number of hits on enemy ship, either by Ram, Shot or Mine explosion (1 hit by enemy ship cell damaged, including chain explosions)
sunken : number of "last hit" on an enemy ship which caused it to sink
damage : number of hits received (not decreased by Repair, but prevented by Shield)
alive : checks wether your ship is alive at the end (at least 1 hull cell undamaged)

Controller

You can find the controller on GitHub. It also contains two samplebots, written in Java. To make it run, check out the project and open it in your Java IDE. The entry point in the main method of the class Game. Java 8 required.

To add bots, first you need either the compiled version for Java (.class files) or the sources for interpreted languages. Place them in the root folder of the project. Then, create a new Java class in the players package (you can take example on the already existing bots). This class must implement Player to override the method String getCmd(). The String returned is the shell command to run your bots. You can for example make a Ruby bot work with this command : return "C:\Ruby\bin\ruby.exe MyBot.rb";. Finally, add the bot in the players array at the top of the Game class.

Rules

  • Bots should not be written to beat or support specific other bots.
  • Writing to files is allowed. Please write to"yoursubmissionname.txt", the folder will be emptied before a game starts. Other external resources are disallowed.
  • Your submission has 1 second to respond.
  • Provide commands to compile and run your submissions.
  • You can write multiple submissions

Supported Languages

I'll try and support every language, but it needs to be available online for free. Please provide instructions for installation if you're not using a "mainstream" language.

As of right now, I can run : Java 6-7-8, PHP, Ruby, Perl, Python 2-3, Lua, R, node.js, Haskell, Kotlin, C++ 11.

\$\endgroup\$
8
  • \$\begingroup\$ Interesting KotH, I just have a few questions : can we write multiple submissions (one for each type of ship for instance)? When you're talking about AoE, it's a square around the position right (it hits [x+1;y+1])? \$\endgroup\$
    – Katenkyo
    May 16, 2017 at 9:09
  • \$\begingroup\$ @Katenkyo Yes, you can write multiple submissions. Yes, it hits 9 cells : [x - 1; y - 1],[x - 1; y],[x - 1; y + 1],[x; y - 1],[x; y],[x; y + 1],[x + 1; y - 1],[x + 1; y],[x + 1; y + 1] \$\endgroup\$
    – Thrax
    May 16, 2017 at 9:51
  • \$\begingroup\$ so, does the submarine automatically surface? on which turn? \$\endgroup\$ May 16, 2017 at 23:03
  • \$\begingroup\$ also are turns taken simultaneously? \$\endgroup\$ May 16, 2017 at 23:07
  • \$\begingroup\$ also what is useful about the ram ability? (why not just shoot?) \$\endgroup\$ May 17, 2017 at 2:20

2 Answers 2

3
\$\begingroup\$

RandomBot

This is an example bot. It chooses a ship, an action and a target cell (if needed) randomly.

import java.util.Random;

public class RandomBot {

    int round;
    int playerID;

    public static void main(String[] args) {

        if (args.length == 0) {
            Random random = new Random();
            int ship = random.nextInt(5);
            String[] ships = { "1", "2", "3", "4", "5" };
            System.out.println(ships[ship]);
        } else {
            new RandomBot().play(args[0].split(";"));
        }
    }

    private void play(String[] args) {

        round = Integer.parseInt(args[0]);
        playerID = Integer.parseInt(args[1]);

        String[] actions = { "M", "B", "C", "K", "F", "S", "N", "A" };
        Random random = new Random();
        int action = random.nextInt(8);

        int rangeX = random.nextInt(5);
        int rangeY = random.nextInt(5);
        int mineX = random.nextInt(1);
        int mineY = random.nextInt(1);

        String signX = random.nextInt(1) == 1 ? "+" : "-";
        String signY = random.nextInt(1) == 1 ? "+" : "-";

        System.out.println(actions[action] + (action == 4 ? signX + rangeX + signY + rangeY : "") + (action == 6 ? signX + mineX + signY + mineY : ""));
    }

}

PassiveBot

This is an example bot. It does nothing.

public class PassiveBot {

    int round;
    int playerID;

    public static void main(String[] args) {

        if (args.length == 0) {
            System.out.println("5");
        } else {
            new PassiveBot().play(args[0].split(";"));
        }
    }

    private void play(String[] args) {

        round = Integer.parseInt(args[0]);
        playerID = Integer.parseInt(args[1]);

        System.out.println("W");
    }

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

PeaceMaker, Python 2 (Battleship)

PeaceMaker shoots 3 times on the nearest enemies (spiral distance) and moves back and forth in a line while staying at least 2 cells away from mines.

from os import sys

def reversedSpiralOrder(length):

    #Initialize our four indexes
    top = 0
    down = length - 1
    left = 0
    right = length - 1
    result = ""

    while 1:

        # Print top row
        for j in range(left, right + 1):
            result += str(top * length + j) + ";"
        top += 1
        if top > down or left > right:
            break

        # Print the rightmost column
        for i in range(top, down + 1):
            result += str(i * length + right) + ";"
        right -= 1
        if top > down or left > right:
            break

        # Print the bottom row
        for j in range(right, left + 1, -1):
            result += str(down * length + j) + ";"
        down -= 1
        if top > down or left > right:
            break

        # Print the leftmost column
        for i in range(down, top + 1, -1):
            result += str(i * length + left) + ";"
        left += 1
        if top > down or left > right:
            break

    result = result.split(";")
    del result[-1]
    return result[::-1]

def canMove(x, y, direction, hull, map):

    # M = 1, B = 2
    moves = 0

    if direction == 0:
        y1 = -1
        y2 = -2
        hx1 = hx2 = x1 = x2 = 0
        hy1 = -y1 + hull
        hy2 = -y2 + hull
    elif direction == 1:
        x1 = 1
        x2 = 2
        hy1 = hy2 = y1 = y2 = 0
        hx1 = -x1 - hull
        hx2 = -x2 - hull
    elif direction == 2:
        y1 = 1
        y2 = 2
        hx1 = hx2 = x1 = x2 = 0
        hy1 = -y1 - hull
        hy2 = -y2 - hull
    elif direction == 3:
        x1 = -1
        x2 = -2
        hy1 = hy2 = y1 = y2 = 0
        hx1 = -x1 + hull
        hx2 = -x2 + hull

    if map[y + y1][x + x1] == "." and map[y + y2][x + x2] != "M":
        moves += 1

    if map[y + hy1][x + hx1] == "." and map[y + hy2][x + hx2] != "M":
        moves += 2

    return moves

if len(sys.argv) <= 1:
    f = open("PeaceMaker.txt","w")
    f.write("")
    print "4"
else:
    arguments = sys.argv[1].split(";")
    sight = 19

    round = int(arguments[0])
    playerID = int(arguments[1])
    x = int(arguments[2].split(",")[0])
    y = int(arguments[2].split(",")[1])
    direction = int(arguments[2].split(",")[2])
    hull = arguments[3]
    moves = int(arguments[4].split(",")[0])
    shots = int(arguments[4].split(",")[1])
    mines = int(arguments[4].split(",")[2])
    cooldown = int(arguments[4].split(",")[3])
    hits = int(arguments[5].split(",")[0])
    kills = int(arguments[5].split(",")[0])
    taken = int(arguments[5].split(",")[0])
    underwater = int(arguments[6].split(",")[0])
    shield = int(arguments[6].split(",")[1])
    scan = int(arguments[6].split(",")[2])
    map = [[list(arguments[7])[j * sight + i] for i in xrange(sight)] for j in xrange(sight)]

    initialShots = shots


    priorities = reversedSpiralOrder(sight)

    actions = ""
    sighted = 0
    for priority in priorities:
        pX = int(priority) % sight
        pY = int(priority) / sight

        if map[pY][pX] == "A":
            sighted += 1
            if shots > 0:
                shots -= 1
                actions += "F" + ("+" if pX - 9 >= 0 else "") + str(pX - 9)  + ("+" if pY - 9 >= 0 else "") + str(pY - 9)

    if shots == initialShots and sighted > 0:
        actions += "D"
    elif shots == initialShots and sighted <= 0:
        actions += "S"
    else:
        actions += ""

    f = open("PeaceMaker.txt","r")
    fC = f.read(1)
    lastDirection = int("1" if fC == "" else fC)

    y = 9
    x = 9

    if lastDirection == 1:
        if canMove(x, y, direction, len(hull), map) == 1 or canMove(x, y, direction, len(hull), map) == 3:
            actions += "M"
        elif canMove(x, y, direction, len(hull), map) == 2:
            actions += "B"
            lastDirection = 0
    elif lastDirection == 0:
        if canMove(x, y, direction, len(hull), map) == 2 or canMove(x, y, direction, len(hull), map) == 3:
            actions += "B"
        elif canMove(x, y, direction, len(hull), map) == 1:
            actions += "M"
            lastDirection = 1

    f = open("PeaceMaker.txt","w")
    f.write(str(lastDirection))

    print actions
\$\endgroup\$
1
  • 1
    \$\begingroup\$ 'PeaceMaker shoots'. You lost me there. \$\endgroup\$
    – Okx
    May 17, 2017 at 9:56

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

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