# What Doesn't Kill Me…

## Overview

This is a bot battle to see who can survive the longest. These bots increase their power by being attacked, though, so you need to think carefully before you shoot.

Each turn, you can choose a bot to attack, or defend. Attacking will lower its life and increase its power. Last bot standing wins.

### Bots

Each bot starts with 1000 life and 10 power.

When attacked:

• your power raises by 1.

So, if on the first turn, you are attacked by two bots, you will have 980 life and 12 power.

If you choose to defend:

• your power will be lowered by 1
• all attacks against you this turn will be reduced by half
• if you are attacked, you will gain 2 power for each attacker instead of 1

So, if you defend on the first turn and are attacked by two bots, you will have 990 life and 13 power. If you defend and are not attacked, you will have 1000 life, but 9 power.

If at the end of a turn your power is below one, it will be set to one. If your life is below 1, you die.

### Input/Output

Bots are called once per turn. There is a time limit of one second for each turn.

Initial

The first time your bot is called, it will be given no arguments. Respond with ok. This is done only to make sure your bot responds. If it doesn't, it will not be added to the player list.

Each turn

Each turn, your bot is given information about all bots in the game as command line arguments. An example of these arguments is:

1 0,1000,10,1 1,995,11,D


The first argument is your bot's unique id. Then, a space separated list of bots appears. Each bot is formatted as:

id,life,power,lastAction


lastAction may be an integer representing which bot they attacked, D if they defended, and X if this is the first turn. The others are all integers.

So in the example above, you are bot 1 and defended on your last turn. Bot 0 attacked you and is still at starting health/power.

Output for each turn is very simple. Simply output the bot you want to attack as an integer (eg 0 or 3), or D to defend. Don't attack dead or non-existent bots, as that counts as an invalid command. Any invalid command will result in you losing 1 power.

### Tournament Structure

Each game consists of all bots starting at 1000 health and 10 power. Actions by all bots are taken simultaneously. The maximum number of turns for a game is 1000.

If at the end of the turn there is one bot remaining alive (life > 0), it scores one point and another game is started. If the turn limit is reached and there are multiple bots alive, nobody gets a point. If all remaining bots die on the same turn, nobody gets a point.

A tournament consists of 15 games. Whoever has the most points at the end wins! Ties are broken by the sum of life remaining in each won game.

### State

Bots may only read from or write to a single file named after itself, in a direct subfolder named state ("Hero" can write to state/hero.whatever). This file should not exceed 10242 bytes in size. Take care to observe the time limit. Your program must terminate within one second to count, not just give a response.

These files will be wiped before each tournament, but will persist game to game. All bot identifiers (id) will also remain the same between games.

### Controller

Below is the tournament controller (Stronger.java). By default, it only outputs the final results (sorted list of players, winner on top), which may take quite a while. It's not frozen, just silent. If you'd like a more detailed turn-by turn output, add the -log argument when running.

To add bots, you have two options:

• add the command as an argument (java Stronger -log "python bot.py")

• add the command to defaultPlayers[] in the source ("python bot.py")

The bots Hero, Bully, and Coward can be found in this answer, and will be used for scoring purposes.

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Scanner;

