Aidez nos robots à atteindre le téléporteur

17

MISE À JOUR: Ajout d'un framework Python pour commencer.

La station spatiale a été dépassée par des robots concasseurs. Vous devez diriger autant de nos robots techniques coûteux et fragiles appelés «lapins» vers un téléporteur de sortie avant que la station ne s'autodétruise, mais les robots concasseurs patrouillent dans les couloirs.

Votre programme reçoit une carte ASCII et chaque tour est informé de l'emplacement des robots de broyage et de vos lapins actuels. Votre programme doit ensuite déplacer vos lapins vers le téléporteur de sortie tout en évitant les bots concasseurs.

animation de démonstration

Exécution

Exécutez le contrôleur Python 2 avec:

python controller.py <mapfile> <turns> <seed> <runs> <prog>...
<prog> can be <interpreter> <yourprog> or similar.

La graine est un petit entier utilisé pour le broyeur et votre programme PRNG afin que les exécutions soient répétables. Votre programme doit fonctionner de manière cohérente quelle que soit la semence réellement utilisée. Si la valeur de départ est nulle, le contrôleur utilise une valeur de départ aléatoire pour chaque exécution.

Le contrôleur exécutera votre programme avec le nom du fichier texte de la carte et amorcera comme arguments. Par exemple:

perl wandomwabbits.pl large.map 322

Si votre programme utilise un PRNG, vous devez l'initialiser avec la graine donnée. Le contrôleur envoie ensuite vos mises à jour de programme via STDIN et lit vos mouvements de lapin via STDOUT.

Chaque tour, le contrôleur sortira 3 lignes:

turnsleft <INT>
crusher <x,y> <movesto|crushes> <x,y>; ...
rabbits <x,y> <x,y> ...

attend ensuite que le programme affiche une ligne:

move <x,y> to <x,y>; ...

MISE À JOUR: Votre programme aura 2 secondes pour s'initialiser avant que les premières lignes soient envoyées par le contrôleur.

Si votre programme prend plus de 0,5 seconde pour répondre par des mouvements après l'entrée de l'emplacement du lapin du contrôleur, le contrôleur se fermera.

S'il n'y a pas de lapins sur la grille, la ligne de lapins n'aura pas de valeurs et votre programme devrait sortir une ligne de chaîne "move" nue.

N'oubliez pas de vider le flux de sortie de votre programme à chaque tour ou le contrôleur peut se bloquer.

Exemple

entrée prog:

turnsleft 35
crusher 22,3 crushes 21,3; 45,5 movesto 45,4
rabbits 6,4 8,7 7,3 14,1 14,2 14,3

sortie prog:

move 14,3 to 14,4; 14,2 to 14,3; 6,4 to 7,4

Logique du contrôleur

La logique de chaque tour:

  • si le virage à gauche est nul, imprimer le score et quitter.
  • pour chaque cellule de départ vide, ajoutez un lapin si aucun concasseur n'est en vue.
  • pour chaque broyeur, décidez de la direction du mouvement (voir ci-dessous).
  • pour chaque broyeur, déplacez-vous si possible.
  • si le broyeur se trouve dans un emplacement pour le lapin, retirer le lapin.
  • sortie du tourne-tour, des actions du concasseur et des emplacements des lapins à programmer.
  • lire les demandes de déplacement de lapin du programme.
  • si un lapin n'existe pas ou ne se déplace pas, sautez.
  • tracer chaque nouvel emplacement de lapins.
  • si le lapin frappe un concasseur, le lapin est détruit.
  • si le lapin est en sortie du téléporteur, le lapin est retiré et le score augmenté.
  • si les lapins entrent en collision, ils sont tous les deux détruits.

Chaque broyeur a toujours une direction de cap (un de NSEW). Un concasseur suit cette logique de navigation à chaque tour:

  • Si un ou plusieurs lapins sont visibles dans l'une des 4 directions orthogonales, changez de direction pour l'un des lapins les plus proches. Notez que les concasseurs ne peuvent pas voir au-delà d'un autre concasseur.
  • sinon choisissez aléatoirement entre les options ouvertes avant, gauche, droite si possible.
  • sinon, si des obstacles (mur ou autre concasseur) à l'avant, à gauche et à droite, réglez la direction vers l'arrière.

