Pomóż naszym robotom dotrzeć do teleportera

17

AKTUALIZACJA: Dodano platformę Python, aby rozpocząć.

Stacja kosmiczna została przejęta przez roboty kruszące. Musisz skierować tylu naszych drogich i delikatnych botów technicznych zwanych „królikami” do teleportera wyjściowego, zanim stacja samozniszczy, ale roboty kruszące patrolują korytarze.

Twój program otrzymuje mapę ASCII, a każda kolejka jest informowana, gdzie znajdują się roboty kruszące i jakie są twoje obecne króliki. Twój program powinien następnie przesunąć króliki w stronę teleportera wyjściowego, unikając jednocześnie robotów kruszących.

animacja demo

Wykonanie

Uruchom kontroler Python 2 z:

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

Ziarno to mała liczba całkowita używana do kruszarki i twojego programu PRNG, dzięki czemu przebiegi są powtarzalne. Twój program powinien działać konsekwentnie, niezależnie od faktycznego użytego materiału siewnego. Jeśli seed wynosi zero, sterownik użyje losowego seed dla każdego przebiegu.

Kontroler uruchomi twój program z nazwą pliku tekstowego mapy i nasion jako argumentów. Na przykład:

perl wandomwabbits.pl large.map 322

Jeśli twój program używa PRNG, powinieneś zainicjować go z podanym ziarnem. Następnie sterownik wysyła aktualizacje programu przez STDIN i odczytuje ruchy królika przez STDOUT.

Za każdym razem kontroler wyświetla 3 linie:

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

następnie czeka, aż program wypisze jeden wiersz:

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

AKTUALIZACJA: Twój program będzie miał 2 sekundy na zainicjowanie przed wysłaniem pierwszych linii przez kontroler.

Jeśli twój program potrzebuje więcej niż 0,5 sekundy, aby odpowiedzieć ruchami po wprowadzeniu lokalizacji królika kontrolera, kontroler zakończy działanie.

Jeśli na siatce nie ma królików, linia królików nie będzie miała żadnych wartości, a twój program powinien wypisać pustą linię „ruch”.

Pamiętaj, aby opróżniać strumień wyjściowy programu co turę, w przeciwnym razie kontroler może się zawiesić.

Przykład

wejście 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

wyjście progowe:

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

Logika kontrolera

Logika dla każdej tury:

  • jeśli skręt w lewo wynosi zero, wydrukuj wynik i wyjdź.
  • do każdej pustej komórki początkowej dodaj królika, jeśli nie widać kruszarki.
  • dla każdej kruszarki zdecyduj o kierunku ruchu (patrz poniżej).
  • dla każdej kruszarki przesuń, jeśli to możliwe.
  • jeśli kruszarka znajduje się w miejscu królika, usuń królika.
  • wyjście w lewo, akcje kruszarki i lokalizacje królików do zaprogramowania.
  • czytaj żądania przeniesienia królika z programu.
  • jeśli królik nie istnieje lub ruch nie jest możliwy, pomiń.
  • wykreśl każdą nową lokalizację królików.
  • jeśli królik uderzy w kruszarkę, królik zostanie zniszczony.
  • jeśli królik jest w teleporterze wyjściowym, królik jest usuwany, a wynik zwiększany.
  • jeśli króliki zderzą się, oba zostaną zniszczone.

Każda kruszarka ma zawsze kierunek (jeden z NSEW). Rozdrabniacz podąża za tą logiką nawigacji co turę:

  • Jeśli jeden lub więcej królików jest widocznych w jednym z 4 ortogonalnych kierunków, zmień kierunek na jednego z najbliższych królików. Pamiętaj, że kruszarki nie widzą obok innej kruszarki.
  • w przeciwnym razie wybieraj losowo pomiędzy otwartymi do przodu, w lewo, w prawo, jeśli to możliwe.
  • w przeciwnym razie, jeśli przeszkody (ściana lub inna kruszarka) z przodu, z lewej i prawej strony, ustaw kierunek z tyłu.

Następnie dla każdej kruszarki:

  • Jeśli w nowym kierunku kruszarki nie ma przeszkody, przesuń się (i prawdopodobnie zmiażdż).

Symbole na mapie

Mapa jest prostokątną siatką znaków ASCII. Mapa składa się ze ścian #, przestrzeni korytarza , pozycji początkowych królika s, teleporterów wyjściowych ei lokalizacji początkowych kruszarki c. Lewy górny róg to lokalizacja (0,0).

Mała mapa

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

Mapa testowa

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

Przykład dużego uruchomienia mapy

duże demo

Wynik

Aby ocenić swój program, uruchom kontroler z mapą testową, 500 zwojów, 5 przejazdów i zerowym wynikiem 0. Twój wynik to całkowita liczba królików, które pomyślnie teleportowały się ze stacji w bezpieczne miejsce. W przypadku remisu wygra odpowiedź z największą liczbą głosów.