public class Stronger {

static final String[] defaultPlayers = {
"java Hero",
"java Bully",
"java Coward"
};
final int timeout = 1000;
final int startLife = 1000;
final int startPower = 10;
final int numRounds = 15;

boolean log = false;
List<Player> players;

public static void main(String[] args){
new Stronger().run(args);
}

void run(String[] args){
init(args);
for(int i=0;i<numRounds;i++){
Collections.shuffle(players);
runGame();
}
Collections.sort(players);
for(Player player : players)
System.out.println(player.toString());
}

void runGame(){
log("Player Count: " + players.size());
for(Player player : players)
player.reset();
int turn = 0;
while(turn++ < startLife){
if(aliveCount() < 2)
break;
log("Turn " + turn);
List<Player> clones = new ArrayList<Player>();
for(Player player : players)
for(Player player : players){
if(player.life < 1 || player.timedOut)
continue;
String[] args = new String[players.size()+1];
args[0] = "" + player.id;
for(int i=1;i<args.length;i++)
args[i] = players.get(i-1).toArgument();
Player clone = player.findCopyOrMe(clones);
clone.timedOut = true;
clone.life = 0;
}
}

for(Player player : players){
if(player.life < 1 || player.timedOut)
continue;
Player clone = player.findCopyOrMe(clones);
if(clone.lastAction.equals("D")){
clone.power--;
}else{
try{
int target = Integer.parseInt(clone.lastAction);
for(Player t : players)
if(t.id == target && t.life < 1)
throw new Exception();
for(Player tclone : clones){
if(tclone.id == target){
int atk = player.power;
if(tclone.lastAction.equals("D")){
atk -= player.power / 2;
tclone.power++;
}
tclone.life -= atk;
tclone.power++;
}
}
} catch (Exception e){
log(player.cmd + " returned an invalid command: (" + clone.lastAction + ")");
clone.power--;
}
}
}
players = clones;
for(Player player : players){
if(player.power < 1)
player.power = 1;
log(player.life + "\t\t" + player.power + "\t\t(" + player.id + ")\t" + player.cmd);
}
log("\n");
}

if(aliveCount() == 1)
for(Player player : players)
if(player.life > 0){
player.scoreRounds++;
player.scoreLife += player.life;
}
}

void log(String msg){if(log)System.out.println(msg);}

try{
List<String> cmd = new ArrayList<String>();
String[] tokens = player.cmd.split(" ");
for(String token : tokens)
for(String arg : args)
ProcessBuilder builder = new ProcessBuilder(cmd);
builder.redirectErrorStream();
long start = System.currentTimeMillis();
Process process = builder.start();
Scanner scanner = new Scanner(process.getInputStream());
process.waitFor();
scanner.close();
process.destroy();
if(System.currentTimeMillis() - start > timeout)
return "T";
}catch(Exception e){
e.printStackTrace();
return "Exception: " + e.getMessage();
}
}

void init(String[] args){
players = new ArrayList<Player>();
for(String arg : args){
if(arg.toLowerCase().startsWith("-log")){
log = true;
}else{
Player player = createPlayer(arg);
if(player != null)
}
}
for(String cmd : defaultPlayers){
Player player = createPlayer(cmd);
if(player != null)
}
}

Player createPlayer(String cmd){
Player player = new Player(cmd);
log(player.cmd + " " + reply);
return player;
return null;
}

int aliveCount(){
int alive = 0;;
for(Player player : players)
if(player.life > 0)
alive++;
return alive;
}

static int nextId = 0;
class Player implements Comparable<Player>{
int id, life, power, scoreRounds, scoreLife;
boolean timedOut;
String cmd, lastAction;

Player(String cmd){
this.cmd = cmd;
id = nextId++;
scoreRounds = 0;
scoreLife = 0;
reset();
}

public Player copy(){
Player copy = new Player(cmd);
copy.id = id;
copy.life = life;
copy.power = power;
copy.scoreRounds = scoreRounds;
copy.scoreLife = scoreLife;
copy.lastAction = lastAction;
return copy;
}

void reset(){
life = startLife;
power = startPower;
lastAction = "X";
timedOut = false;
}

Player findCopyOrMe(List<Player> copies){
for(Player copy : copies)
if(copy.id == id)
return copy;
return this;
}

public int compareTo(Player other){
if(scoreRounds == other.scoreRounds)
return other.scoreLife - scoreLife;
return other.scoreRounds - scoreRounds;
}

public String toArgument(){
return id + "," + life + "," + power + "," + lastAction;
}

public String toString(){
String out = "" + scoreRounds + "\t" + scoreLife;
while(out.length() < 20)
out += " ";
return out + "(" + id + ")\t" + cmd;
}
}
}


### Rules

• You may enter up to two bots. If you want to remove one from play to enter a third, please delete its post.

• You may not target or otherwise single out a bot by meta-analysis. Use only the information your bot is given. This includes your own bots, so you may not enter two bots that collude.

• Do not attempt to interfere with the running of the controller or other bots in any way.

• Your bot may not instantiate or otherwise run the controller or other bots.

