107
50
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 attacker's power is subtracted from your life
- 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)
clones.add(player.copy());
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();
String reply = getReply(player, args);
Player clone = player.findCopyOrMe(clones);
if(reply.equals("T")){
clone.timedOut = true;
clone.life = 0;
}
clone.lastAction = reply.trim();
}
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);}
String getReply(Player player, String[] args){
try{
List<String> cmd = new ArrayList<String>();
String[] tokens = player.cmd.split(" ");
for(String token : tokens)
cmd.add(token);
for(String arg : args)
cmd.add(arg);
ProcessBuilder builder = new ProcessBuilder(cmd);
builder.redirectErrorStream();
long start = System.currentTimeMillis();
Process process = builder.start();
Scanner scanner = new Scanner(process.getInputStream());
process.waitFor();
String reply = scanner.nextLine();
scanner.close();
process.destroy();
if(System.currentTimeMillis() - start > timeout)
return "T";
return reply;
}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)
players.add(player);
}
}
for(String cmd : defaultPlayers){
Player player = createPlayer(cmd);
if(player != null)
players.add(player);
}
}
Player createPlayer(String cmd){
Player player = new Player(cmd);
String reply = getReply(player, new String[]{});
log(player.cmd + " " + reply);
if(reply != null && reply.equals("ok"))
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)
clones.add(player.copy());
AtomicInteger count=new AtomicInteger(players.size());
for(Player player : players){
new Thread(() -> {
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();
String reply = getReply(player, args);
Player clone = player.findCopyOrMe(clones);
if(reply.equals("T")){
clone.timedOut = true;
clone.life = 0;
}
clone.lastAction = reply.trim();
}
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);}
String getReply(Player player, String[] args){
try{
List<String> cmd = new ArrayList<String>();
String[] tokens = player.cmd.split(" ");
for(String token : tokens)
cmd.add(token);
for(String arg : args)
cmd.add(arg);
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();
String reply = scanner.nextLine();
scanner.close();
process.destroy();
if(System.currentTimeMillis() - start > timeout)
return "T";
return reply;
}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)
players.add(player);
}
}
for(String cmd : defaultPlayers){
Player player = createPlayer(cmd);
if(player != null)
players.add(player);
}
}
Player createPlayer(String cmd){
Player player = new Player(cmd);
String reply = getReply(player, new String[]{});
log(player.cmd + " " + reply);
if(reply != null && reply.equals("ok"))
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;
}
}
}
You mean comma separated, not space seperated. – Tim – 2015-05-14T18:30:59.683
2@Tim The bot list is space separated. Each bot's stats are comma separated. – Geobits – 2015-05-14T18:32:22.917
1@Geobits : how often will you run the Tournament and update the leaderboard? – Robbie Wxyz – 2015-05-14T20:08:48.133
1@SuperScript Not on a set schedule. Will do an initial scoreboard tonight, and every one/two days after as long as new entries are coming in regularly. – Geobits – 2015-05-14T20:10:59.203
i have a bot but it's not working yet. should i post it, or wait til tomorrow when i have more time to test? – sirpercival – 2015-05-14T21:48:58.580
@sirpercival You can do either really, but if you don't want to chance the down votes, it's better to post a fully working bot. At least "working" in the sense that it can be compiled and run, even if the logic isn't airtight yet. – Geobits – 2015-05-15T02:28:15.570
Your scoring seems to rather strongly encourage defensive play -- there's no benefit to aggression, only to survival. I've seen this sort of thing before, with RoboWar's Alliance of Pacifist Scum. – Mark – 2015-05-15T04:07:07.547
3@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. – Geobits – 2015-05-15T04:28:05.220
@sirpercival look at what I did, I posted a completely broken bot :P – Elias Benevedes – 2015-05-15T05:02:55.300
Nice challenge. I like it that there is no moving around. While working on a bot, I noticed that the order of the bots make a great difference in the outcome. That's because Hero and Bully are using
life >= best
andlife <= best
. Robots that are entered later in the contest (higher ID) have a disadvantage. Do you randomize the order the robots are entered into the competition? – agtoever – 2015-05-15T10:11:21.760Oh. Wait. Now I see the
Collections.shuffle(players)
inStronger.java
. I suppose that is shuffling the players... – agtoever – 2015-05-15T10:21:26.057@agtoever Yea, that shuffles them between each game for just that purpose. – Geobits – 2015-05-15T12:49:33.080
I'm confused on how to run the Stronger.java file. Do the other class files (Hero.class, for instance) have to be in the same package? Must Stronger and Hero run at the same time? Or am I not supposed to run it? – TNT – 2015-05-16T03:35:47.483
@TNT Stronger is in the default package, so it does make it easier if the bots are too. Just put all the class files in a single folder (along with bots from other languages, etc) and run
java Stronger -log
from that folder in a terminal (removing-log
still works, but it just doesn't give output for a while). If using an IDE, you'll need to make sure the class paths and working directory settings are correct, but that varies from IDE to IDE. – Geobits – 2015-05-16T05:41:38.907Have been waiting for a challenge like this :) But one thing bothers me: May I use the names of the enemies to predict their behavior? – Sebb – 2015-05-17T16:06:36.223
@Sebb No. Rule 2: "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." – OJFord – 2015-05-17T18:00:17.643
8If 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. – OJFord – 2015-05-17T18:04:45.947
3I have a parallelized version of the controller that runs games faster than the original controller does, if anyone is interested I can post it... – Others – 2015-05-17T23:58:30.857
May I train my genetic programming based bots using the sample bots as a base for my bots? – Potatomato – 2015-05-18T04:16:42.600
@Others You can post it as an addition to the main question if you'd like. If I can easily determine the behavior is correct, I'll use that for my next run (hopefully tonight). – Geobits – 2015-05-18T12:48:07.277
@Potatomato Hmm. I'd say it's okay as long as you're only using the sample bots. – Geobits – 2015-05-18T12:48:55.390
I don't think that the parallelized version will work, since some bots rely on their files (which may be a problem). – CommonGuy – 2015-05-18T14:23:14.140
3@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. – Geobits – 2015-05-18T14:35:01.730
@Geobits My bot bases its action on what happened in the very bout that he is currently playing. So if two instances of the bot write in parallel to the same file while playing two different bouts it screws up the logic of the bot. – plannapus – 2015-05-19T06:40:27.593
1@plannapus Unless I've misunderstood, that's not going to happen. What's being parallelised is the running of entered bots, per bout. i.e. Analyst & Tank run simultaneously in turn 1 - but neither of them has a turn 2 until both have completed. – OJFord – 2015-05-19T14:57:48.970
Ok then I am the one who misunderstood. I thought the bouts were run in parallel, not the bots (having written that, no wonder i m confused :) ). – plannapus – 2015-05-20T06:48:27.923
2@Geobits are you gonna post a new leaderboard? – sirpercival – 2015-05-21T12:45:56.703
If you do post a new one I'd like to see results of a larger number of runs. I did a partial version with 150 rounds and that lets you see some results for each of the bots that have reasonable strategies. Great competition! – Ralph Marshall – 2015-05-21T13:40:10.330
@sirpercival Yea, it's been a rough week. I'll try my best to get that done tonight. – Geobits – 2015-05-21T13:41:06.367
1@RalphMarshall Depends on how long it takes with all the bots, but the "official" results will be as posted, 15 rounds/games. If I do post results from a larger one, it's for info purposes only <insert standard disclaimer here>. – Geobits – 2015-05-21T13:42:20.773
Great. Thanks. Looking forward to the new update. – Ralph Marshall – 2015-05-21T14:09:56.377
@Geobits no worries, sorry about your week :( – sirpercival – 2015-05-21T17:25:12.293
@Geobits Must the bots be one file? – Others – 2015-05-22T16:14:50.100
@Others I'd prefer it, just to make running everything easier. If your language forces separate files, that's fine as long as it's a single command to run, and input/output work properly. – Geobits – 2015-05-22T16:52:30.830
@Geobits Ok, thats fine. – Others – 2015-05-22T17:22:19.150
2Oooh, I won a round! I would have expected my Guardian to do better than my Tank, but I'll take what I can get. – Xynariz – 2015-05-22T20:45:24.647
Come back from vacation and I'm in 6th! I enjoy the simplicity of the rules; they have surprising strategic depth. – Shaangor – 2015-05-26T18:01:43.707
@Geobits When do you think you'll have some time to run an other contest? ^^ Some entry actually changed, and some new ones are here ! Btw, Good job for such a nice KotH, there's a lot of people around here ! – Katenkyo – 2015-05-27T08:50:28.240
Now that I have both first and third place I move that we close the contest ;-) But seriously, I'm looking forward to the next round of results, and agree this has been a very well done KotH – Ralph Marshall – 2015-05-28T00:58:16.977