Puis pour chaque broyeur:

  • S'il n'y a pas d'obstacle dans la nouvelle direction du concasseur, déplacez-vous (et éventuellement écrasez).

Les symboles de la carte

La carte est une grille rectangulaire de caractères ASCII. La carte est composée de murs #, d'espaces de couloir , de positions de départ de lapin s, de téléporteurs de sortie eet d'emplacements de départ de broyeur c. Le coin supérieur gauche est l'emplacement (0,0).

Petite carte

###################
#        c        #
# # ######## # # ##
# ###s    #  ####e#
#   # # # ## ##   #
### # ###  # ## # #
#         ##      #
###################

Carte de test

#################################################################
#s                       ############################          s#
## ## ### ############ # #######                ##### ####### ###
## ## ### #            # ####### ########## # # ####   ###### ###
## ## ### # ############ ####### ##########     ##### ####### ###
## ## ##  #              ####### ########## # # ##### ####      #
##    ### #### #### ########     ##########     ##### #### ## ###
######### ####      ######## ################ ####### ####    ###
#########  ################# ################   c     ####### ###
######### ##################          ####### ####### ###########
######### ################## ######## #######         ###########
##### ###   c                          ###### ###################
#         #### ### # # # # # # # # # # ###### ##############    #
# ####### ####                         ###    ####     ##### ## #
#         #### ### # # # # # # # # # # ### # ###   #########    #
##### ### #### ###                   #####   ### #  ######## ####
############## ### # # # # # # # # # # #######   ##  ####### ####
#### #### #### ###                     ###   # # ###  ###### ####
##             ### # # # # # # # # # # ### ### #  ###  ##### ####
##### ######## ### # # # ##### # # # # ### ### # #####  #### ####
##### ##### ######         c   #       ### ###   ######  ### ####
##       c   ######################### ### ##### ####### ### ####
##### # ### #######   ########         ### ##### c  ##    ## ####
#####   #   ####### ########## ## ######## #     ######## ## ####
######### # #######            ## #     ## # # # #####     # ####
### ##### #     ### # ############## # ### #      ###  ## #  ####
#      ## # ### ### # ############## # ### ##### #####    ## ####
### ## ## #     ###                  #           ########       #
#s  ##      ###################################################e#
#################################################################

Exemple de grande exécution de carte

grande démo

But

Pour évaluer votre programme, exécutez le contrôleur avec la carte de test, 500 tours, 5 courses et une graine de 0. Votre score est le nombre total de lapins téléportés avec succès hors de la station en sécurité. En cas d'égalité, la réponse avec le plus de votes l'emportera.

Votre réponse doit inclure un titre avec le nom de l'entrée, la langue utilisée et le score. Dans le corps de la réponse, veuillez inclure la sortie du score du contrôleur avec les numéros de départ afin que d'autres puissent répéter vos courses. Par exemple:

Running: controller.py small.map 100 0 5 python bunny.py
   Run                 Seed      Score
     1                  965          0
     2                  843          6
     3                  749         11
     4                  509         10
     5                  463          3
Total Score: 30

Vous pouvez utiliser des bibliothèques standard et disponibles gratuitement, mais les failles standard sont interdites. Vous ne devez pas optimiser votre programme pour une graine, un nombre de tours, un ensemble d'entités cartographiques ou d'autres paramètres donnés. Je me réserve le droit de modifier la carte, le nombre de tours et le germe si je soupçonne une violation de cette règle.

Code contrôleur

#!/usr/bin/env python
# Control Program for the Rabbit Runner on PPCG.
# Usage: controller.py <mapfile> <turns> <seed> <runs> <prog>...
# Tested on Python 2.7 on Ubuntu Linux. May need edits for other platforms.
# v1.0 First release.
# v1.1 Fixed crusher reporting bug.
# v1.2 Control for animation image production.
# v1.3 Added time delay for program to initialise

import sys, subprocess, time, re, os
from random import *

# Suggest installing Pillow if you don't have PIL already
try:
    from PIL import Image, ImageDraw
except:
    Image, ImageDraw = None, None
GRIDLOG = True      # copy grid to run.log each turn (off for speed)
MKIMAGE = False     # animation image creation (much faster when off)
IMGWIDTH = 600      # animation image width estimate
INITTIME = 2        # Allow 2 seconds for the program to initialise