# Results

(of bots submitted as of of 2015-05-22 00:00:00Z)

This round of play went a bit better, with only two games stalling out at 1000 turns. Kudos to Ralph Marshall's Santayana, which took first place, being the only bot that scored three wins. That wasn't enough, so he also took third place with Tactician. Stormcrow took second with Phantom Menace, a fine first post here. All in all we had a very nice showing by new members, with the top six places going to people with less than five posts. Congratulations, and welcome to the site!

Bots that scored zero wins are not listed to save space. All bots posted before the timestamp above were run, so if you don't see yours, it didn't win anything.

Wins    Life(tiebreaker)  Name

3       561               perl Santayana.pl
2       850               java PhantomMenace
2       692               perl Tactician.pl
2       524               java Wiisniper
1       227               java Tank
1       184               java Velociraptor
1       7                 java Coward
1       3                 java IKnowYou


Sorta sketchy parallelized controller (by Others):

import java.lang.ProcessBuilder.Redirect;
import java.nio.file.FileSystems;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Scanner;
import java.util.concurrent.atomic.AtomicInteger;

public class Stronger {

static final String[] defaultPlayers = {
"java Hero",
"java Bully",
"java Coward",
"java Psycho",
"./monte.out",
"java Analyst",
"java Guardian",
"java Revenger",
"python precog.py",
//"python snappingTurtle.py",
"python beserker.py",
"./suprise.out",
//"python boxer.py",
"python defense.py",
"java Tank",
"java IKnowYou",
//"java BroBot",
"java Equaliser",
"java Velociraptor",
//"java AboveAverage",
"java PhantomMenace",
"java Wiisniper",
//"python semiRandom.py",
"/usr/bin/perl tactition.pl",
"/usr/bin/perl santayana.pl",
//"java GlitchUser"
"/usr/local/bin/Rscript opportunity.R",
"/usr/local/bin/scala Bandwagoner",
};
final int timeout = 5000;
final int startLife = 1000;
final int startPower = 10;
final int numRounds = 20;

boolean log = true;
List<Player> players;

public static void main(String[] args){
new Stronger().run(args);
}

void run(String[] args){
init(args);
for(int i=1;i<=numRounds;i++){
if(log) System.out.println("Begining round "+ i);
Collections.shuffle(players);
runGame();
}
Collections.sort(players);
for(Player player : players)
System.out.println(player.toString());
}

void runGame(){
log("Player Count: " + players.size());
for(Player player : players)
player.reset();
int turn = 0;
while(turn++ < startLife){
if(aliveCount() < 2)
break;
log("Turn " + turn);
List<Player> clones = new ArrayList<Player>();
for(Player player : players)
AtomicInteger count=new AtomicInteger(players.size());
for(Player player : players){
if(player.life >= 1 && !player.timedOut){
String[] args = new String[players.size()+1];
args[0] = "" + player.id;
for(int i=1;i<args.length;i++)
args[i] = players.get(i-1).toArgument();
Player clone = player.findCopyOrMe(clones);
clone.timedOut = true;
clone.life = 0;
}
}
synchronized(count){
count.decrementAndGet();
count.notify();
}
}).start();
}
synchronized(count){
while(count.get() > 0){
//System.out.println(count);
try{
count.wait();
}catch(InterruptedException e){
}
}
}

for(Player player : players){
if(player.life < 1 || player.timedOut)
continue;
Player clone = player.findCopyOrMe(clones);
if(clone.lastAction.equals("D")){
clone.power--;
}else{
try{
int target = Integer.parseInt(clone.lastAction);
for(Player t : players)
if(t.id == target && t.life < 1)
throw new Exception();
for(Player tclone : clones){
if(tclone.id == target){
int atk = player.power;
if(tclone.lastAction.equals("D")){
atk -= player.power / 2;
tclone.power++;
}
tclone.life -= atk;
tclone.power++;
}
}
} catch (Exception e){
log(player.cmd + " returned an invalid command: (" + clone.lastAction + ")");
clone.power--;
}
}
}
players = clones;
for(Player player : players){
if(player.power < 1)
player.power = 1;
log(player.life + "\t\t" + player.power + "\t\t" + player.lastAction + "\t\t(" + player.id + ")\t" + player.cmd);
}
log("\n");
}