Twoja odpowiedź powinna zawierać tytuł z nazwą wpisu, użytym językiem i wynikiem. W treści odpowiedzi dołącz wynik kontrolera wraz z numerami początkowymi, aby inni mogli powtórzyć twoje przebiegi. Na przykład:

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

Możesz korzystać ze standardowych i swobodnie dostępnych bibliotek, ale standardowe luki są zabronione. Nie wolno optymalizować programu pod kątem nasion, liczby zakrętów, zestawu funkcji mapy ani innych parametrów. Zastrzegam sobie prawo do zmiany mapy, liczby zakrętów i nasion, jeśli podejrzewam naruszenie tej zasady.

Kod kontrolera

#!/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

Kontroler tworzy dziennik tekstowy przebiegów run.logi serię obrazów w animkatalogu. Jeśli instalacja Pythona nie może znaleźć biblioteki obrazów PIL (pobierz jako Pillow), żadne obrazy nie zostaną wygenerowane. Animowałem serię obrazów za pomocą ImageMagick. Na przykład:

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

Możesz opublikować ciekawe animacje lub zdjęcia z odpowiedzią.

Możesz wyłączyć te funkcje i przyspieszyć kontroler, ustawiając GRIDLOG = Falsei / lubMKIMAGE = False w pierwszych kilku liniach programu kontrolera.

Sugerowana struktura Pythona

Aby pomóc rozpocząć, oto framework w Pythonie. Pierwszym krokiem jest odczytanie pliku mapy i znalezienie ścieżek do wyjść. Na każdym kroku powinien być jakiś kod do przechowywania, gdzie są kruszarki, i kod, który decyduje, gdzie przenieść nasze króliki. Najprostszą strategią na początek jest przesunięcie królików w kierunku wyjścia ignorując kruszarki - niektóre króliki mogą się przedostać.

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
źródło
Czy można symulować kontroler przy użyciu dokładnie tego samego RNG? To sprawiłoby, że kruszarki byłyby deterministyczne, pozwalając ci przewidzieć ich zachowanie i ich unikać. Do diabła, prawdopodobnie mógłbyś zrobić jednego lub kilka „królików-przynęt”, aby utrzymać kruszarki w ruchu podczas ustawiania niezabudowanej autostrady królików
lub
Nie, nie możesz symulować RNG (ani nagrywać go dla konkretnego materiału siewnego). Kruszarki są zaprojektowane tak, aby NIE były deterministyczne, więc jest to wyzwanie kodowe, aby stworzyć strategię, która prawdopodobnie ich uniknie. Pomysł „królika-przynęty” jest z pewnością w porządku. Spodziewam się pewnych strategii dotyczących ofiarnych królików.
Logic Knight
Jeśli seed wynosi 0, czy każde uruchomienie nie będzie używać losowego seedu?
TheNumberOne
Tak. Jeśli seed kontrolera ma wartość zero, użyje (i wyda programowi testowemu) losowego seedu dla każdego uruchomienia. To ziarno jest raportowane z wynikiem przebiegu. Podanie tego ziarna z powrotem do kontrolera powinno spowodować, że dokładny przebieg (i wynik) zostanie zweryfikowany. Jest to trochę skomplikowane, ale był to najlepszy sposób, jaki mogłem wymyślić, aby umożliwić replikację uruchomień (deterministyczny) i pozwolić na losowość w zachowaniu kontrolera i programu testowego.
Logic Knight

Odpowiedzi:

2

Szalony, Python 45

Zrobiłem 25 przebiegów z losowym ziarnem, mój komputer nie jest wystarczająco szybki, aby przejść do 1000 (jeśli ktoś chce poprawić wynik) Pierwszy program w pythonie, to był dla mnie problem z debugowaniem. Nie wiem też, czy dobrze go wykorzystałem.

Używa pierwszego algorytmu od wyjścia, jeden z uwzględnieniem kruszarek, drugi bez. Miałem dużo czasu, więc nie zdecydowałem się na coś bardziej złożonego (usunięcie jednej kruszarki itp.). Króliki również zwariują, jeśli w pobliżu znajduje się kruszarka, w nadziei, że pójdzie na niewłaściwą ścieżkę.

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()

Animacja dla przeciętnego biegu

Trafienie
źródło
Myślę, że możesz mieć błędy w wcięciu. Wiodące białe znaki są ważne w Pythonie. np .: walkables.add((b,a))linie.
Logic Knight
powinien już działać dobrze
Hit
1

Bunny, Java, 26.385

Uśredniłem wyniki serii od 1 do 1000 i pomnożyłem przez 5, aby uzyskać mój wynik. Jestem prawie pewien, że odpowiada to średniemu wynikowi we wszystkich możliwych grach ze standardowymi opcjami.

Wyniki

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));
        }
    }

}

Najlepszy testowany przebieg ma seed 1000. Oto jego GIF:

wprowadź opis zdjęcia tutaj

Numer jeden
źródło