point = complex     # use complex numbers as 2d integer points
ORTH = [1, -1, 1j, -1j]     # all 4 orthogonal directions

def send(proc, msg):
    proc.stdin.write((msg+'\n').encode('utf-8'))
    proc.stdin.flush()

def read(proc):
    return proc.stdout.readline().decode('utf-8')

def cansee(cell):
    # return a dict of visible cells containing robots with distances
    see = {}    # see[cell] = dist
    robots = rabbits | set(crushers)
    if cell in robots:
        see[cell] = 0
    for direc in ORTH:
        for dist in xrange(1,1000):
            test = cell + direc*dist
            if test in walls:
                break
            if test in robots:
                see[test] = dist
                if test in crushers:
                    break       # can't see past them
    return see

def bestdir(cr, direc):
    # Decide in best direction for this crusher-bot
    seen = cansee(cr)
    prey = set(seen) & rabbits
    if prey:
        target = min(prey, key=seen.get)    # Find closest
        vector = target - cr
        return vector / abs(vector)
    obst = set(crushers) | walls
    options = [d for d in ORTH if d != -direc and cr+d not in obst]
    if options:
        return choice(options)
    return -direc

def features(fname):
    # Extract the map features
    walls, crusherstarts, rabbitstarts, exits = set(), set(), set(), set()
    grid = [line.strip() for line in open(fname, 'rt')]
    grid = [line for line in grid if line and line[0] != ';']
    for y,line in enumerate(grid):
        for x,ch in enumerate(line):
            if ch == ' ': continue
            cell = point(x,y)
            if ch == '#': walls.add(cell)
            elif ch == 's': rabbitstarts.add(cell)
            elif ch == 'e': exits.add(cell)
            elif ch == 'c': crusherstarts.add(cell)
    return grid, walls, crusherstarts, rabbitstarts, exits

def drawrect(draw, cell, scale, color, size=1):
    x, y = int(cell.real)*scale, int(cell.imag)*scale
    edge = int((1-size)*scale/2.0 + 0.5)
    draw.rectangle([x+edge, y+edge, x+scale-edge, y+scale-edge], fill=color)

def drawframe(runno, turn):
    if Image == None:
        return
    scale = IMGWIDTH/len(grid[0])
    W, H = scale*len(grid[0]), scale*len(grid)
    img = Image.new('RGB', (W,H), (255,255,255))
    draw = ImageDraw.Draw(img)
    for cell in rabbitstarts:
        drawrect(draw, cell, scale, (190,190,255))
    for cell in exits:
        drawrect(draw, cell, scale, (190,255,190))
    for cell in walls:
        drawrect(draw, cell, scale, (190,190,190))
    for cell in crushers:
        drawrect(draw, cell, scale, (255,0,0), 0.8)
    for cell in rabbits:
        drawrect(draw, cell, scale, (0,0,255), 0.4)
    img.save('anim/run%02uframe%04u.gif' % (runno, turn))

def text2point(textpoint):
    # convert text like "22,6" to point object
    return point( *map(int, textpoint.split(',')) )

def point2text(cell):
    return '%i,%i' % (int(cell.real), int(cell.imag))