if(aliveCount() == 1)
for(Player player : players)
if(player.life > 0){
player.scoreRounds++;
player.scoreLife += player.life;
}
}

void log(String msg){if(log)System.out.println(msg);}

try{
List<String> cmd = new ArrayList<String>();
String[] tokens = player.cmd.split(" ");
for(String token : tokens)
for(String arg : args)
ProcessBuilder builder = new ProcessBuilder(cmd);
builder.directory(FileSystems.getDefault().getPath(".", "bin").toFile());
//builder.redirectError(Redirect.PIPE);
long start = System.currentTimeMillis();
Process process = builder.start();
Scanner scanner = new Scanner(process.getInputStream());
process.waitFor();
scanner.close();
process.destroy();
if(System.currentTimeMillis() - start > timeout)
return "T";
}catch(Exception e){
//e.printStackTrace();
return "Exception: " + e.getMessage();
}
}

void init(String[] args){
players = new ArrayList<Player>();
for(String arg : args){
if(arg.toLowerCase().startsWith("-log")){
log = true;
}else{
Player player = createPlayer(arg);
if(player != null)
}
}
for(String cmd : defaultPlayers){
Player player = createPlayer(cmd);
if(player != null)
}
}

Player createPlayer(String cmd){
Player player = new Player(cmd);
log(player.cmd + " " + reply);
return player;
return null;
}

int aliveCount(){
int alive = 0;;
for(Player player : players)
if(player.life > 0)
alive++;
return alive;
}

static int nextId = 0;
class Player implements Comparable<Player>{
int id, life, power, scoreRounds, scoreLife;
boolean timedOut;
String cmd, lastAction;

Player(String cmd){
this.cmd = cmd;
id = nextId++;
scoreRounds = 0;
scoreLife = 0;
reset();
}

public Player copy(){
Player copy = new Player(cmd);
copy.id = id;
copy.life = life;
copy.power = power;
copy.scoreRounds = scoreRounds;
copy.scoreLife = scoreLife;
copy.lastAction = lastAction;
return copy;
}

void reset(){
life = startLife;
power = startPower;
lastAction = "X";
timedOut = false;
}

Player findCopyOrMe(List<Player> copies){
for(Player copy : copies)
if(copy.id == id)
return copy;
return this;
}

public int compareTo(Player other){
if(scoreRounds == other.scoreRounds)
return other.scoreLife - scoreLife;
return other.scoreRounds - scoreRounds;
}

public String toArgument(){
return id + "," + life + "," + power + "," + lastAction;
}

public String toString(){
String out = "" + scoreRounds + "\t" + scoreLife;
while(out.length() < 20)
out += " ";
return out + "(" + id + ")\t" + cmd;
}
}
}

• @Tim The bot list is space separated. Each bot's stats are comma separated. Commented May 14, 2015 at 18:32
• @Mark The current bots are about half defensively oriented and half attack. None of the defensive bots have a single win. I agree that it encourages some defense, but the benefit of aggression is that pure defense cannot win any points at all. You only win points if you are the sole survivor. Commented May 15, 2015 at 4:28
• If anyone's curious how the bots stack up now that there's more submissions, here's the result of my "unofficial" test run: 3-809-Hero; 2-593-Velociraptor; 1-471-SurpriseBot; 1-433-Coward; 1-371-Santayana; 1-364-Wiisniper; 1-262-Analyst; 1-230-Bully; 1-132-Equaliser; 1-71-IKnowYou; 0-0 -- precog, Berserker, BroBot, SemiRandom, MonteBot, Tactician, SnappingTurtle, Psycho, Revenger, Opportunity, PhantomMenace, Tank, Boxer, Guardian, AboveAverage, DefensiveBot. Lots of invalid commands from Opportunity, AboveAvg, BroBot, Boxer, and SemiRandom. Commented May 17, 2015 at 18:04
• I have a parallelized version of the controller that runs games faster than the original controller does, if anyone is interested I can post it... Commented May 17, 2015 at 23:58
• @Manu As long as each bot isn't being executed simultaneously, it shouldn't matter. I assume the parallel version still has all bots finish one turn before moving to the next. Like I said, I'm going to verify before using it if posted. Commented May 18, 2015 at 14:35

