Wrapper for Non-Java Submissions
NOTE MAP_SIZE support has been added. If you care, please update your submission accordingly.
This is community wiki entry for a wrapper, usable by those who want to play but don't like/don't know Java. Please use it, have fun, and I'm happy to help you get things set up.
It's pretty late here as I'm finishing up, so other Java coders, please look this over and suggest improvements. If you can, do so via my github repository by filing an issue or submitting a patch. Thanks!
This entire is being distributed with the UNLICENSE, please follow/fork it from its github repository. Submit patches there if you find issues and I'll update this post.
Current Examples of Wrapper in Use
plannapus: WolfCollectiveMemory in R
toothbrush: Toothbrush in ECMAScript
How to use
What follows are instructions on the protocol for-inter process communication via PIPES I have defined for remote Wolves. Note I've skipped MAP_SIZE as this doesn't appear to exist, in spite of its presence in OP's problem statement. If it does appear, I'll update this post.
IMPORTANT NOTES:
- Only a single invocation of your external process will be made (so wrap your processing logic in an infinite loop. This also lets you keep any processing in-memory, instead of using disk)
- All communication is to this single external process via STDIN and STDOUT
- You must explicitly flush all output sent to STDOUT, and make sure it is newline-terminated
Specification
Remote scripts are supported by a simple protocol via STDIN and STDOUT hooks, and is split into initialization, Move, and Attack.
In each case communication with your process will be via STDIN, and a reply is necessary from STDOUT.
If a reply is not received in 1 second, your process will be assumed to be dead and an exception will be thrown.
All characters will be encoded in UTF-8, for consistency. Every input will terminate with a newline character, and your process should terminate every output reply with a newline as well.
WARNING Be sure to flush your output buffer after every write, to ensure the Java wrapper sees your output. Failure to flush may cause your remote Wolf to fail.
Note that only a single process will be created, all Wolves must be managed within that one process. Read on for how this spec will help.
Initialization
STDIN: S<id><mapsize>
\n
STDOUT: K<id>
\n
<id>
: 00
or 01
or ... or 99
Explanation:
The character S
will be sent followed by two numeric characters 00
, 01
, ..., 99
indicating which of the 100 wolves is being initialized. In all future communication with that specific wolf, the same <id>
will be used.
Following the ID, a variable length sequence of numeric characters will be be sent. This is the size of the map. You'll know the sequence of numeric characters is over when you reach the newline (\n
).
To ensure your process is alive, you must reply with the character K
followed by the same <id>
you received. Any other reply will result in an exception, killing your wolves.
Movement
STDIN: M<id><C0><C1>...<C7><C8>
\n
STDOUT: <mv><id>
\n
<Cn>
: W
or
or B
or S
or L
W
: Wolf
: Empty Space
B
: Bear
S
: Stone
L
: Lion
<mv>
: H
or U
or L
or R
or D
H
: Move.HOLD
U
: Move.UP
L
: Move.LEFT
R
: Move.RIGHT
D
: Move.DOWN
Explanation:
The character M
will be sent followed by the two character <id>
to indicate which Wolf needs to choose a move. Following that, 9 characters will be sent representing that Wolf's surroundings, in row order (top row, middle row, bottom row from leftmost to rightmost).
Reply with one of the valid movement characters <mv>
, followed by the Wolf's two digit <id>
for confirmation.
Attack
STDIN: A<id><C>
\n
STDOUT: <atk><id>
\n
<C>
: W
or B
or S
or L
<atk>
: R
or P
or S
or D
R
: Attack.ROCK
P
: Attack.PAPER
S
: Attack.SCISSORS
D
: Attack.SUICIDE
Explanation:
The character A
will be sent followed by the two character <id>
to indicate which Wolf is participating in an attack. This is followed by a single character <C>
indicating which type of thing is attacking, either a W
olf, B
ear, S
tone, or L
ion.
Reply with one of the <atk>
characters listed above, indicating what your response to the attack is, following by the two digit <id>
for confirmation.
And that's it. There's no more to it. If you lose an attack, that <id>
will never be sent to your process again, that's how you will know your Wolf has died -- if a complete Movement round has passed without that <id>
ever being sent.
Conclusion
Note that any exceptions will kill all the Wolves of your remote type, as only a single "Process" is constructed of your remote wolf, for all wolves of your type that get created.
In this repository you'll find the Wolf.java
file. Search and replace the following strings to set up your bot:
Replace <invocation>
with the command line argument that will properly execute your process.
Replace <custom-name>
with a unique name for your Wolf.
For an example look at the repository, where I have WolfRandomPython.java
that invokes my example remote, the PythonWolf.py
(a Python 3+ Wolf).
Rename the file to be Wolf<custom-name>.java
, where <custom-name>
is replaced with the name you chose above.
To test your Wolf, compile the Java program (javac Wolf<custom-name>.java
), and follow Rusher's instructions to include it in the simulation program.
Important: Be sure to provide clear, concise instructions on how to compile/execute your actual Wolf, which follows the scheme I've outlined above.
Good luck, and may nature be ever in your favor.
The Wrapper Code
Remember, you MUST do the searches and replaces outlined about for this to work. If your invocation is particularly hairy, please contact me for assistance.
Note there is a main
method in this wrapper, to allow rudimentary "pass/fail" testing on your local box. To do so, download the Animal.java class from the project, and remove the package animals;
line from both files. Replace the MAP_SIZE line in Animal.java with some constant (like 100). Compile them using javac Wolf<custom-name>.java
an execute via java Wolf<custom-name>
.
package animals;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.io.OutputStreamWriter;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.Callable;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ThreadPoolExecutor;
/**
* Remote Wolf<custom-name> wrapper class.
*/
public class Wolf<custom-name> extends Animal {
/**
* Simple test script that sends some typical commands to the
* remote process.
*/
public static void main(String[]args){
Wolf<custom-name>[] wolves = new Wolf<custom-name>[100];
for(int i=0; i<10; i++) {
wolves[i] = new Wolf<custom-name>();
}
char map[][] = new char[3][3];
for (int i=0;i<9;i++)
map[i/3][i%3]=' ';
map[1][1] = 'W';
for(int i=0; i<10; i++) {
wolves[i].surroundings=map;
System.out.println(wolves[i].move());
}
for(int i=0; i<10; i++) {
System.out.println(wolves[i].fight('S'));
System.out.println(wolves[i].fight('B'));
System.out.println(wolves[i].fight('L'));
System.out.println(wolves[i].fight('W'));
}
wolfProcess.endProcess();
}
private static WolfProcess wolfProcess = null;
private static Wolf<custom-name>[] wolves = new Wolf<custom-name>[100];
private static int nWolves = 0;
private boolean isDead;
private int id;
/**
* Sets up a remote process wolf. Note the static components. Only
* a single process is generated for all Wolves of this type, new
* wolves are "initialized" within the remote process, which is
* maintained alongside the primary process.
* Note this implementation makes heavy use of threads.
*/
public Wolf<custom-name>() {
super('W');
if (Wolf<custom-name>.wolfProcess == null) {
Wolf<custom-name>.wolfProcess = new WolfProcess();
Wolf<custom-name>.wolfProcess.start();
}
if (Wolf<custom-name>.wolfProcess.initWolf(Wolf<custom-name>.nWolves, MAP_SIZE)) {
this.id = Wolf<custom-name>.nWolves;
this.isDead = false;
Wolf<custom-name>.wolves[id] = this;
} else {
Wolf<custom-name>.wolfProcess.endProcess();
this.isDead = true;
}
Wolf<custom-name>.nWolves++;
}
/**
* If the wolf is dead, or all the wolves of this type are dead, SUICIDE.
* Otherwise, communicate an attack to the remote process and return
* its attack choice.
*/
@Override
public Attack fight(char opponent) {
if (!Wolf<custom-name>.wolfProcess.getRunning() || isDead) {
return Attack.SUICIDE;
}
try {
Attack atk = Wolf<custom-name>.wolfProcess.fight(id, opponent);
if (atk == Attack.SUICIDE) {
this.isDead = true;
}
return atk;
} catch (Exception e) {
System.out.printf("Something terrible happened, this wolf has died: %s", e.getMessage());
isDead = true;
return Attack.SUICIDE;
}
}
/**
* If the wolf is dead, or all the wolves of this type are dead, HOLD.
* Otherwise, get a move from the remote process and return that.
*/
@Override
public Move move() {
if (!Wolf<custom-name>.wolfProcess.getRunning() || isDead) {
return Move.HOLD;
}
try {
Move mv = Wolf<custom-name>.wolfProcess.move(id, surroundings);
return mv;
} catch (Exception e) {
System.out.printf("Something terrible happened, this wolf has died: %s", e.getMessage());
isDead = true;
return Move.HOLD;
}
}
/**
* The shared static process manager, that synchronizes all communication
* with the remote process.
*/
static class WolfProcess extends Thread {
private Process process;
private BufferedReader reader;
private PrintWriter writer;
private ExecutorService executor;
private boolean running;
public boolean getRunning() {
return running;
}
public WolfProcess() {
process = null;
reader = null;
writer = null;
running = true;
executor = Executors.newFixedThreadPool(1);
}
public void endProcess() {
running = false;
}
/**
* WolfProcess thread body. Keeps the remote connection alive.
*/
public void run() {
try {
System.out.println("Starting Wolf<custom-name> remote process");
ProcessBuilder pb = new ProcessBuilder("<invocation>".split(" "));
pb.redirectErrorStream(true);
process = pb.start();
System.out.println("Wolf<custom-name> process begun");
// STDOUT of the process.
reader = new BufferedReader(new InputStreamReader(process.getInputStream(), "UTF-8"));
System.out.println("Wolf<custom-name> reader stream grabbed");
// STDIN of the process.
writer = new PrintWriter(new OutputStreamWriter(process.getOutputStream(), "UTF-8"));
System.out.println("Wolf<custom-name> writer stream grabbed");
while(running){
this.sleep(0);
}
reader.close();
writer.close();
process.destroy(); // kill it with fire.
executor.shutdownNow();
} catch (Exception e) {
e.printStackTrace();
System.out.println("Wolf<custom-name> ended catastrophically.");
}
}
/**
* Helper that invokes a read with a timeout
*/
private String getReply(long timeout) throws TimeoutException, ExecutionException, InterruptedException{
Callable<String> readTask = new Callable<String>() {
@Override
public String call() throws Exception {
return reader.readLine();
}
};
Future<String> future = executor.submit(readTask);
return future.get(timeout, TimeUnit.MILLISECONDS);
}
/**
* Sends an initialization command to the remote process
*/
public synchronized boolean initWolf(int wolf, int map_sz) {
while(writer == null){
try {
this.sleep(0);
}catch(Exception e){}
}
boolean success = false;
try{
writer.printf("S%02d%d\n", wolf, map_sz);
writer.flush();
String reply = getReply(5000l);
if (reply != null && reply.length() >= 3 && reply.charAt(0) == 'K') {
int id = Integer.valueOf(reply.substring(1));
if (wolf == id) {
success = true;
}
}
if (reply == null) {
System.out.println("did not get reply");
}
} catch (TimeoutException ie) {
endProcess();
System.out.printf("Wolf<custom-name> %d failed to initialize, timeout\n", wolf);
} catch (Exception e) {
endProcess();
System.out.printf("Wolf<custom-name> %d failed to initialize, %s\n", wolf, e.getMessage());
}
return success;
}
/**
* Send an ATTACK command to the remote process.
*/
public synchronized Attack fight(int wolf, char opponent) {
Attack atk = Attack.SUICIDE;
try{
writer.printf("A%02d%c\n", wolf, opponent);
writer.flush();
String reply = getReply(1000l);
if (reply.length() >= 3) {
int id = Integer.valueOf(reply.substring(1));
if (wolf == id) {
switch(reply.charAt(0)) {
case 'R':
atk = Attack.ROCK;
break;
case 'P':
atk = Attack.PAPER;
break;
case 'S':
atk = Attack.SCISSORS;
break;
case 'D':
atk = Attack.SUICIDE;
break;
}
}
}
} catch (TimeoutException ie) {
endProcess();
System.out.printf("Wolf<custom-name> %d failed to attack, timeout\n", wolf);
} catch (Exception e) {
endProcess();
System.out.printf("Wolf<custom-name> %d failed to attack, %s\n", wolf, e.getMessage());
}
return atk;
}
/**
* Send a MOVE command to the remote process.
*/
public synchronized Move move(int wolf, char[][] map) {
Move move = Move.HOLD;
try{
writer.printf("M%02d", wolf);
for (int row=0; row<map.length; row++) {
for (int col=0; col<map[row].length; col++) {
writer.printf("%c", map[row][col]);
}
}
writer.print("\n");
writer.flush();
String reply = getReply(1000l);
if (reply.length() >= 3) {
int id = Integer.valueOf(reply.substring(1));
if (wolf == id) {
switch(reply.charAt(0)) {
case 'H':
move = Move.HOLD;
break;
case 'U':
move = Move.UP;
break;
case 'L':
move = Move.LEFT;
break;
case 'R':
move = Move.RIGHT;
break;
case 'D':
move = Move.DOWN;
break;
}
}
}
} catch (TimeoutException ie) {
endProcess();
System.out.printf("Wolf<custom-name> %d failed to move, timeout\n", wolf);
} catch (Exception e) {
endProcess();
System.out.printf("Wolf<custom-name> %d failed to move, %s\n", wolf, e.getMessage());
}
return move;
}
}
}
int MAP_SIZE
" I'm having trouble figuring out how to use this. Netbeans says there's no instance of the stringMAP_SIZE
in any of the files in the project. \$\endgroup\$