def run(number, nseed):
    score = 0
    turnsleft = turns
    turn = 0
    seed(nseed)
    calltext = program + [mapfile, str(nseed)]
    process = subprocess.Popen(calltext,
            stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=errorlog)
    time.sleep(INITTIME)
    rabbits.clear()
    crushers.clear()
    crushers.update( dict((cr, choice(ORTH)) for cr in crusherstarts) )

    while turnsleft > 0:
        # for each empty start cell, add a rabbit if no crusher in sight.
        for cell in rabbitstarts:
            if cell in rabbits or set(cansee(cell)) & set(crushers):
                continue
            rabbits.add(cell)
        # write the grid to the runlog and create image frames
        if GRIDLOG:
            for y,line in enumerate(grid):
                for x,ch in enumerate(line):
                    cell = point(x,y)
                    if cell in crushers: ch = 'X'
                    elif cell in rabbits: ch = 'o'
                    runlog.write(ch)
                runlog.write('\n')
            runlog.write('\n\n')
        if MKIMAGE:
            drawframe(number, turn)
        # for each crusher, decide move direction.
        for cr, direc in crushers.items():
            crushers[cr] = bestdir(cr, direc)
        # for each crusher, move if possible.
        actions = []
        for cr, direc in crushers.items():
            newcr = cr + direc
            if newcr in walls or newcr in crushers:
                continue
            crushers[newcr] = crushers.pop(cr)
            action = ' movesto '
            # if crusher is at a rabbit location, remove rabbit.
            if newcr in rabbits:
                rabbits.discard(newcr)
                action = ' crushes '
            actions.append(point2text(cr)+action+point2text(newcr))
        # output turnsleft, crusher actions, and rabbit locations to program.
        send(process, 'turnsleft %u' % turnsleft)
        send(process, 'crusher ' + '; '.join(actions))
        rabbitlocs = [point2text(r) for r in rabbits]
        send(process, ' '.join(['rabbits'] + rabbitlocs))
        # read rabbit move requests from program.
        start = time.time()
        inline = read(process)
        if time.time() - start > 0.5:
            print 'Move timeout'
            break
        # if a rabbit not exist or move not possible, no action.
        # if rabbit hits a crusher, rabbit is destroyed.
        # if rabbit is in exit teleporter, rabbit is removed and score increased.
        # if two rabbits collide, they are both destroyed.
        newrabbits = set()
        for p1,p2 in re.findall(r'(\d+,\d+)\s+to\s+(\d+,\d+)', inline):
            p1, p2 = map(text2point, [p1,p2])
            if p1 in rabbits and p2 not in walls:
                if p2-p1 in ORTH:
                    rabbits.discard(p1)
                    if p2 in crushers:
                        pass        # wabbit squished
                    elif p2 in exits:
                        score += 1  # rabbit saved
                    elif p2 in newrabbits:
                        newrabbits.discard(p2)  # moving rabbit collision
                    else:
                        newrabbits.add(p2)
        # plot each new location of rabbits.
        for rabbit in newrabbits:
            if rabbit in rabbits:
                rabbits.discard(rabbit)     # still rabbit collision
            else:
                rabbits.add(rabbit)
        turnsleft -= 1
        turn += 1
    process.terminate()
    return score


mapfile = sys.argv[1]
turns = int(sys.argv[2])
argseed = int(sys.argv[3])
runs = int(sys.argv[4])
program = sys.argv[5:]
errorlog = open('error.log', 'wt')
runlog = open('run.log', 'wt')
grid, walls, crusherstarts, rabbitstarts, exits = features(mapfile)
rabbits = set()
crushers = dict()

if 'anim' not in os.listdir('.'):
    os.mkdir('anim')
for fname in os.listdir('anim'):
    os.remove(os.path.join('anim', fname))

total = 0
print 'Running:', ' '.join(sys.argv)
print >> runlog, 'Running:', ' '.join(sys.argv)
fmt = '%10s %20s %10s'
print fmt % ('Run', 'Seed', 'Score')
for n in range(runs):
    nseed = argseed if argseed else randint(1,1000)
    score = run(n, nseed)
    total += score
    print fmt % (n+1, nseed, score)
print 'Total Score:', total
print >> runlog, 'Total Score:', total

Le contrôleur crée un journal de texte des exécutions run.loget une série d'images dans le animrépertoire. Si votre installation Python ne trouve pas la bibliothèque d'images PIL (à télécharger en tant que Pillow), aucune image ne sera générée. J'ai animé la série d'images avec ImageMagick. Par exemple:

convert -delay 100 -loop 0 anim/run01* run1anim.gif

Vous êtes invités à publier des animations ou des images intéressantes avec votre réponse.

Vous pouvez désactiver ces fonctionnalités et accélérer le contrôleur en réglant GRIDLOG = Falseet / ou MKIMAGE = Falsedans les premières lignes du programme du contrôleur.

Framework Python suggéré

Pour vous aider à démarrer, voici un framework en Python. La première étape consiste à lire le fichier de carte et à trouver les chemins d'accès aux sorties. À chaque tour, il devrait y avoir un code pour stocker où se trouvent les broyeurs et un code qui décide où déplacer nos lapins. La stratégie la plus simple pour commencer est de déplacer les lapins vers une sortie en ignorant les broyeurs - certains lapins pourraient passer.