# Java - CrystalBall

Well, I'm a bit (10 days) late to the party... but hey, I'm slow.

Hello, and welcome to the second page of answers! This is CrystalBall. It tries to find the best move by running random simulations and comparing the resulting move to simple criteria. It also relies on a bit of luck as well: after testing on 4 values, CrystalBall outputs different values each time it's run. Fingers crossed it'll output the right value! (though I don't see MonteBot in the top bots so I don't have much hope for CrystalBall)

Be warned that enabling the DEBUG variable causes long outputs (300+ lines for 50 sims)

    import java.util.*;
import java.time.*;

public class CrystalBall {

/**
* Elapsed time measurement.
* Unused at the moment
*/
//static final long startTime = System.currentTimeMillis();
//static long stopTime = startTime + 1000; // about 1 second

/**
* Maximum number of players.
*/
public static final int MAX_PLAYERS = 24;

/**
* If we should print debug text.
*/
static boolean DEBUG = false;

/**
* The amount of simulations, and if they are finished or not.
*/

//static boolean simsFinished = false;

/**
* An array of the possible moves, the best move and moves to think about
*/
static ArrayList<String> possibleMoves = new ArrayList<String>();
static String bestMove = "Calculating";
static ArrayList<String> thoughtList = new ArrayList<String>();

/**
* Our random number generator, with the seed of the current time in milliseconds
* @param  System.currentTimeMillis() The current system time in milliseconds
* @return                            A random integer
*/
static Random random = new Random(System.currentTimeMillis());

/**
* Prints a string to the console.
* @param message A message to be printed.
*/
public static void log(String message){
if (DEBUG){
System.out.println("[CrystalBall]: " + message);
}
}

public static void main(String[] args) {

//Respond with OK to say we're here if there are no args
if(args.length <= 1 || args.length == 0 || args.length == 1){
System.out.println("ok");
System.exit(0);
}

int SIMS = (args.length - 1) * 12;
log("Number of sims: " + SIMS);

/**
*/
int myHealth = 0, myPower = 0, myLastAction = 0;

/**
* The ID of our bot
*/
String me = args[0];
log("My ID = " + me);

// Might be useful, uncomment if needed
//ArrayList<String> argsList = new ArrayList<String>(Arrays.asList(args));

// We'll loop through all the args until we find me!
for (int a = 1; a < args.length; a++){
// We'll split the array up into data about the bot
String[] split = args[a].split(",");
// Is it me?
if (split[0].equals(me)){
// Great! We've found me!
log("Found me! My ID is " + me + " and this bots ID is " + split[0]);

// Now, let's set our variables
myHealth = Integer.parseInt(split[1]);
myPower = Integer.parseInt(split[2]);
myLastAction = Integer.parseInt(split[3]);
}
}

// Do we have more than MAX_PLAYERS?
if (args.length - 1 >= MAX_PLAYERS){
log("WARNING! Too many players!");
log("Maximum players: " + MAX_PLAYERS);
}

// Let's add all the bot IDs to our possibleMoves array so we can simulate what he
// will do
for (int x = 1; x < args.length; x++){

// Current data is something like: 1,99,12,D

// Now the data is: Array[1,99,12,D]
String[] bots = args[x].split(",");

// If he's alive, add him
if (Integer.parseInt(bots[1]) > 1){
}

}

// Let's simulate!
// Each simulation, we chose random moves for each bot
// TODO Maybe make it based on moves in the past?
// TODO We need to make sure that one second hasn't elapsed each time we loop
for (int s = 0; s < SIMS; s++){

// Have we finished?
if (s == SIMS || s == SIMS - 1){
log("==> SIMS FINISHED, OUTPUTTING DATA <==");
if (bestMove.equals("Calculating")){
log("No best moves! Choosing randomly from thoughtList");
System.out.println(thoughtList.get(random.nextInt(thoughtList.size())));
break;
} else {
System.out.println(bestMove);
break;

}
}

// Loop through all the bots and make a random move for each
for (int i = 1; i < args.length; i++){

String[] data = args[i].split(",");

// TODO this may not work if dataList is created before data

// ArrayList<String> dataList = new ArrayList<String>(Arrays.asList(data));

// What's my move, and what's his?
// Note that at the moment we do not need his move
String myMove = possibleMoves.get(random.nextInt(possibleMoves.size()));
String botMove = possibleMoves.get(random.nextInt(possibleMoves.size()));
// log("FINISHED SIMULATING MOVES. Took " + (System.currentTimeMillis() - startTime) + "ms");

int botHealth = Integer.parseInt(data[1]);
int botPower = Integer.parseInt(data[2]);

// Let's check if it was a good move
// TODO Perhaps we should have a list of good moves and randomly pick from that, because at the moment it will pick the last best move, not the best best move.
// TODO We should actually simulate the move and compare the outcome to the current best move.
// TODO We should never attack a defending bot, they will be hard to attack.

// TODO Also, if the timer is up or we've finished our sims then we stop simulating and return the best answer so far

// Did we attack him? or Is my move his bot ID?
if (myMove.equals(data[0])){

// If we can do a lot of damage, we'll use this move.
if (botHealth - myPower <= 100 && !me.equals(data[0])){
log("Yay! We attacked a bot and we can do plenty of damage to it!");
log("Best move: " + myMove);
bestMove = myMove;

} else {

// Add this move to our list of moves to think about
log("Adding " + myMove + " to thought list.");

}

// Did I attack myself? If I'm low on power, let's stock up!
} else if (myMove.equals(me)){
if (myPower <= 10 && myHealth >= 95){
log("Low on power. Attacking myself to get extra power.");
log(myMove);
bestMove = myMove;
} else {
log("No need to stock up on power.");
}

// Did we defend?
} else if (myMove.equals("D")){
// Did the bot attack us last turn?
if (data[3].equals(me)){
// TODO It'd be nice if we saved a file so we can tell if he keeps attacking us
log("This bot attacked us last turn! We will defend: he may be dangerous.");
log(myMove);
bestMove = myMove;
} else {
// Add this to our thought list
}

// Is he predicted to attack us?
// FIXME This clashes with the defend statement above
// FIXME Find a better way to stop the simulation!
} else if (botMove.equals(me) || data[3].equals(me)){
// Let's suprise it
log("This bot could potentially attack us! Let's suprise it!");
bestMove = data[0];
log(s + "");
}
}
}

/*while(s < SIMS){

}*/

}

}