import sys, re
from random import *

mapfile = sys.argv[1]
argseed = int(sys.argv[2])
seed(argseed)

grid = [line.strip() for line in open(mapfile, 'rt')]
#
# Process grid to find teleporters and paths to get there
#

while 1:
    msg = sys.stdin.readline()

    if msg.startswith('turnsleft'):
        turnsleft = int(msg.split()[1])

    elif msg.startswith('crusher'):
        actions = re.findall(r'(\d+),(\d+) (movesto|crushes) (\d+),(\d+)', msg)
        #
        # Store crusher locations and movement so we can avoid them
        #

    elif msg.startswith('rabbits'):
        moves = []
        places = re.findall(r'(\d+),(\d+)', msg)
        for rabbit in [map(int, xy) for xy in places]:
            #
            # Compute the best move for this rabbit
            newpos = nextmoveforrabbit(rabbit)
            #
            moves.append('%u,%u to %u,%u' % tuple(rabbit + newpos))
        print 'move ' + '; '.join(moves)
        sys.stdout.flush()
Logic Knight
la source
Est-il permis de simuler le contrôleur, avec exactement le même RNG? Cela rendrait les concasseurs déterministes, vous permettant de prédire leur comportement et de les éviter. Enfer, vous pourriez probablement faire un ou quelques `` lapins-appâts '' pour garder les broyeurs occupés tout en installant une autoroute sans tracas de lapins
orlp
Non, vous ne pouvez pas simuler le RNG (ou l'enregistrer pour une graine particulière). Les concasseurs sont conçus pour ne PAS être déterministes, il s'agit donc d'un défi de code pour élaborer une stratégie susceptible de les éviter. L'idée du «lapin appât» est certainement acceptable. J'attends des stratégies impliquant des lapins sacrificiels.
Logic Knight
Si la graine est 0, chaque exécution n'utilisera-t-elle pas une graine aléatoire?
TheNumberOne
Oui. Si la valeur de départ du contrôleur est nulle, il utilisera (et enverra au programme de test) une valeur de départ aléatoire pour chaque exécution. Cette graine est rapportée avec le score de la course. La réintroduction de cette graine dans le contrôleur devrait permettre de vérifier cette analyse (et ce score) exacts. C'est un peu complexe, mais c'était la meilleure façon de comprendre pour permettre la réplication des exécutions (déterministes) et permettre le caractère aléatoire du contrôleur et du comportement du programme de test.
Logic Knight

Réponses:

2

Fou, Python 45

J'ai fait 25 runs avec des graines aléatoires, mon ordinateur n'est pas assez rapide pour aller à 1000 (si quelqu'un veut corriger le score) Premier programme en python, ce fut une douleur à déboguer pour moi. De plus, je ne sais pas si je l'ai bien utilisé.

Il utilise un premier algorithme dès la sortie, l'un prenant en compte les concasseurs, l'autre sans. J'ai eu beaucoup de temps d'arrêt, donc je n'ai pas opté pour quelque chose de plus complexe (un retrait de concasseur, etc.). Les lapins deviennent également fous s'il y a un concasseur à proximité dans l'espoir de le faire se tromper.

import sys, re
from random import *

mapfile = sys.argv[1]
argseed = int(sys.argv[2])
seed(argseed)

grid = [line.strip() for line in open(mapfile, 'rt')]
width = len(grid[0])
height = len(grid)

starts = set([])
end = ()
walkables = set([])
crushers = set([])
#
# Process grid to find teleporters and paths to get there
#
for a in range(height):
    for b in range(width):
        if grid[a][b] == 'e':
            end = (b,a)
            walkables.add((b,a))
        elif grid[a][b] == 's':
            starts.add((b,a))
            walkables.add((b,a))
        elif grid[a][b] == 'c':
            crushers.add((b,a))
            walkables.add((b,a))
        elif grid[a][b] == ' ':
            walkables.add((b,a))

toSearch = [ (end, 0) ]
inSearch = [ end ]
visited = set([])
gradient = [[0]*height for x in range(width)]
while len(toSearch) > 0 :
    current = toSearch.pop(0)
    (row, col) = current[0]
    length = current[1]
    visited.add(current[0])
    neighbors = {(row+1,col),(row-1,col),(row,col+1),(row,col-1)}
    neighborSpaces = walkables & neighbors
    unvisited = neighborSpaces - visited
    for node in unvisited:
        if not node in inSearch:
            gradient[node[0]][node[1]]=[current[0][0],current[0][1]]
            inSearch.append(node)
            toSearch.append((node, length+1))
    toSearch.sort(key=lambda node: node[1])

while 1:
    msg = sys.stdin.readline()

    if msg.startswith('turnsleft'):
        turnsleft = int(msg.split()[1])

    elif msg.startswith('crusher'):
        # Update crushers
        actions = re.findall(r'(\d+),(\d+) (movesto|crushes) (\d+),(\d+)', msg)
        for one_action in actions:
            crushers.discard((int(one_action[0]),int(one_action[1])))
            crushers.add((int(one_action[3]),int(one_action[4])))

    elif msg.startswith('rabbits'):
        toSearch = [ (end, 0) ]
        inSearch = [ end ]
        visited = set([])
        gradient2 = [[0]*height for x in range(width)]
        while len(toSearch) > 0 :
            current = toSearch.pop(0)
            (row, col) = current[0]
            length = current[1]
            visited.add(current[0])
            neighbors = {(row+1,col),(row-1,col),(row,col+1),(row,col-1)}
            neighborSpaces = (walkables - crushers) & neighbors
            unvisited = neighborSpaces - visited
            for node in unvisited:
                if not node in inSearch:
                    gradient2[node[0]][node[1]]=[current[0][0],current[0][1]]
                    inSearch.append(node)
                    toSearch.append((node, length+1))
            toSearch.sort(key=lambda node: node[1])
        moves = []
        places = re.findall(r'(\d+),(\d+)', msg)
        for rabbit in [map(int, xy) for xy in places]:
            # If any crushers insight, go crazy to lose him
            directions = [(1,0),(-1,0),(0,1),(0,-1)]
            crazy = False
            newpos = 0
            for direction in directions:
                (row, col) = rabbit
                sight = 0
                while len({(row,col)} & walkables)>0 and sight<5 and crazy == False:
                    sight+=1
                    if (row,col) in crushers:
                        directions.remove(direction)
                        crazy = True
                    (row,col) = (row+direction[0],col+direction[1])
            for direction in directions:
                if not (rabbit[0]+direction[0],rabbit[1]+direction[1]) in walkables:
                    directions.remove(direction)
            if len(directions)==0:
                directions = [(1,0),(-1,0),(0,1),(0,-1)]
            direction = choice(directions)
            newpos = [rabbit[0]+direction[0],rabbit[1]+direction[1]]
            # Else use gradients
            if crazy == False:
                newpos = gradient2[rabbit[0]][rabbit[1]]
                if newpos == 0:
                    newpos = gradient[rabbit[0]][rabbit[1]]
            moves.append('%u,%u to %u,%u' % tuple(rabbit + newpos))
        print 'move ' + '; '.join(moves)
        sys.stdout.flush()

Animation pour une course moyenne

Frappé
la source
Je pense que vous pouvez avoir des erreurs dans votre indentation. Le premier espace blanc est important en Python. par exemple: les walkables.add((b,a))lignes.
Logic Knight
devrait bien fonctionner maintenant
Hit
1

Bunny, Java, 26.385

J'ai fait la moyenne des scores des runs 1 à 1000 et multiplié par 5 pour obtenir mon score. Je suis presque sûr que cela équivaut au score moyen de tous les jeux possibles avec les options standard.

Les scores

import java.awt.Point;
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.InputStreamReader;
import java.io.PrintStream;
import java.util.*;
import java.util.stream.Collectors;

public class Main {

    private static final char WALL = '#';
    private static final char CRUSHER = 'c';
    private static final char ESCAPE = 'e';
    private static final char HUTCH = 's';

    private int height;
    private int width;

    private char[][] map;
    private List<Point> escapes = new ArrayList<>();
    private List<Point> crushers = new ArrayList<>();
    private List<Point> rabbits = new ArrayList<>();
    private List<Point> hutches = new ArrayList<>();

    private BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
    private PrintStream out = System.out;
    private int[][] distances;

    public Main(String[] args) throws Exception {
        loadMap(args[0]);
    }

    private void loadMap(String mapFileName) throws Exception {
        char[][] preMap = new BufferedReader(new FileReader(mapFileName))
                .lines()
                .map(String::toCharArray)
                .toArray(char[][]::new);

        width = preMap[0].length;
        height = preMap.length;

        map = new char[width][height];    //tranpose

        for (int x = 0; x < width; x++){
            for (int y = 0; y < height; y++){
                map[x][y] = preMap[y][x];
            }
        }

        processMap();

        distances = dijkstra();

    }

    private void processMap() {
        for (int x = 0; x < width; x++){
            for (int y = 0; y < height; y++){
                char c = map[x][y];
                Point p = new Point(x, y);
                if (c == CRUSHER){
                    crushers.add(p);
                }
                if (c == ESCAPE){
                    escapes.add(p);
                }
                if (c == HUTCH){
                    hutches.add(p);
                }
            }
        }
    }

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

    private void run() throws Exception{
        while (true) {
            in.readLine();
            readCrushers();
            readRabbits();
            makeDecision();
        }
    }

    private void makeDecision() {
        Map<Point, Point> moves = new HashMap<>();

        for (Point rabbit : rabbits){
            Point bestDirection = null;
            for (Point p : pointsAroundInclusive(rabbit)){
                if (
                        (bestDirection == null ||
                                distances[p.x][p.y] < distances[bestDirection.x][bestDirection.y]
                        ) && !moves.entrySet().contains(p)){
                    bestDirection = p;
                }
            }
            if (bestDirection != null) {
                moves.put(rabbit, bestDirection);
            }
        }

        out.println("move" +
                moves.entrySet().stream().map(a -> {
                    Point l0 = a.getKey();
                    Point l1 = a.getValue();
                    return " " + l0.x + "," + l0.y + " to " + l1.x + "," + l1.y;
                }).collect(Collectors.joining(";")));
    }

    private List<Point> pointsAroundInclusive(Point point) {
        List<Point> result = pointsAroundExclusive(point);
        result.add(point);
        return result;
    }

    private int[][] dijkstra() {
        Queue<Point> queue = new LinkedList<>();
        Set<Point> scanned = new HashSet<>();
        queue.addAll(escapes);
        scanned.addAll(escapes);

        int[][] distances = new int[width][height];

        while (!queue.isEmpty()) {
            Point next = queue.remove();
            int distance = distances[next.x][next.y];

            pointsAroundExclusive(next).stream()
                    .filter(p -> !scanned.contains(p))
                    .forEach(p -> {
                        distances[p.x][p.y] = distance + 1;
                        scanned.add(p);
                        queue.add(p);
                    });
        }

        return distances;
    }

    private List<Point> pointsAroundExclusive(Point p) {
        Point[] around = new Point[]{
                new Point(p.x - 1, p.y),
                new Point(p.x + 1, p.y),
                new Point(p.x, p.y - 1),
                new Point(p.x, p.y + 1)
        };

        List<Point> result = new ArrayList<>(Arrays.asList(around));
        result.removeIf(a -> {
            if (a.x < 0 || a.x >= width){
                return true;
            }
            if (a.y < 0 || a.y >= height){
                return true;
            }
            char c = map[a.x][a.y];
            return c == WALL;
        });

        return result;
    }

    private void readRabbits() throws Exception {
        String[] locations = in.readLine().substring("rabbits".length()).trim().split("\\s");
        rabbits.clear();

        for (String location : locations){

            if (location.equals("")){
                continue;
            }

            String[] decomposed = location.split(",");

            int x = Integer.parseInt(decomposed[0]);
            int y = Integer.parseInt(decomposed[1]);

            rabbits.add(new Point(x, y));
        }

    }

    private void readCrushers() throws Exception {
        String[] locations = in.readLine().substring("crusher".length()).trim().split("(; )?\\d+,\\d+ (movesto|crushes) ");
        crushers.clear();

        for (String location : locations){

            if (location.equals("")){
                continue;
            }

            String[] decomposed = location.split(",");

            int x = Integer.parseInt(decomposed[0]);
            int y = Integer.parseInt(decomposed[1]);

            crushers.add(new Point(x, y));
        }
    }

}

La meilleure course testée a une graine 1000. Voici un GIF:

entrez la description de l'image ici

Le numéro un
la source