Edits & Fixes

Please let me know if it is still broken! Thanks!

Fix #1: Fixed a bug where CrystalBall would cause an ArrayOutOfBoundsException when called with no arguments.

• It doesn't seem to output ok when called with no arguments at the moment Commented May 26, 2015 at 11:09
• @the-e Interesting. I can't look into it right now, but I'll try and fix it tonight. Also, what does the bot do? Does it just print out nothing or a stacktrace? Commented May 27, 2015 at 0:09
• When I run it with no args, I get an ArrayIndexOutOfBoundsException on String me = args[0];. I recommend that the first thing you do is check for no args so your bot can respond and exit properly. Also the log statements will cause your bot to give an invalid output, so they aren't necessary in this case. I do like the idea of the simulations, though (I'm too lazy to make a bot that creates and analyzes them :P).
– TNT
Commented May 27, 2015 at 4:06
• Your bot does cause the controller to throw an exception, possibly because an exception is being thrown somewhere in your code which prevents any output. To aid in debugging, I suggest wrapping everything in a try-catch block, and in the catch use printStackTrace(System.out); that way you can figure out what exception is being thrown in your program when you run the controller. Otherwise you might have to carefully analyze what's going on...
– TNT
Commented May 27, 2015 at 4:20
• Thanks so much TNT for your help :D. Simulations were certainly challenging, but it was heaps of fun (this is my first programming puzzle ever). So, I have fixed the problem! All I did was move the check upwards and add a few additional checks (if args.length == 0) Commented May 27, 2015 at 9:59