로봇이 텔레 포터에 도달하도록 도와주세요


17

업데이트 : 시작하기 위해 Python 프레임 워크가 추가되었습니다.

우주 정거장은 크러셔 로봇에 의해 추월되었습니다. 역이 스스로 파괴되기 전에 "토끼"라고 불리는 우리의 값 비싸고 부서지기 쉬운 기술 로봇을 출구 텔레 포터로 보내야하지만 크러셔 로봇은 복도를 순찰하고 있습니다.

귀하의 프로그램은 ASCII 맵을 제공받으며, 각 턴에는 크러셔 봇이 어디에 있는지 그리고 현재 토끼가 있는지 알려줍니다. 그런 다음 분쇄기 로봇을 피하면서 토끼를 출구 텔레 포터쪽으로 옮깁니다.

데모 애니메이션

실행

다음을 사용하여 Python 2 컨트롤러를 실행하십시오.

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

시드는 크러셔 및 프로그램 PRNG에 사용되는 작은 정수이므로 실행을 반복 할 수 있습니다. 사용 된 실제 시드에 관계없이 프로그램이 일관되게 수행되어야합니다. 시드가 0이면 컨트롤러는 각 실행에 대해 임의의 시드를 사용합니다.

컨트롤러는 맵 텍스트 파일 이름과 시드를 인수로 사용하여 프로그램을 실행합니다. 예 :

perl wandomwabbits.pl large.map 322

프로그램이 PRNG를 사용하는 경우 주어진 시드로 초기화해야합니다. 그런 다음 컨트롤러는 STDIN을 통해 프로그램 업데이트를 보내고 STDOUT을 통해 토끼 움직임을 읽습니다.

컨트롤러는 매번 3 라인을 출력합니다 :

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

그런 다음 프로그램이 한 줄을 출력하기를 기다립니다.

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

업데이트 : 컨트롤러가 첫 줄을 보내기 전에 프로그램을 초기화하는 데 2 ​​초가 걸립니다.

컨트롤러 래빗 위치 입력 후 이동에 응답하는 데 프로그램이 0.5 초 이상 걸리면 컨트롤러가 종료됩니다.

격자에 토끼가 없으면 토끼 줄에 값이 없으며 프로그램에서 "이동"문자열 줄을 출력해야합니다.

매번 프로그램 출력 스트림을 플러시해야합니다. 그렇지 않으면 컨트롤러가 멈출 수 있습니다.

프로그램 입력 :

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

프로그램 출력 :

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

컨트롤러 로직

각 턴의 논리 :

  • 좌회전이 0이면 점수를 인쇄하고 종료합니다.
  • 빈 시작 셀마다 분쇄기가 보이지 않으면 토끼를 추가하십시오.
  • 각 파쇄기마다 이동 방향을 결정하십시오 (아래 참조).
  • 각 분쇄기마다 가능하면 움직입니다.
  • 분쇄기가 토끼 위치에 있으면 토끼를 제거하십시오.
  • 출력 좌회전, 파쇄기 동작 및 토끼 위치 프로그래밍.
  • 프로그램에서 토끼 이동 요청을 읽습니다.
  • 토끼가 없거나 움직일 수 없으면 건너 뛰십시오.
  • 토끼의 각각의 새로운 위치를 계획하십시오.
  • 토끼가 분쇄기에 부딪 치면 토끼가 파괴됩니다.
  • 토끼가 텔레 포터 출구에 있으면 토끼를 제거하고 점수를 올립니다.
  • 토끼가 충돌하면 모두 파괴됩니다.

각 분쇄기는 항상 방향 방향 (NSEW 중 하나)을 갖습니다. 크러셔는 매번이 탐색 논리를 따릅니다.

  • 하나 이상의 토끼가 4 개의 직교 방향 중 하나에서 보이는 경우, 가장 가까운 토끼 중 하나로 방향을 변경하십시오. 크러셔는 다른 크러셔를 지나칠 수 없습니다.
  • 그렇지 않으면 가능한 경우 열린 앞으로, 왼쪽, 오른쪽 옵션 중에서 무작위로 선택하십시오.
  • 그렇지 않으면 전방, 좌측 우측에 장애물 (벽 또는 기타 분쇄기)이있는 경우 방향을 후방으로 설정하십시오.

그런 다음 각 분쇄기마다 :

  • 새로운 파쇄기 방향으로 장애물이 없으면 움직여서 분쇄하십시오.

지도 기호

맵은 ASCII 문자의 사각형 격자입니다. 맵은 벽 #, 복도 공간 , 토끼 시작 위치 s, 출구 텔레 포터 e및 크러셔 시작 위치로 구성 c됩니다. 왼쪽 상단은 위치 (0,0)입니다.

작은지도

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

테스트 맵

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

큰지도 실행 예

큰 데모

점수

프로그램을 평가하려면 테스트 맵, 500 턴, 5 런 및 시드 0으로 컨트롤러를 실행하십시오. 점수는 스테이션에서 안전으로 성공적으로 텔레포트 된 총 토끼 수입니다. 동점 일 경우 가장 많은 표를 얻은 답이 이길 것입니다.

답변에는 출품작 이름, 사용 언어 및 점수가 포함 된 제목이 포함되어야합니다. 답변 본문에 다른 사람들이 달리기를 반복 할 수 있도록 시드 번호가 포함 된 컨트롤러 점수 출력을 포함하십시오. 예를 들면 다음과 같습니다.

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

표준 라이브러리 및 자유롭게 사용 가능한 라이브러리를 사용할 수 있지만 표준 허점은 금지되어 있습니다. 주어진 시드, 회전 수, 맵 기능 세트 또는 기타 매개 변수에 대해 프로그램을 최적화해서는 안됩니다. 본인은이 규칙을 위반 한 것으로 의심 될 경우지도, 회전 횟수 및 시드를 변경할 권리가 있습니다.

컨트롤러 코드

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

컨트롤러는 디렉토리에 런의 텍스트 로그 run.log와 일련의 이미지를 anim만듭니다. Python 설치에서 PIL 이미지 라이브러리를 찾을 수 없으면 (베개로 다운로드) 이미지가 생성되지 않습니다. ImageMagick으로 이미지 시리즈에 애니메이션을 적용했습니다. 예 :

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

답변이 포함 된 재미있는 애니메이션이나 이미지를 게시 할 수 있습니다.

컨트롤러 프로그램의 처음 몇 줄을 설정 GRIDLOG = False및 / 또는 설정하여 이러한 기능을 끄고 컨트롤러 속도를 높일 수 있습니다 MKIMAGE = False.

제안 된 파이썬 프레임 워크

시작을 돕기 위해 다음은 Python의 프레임 워크입니다. 첫 번째 단계는 맵 파일을 읽고 엑시트의 경로를 찾는 것입니다. 매 차례마다 분쇄기의 위치를 ​​저장하는 코드와 토끼를 움직일 위치를 결정하는 코드가 있어야합니다. 가장 간단한 전략은 분쇄기를 무시하고 출구로 토끼를 옮기는 것입니다. 일부 토끼는 통과 할 수 있습니다.

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

정확히 동일한 RNG로 컨트롤러를 시뮬레이션 할 수 있습니까? 이렇게하면 분쇄기가 효과적으로 결정론적일 수 있으므로 행동을 예측하고 피할 수 있습니다. 지옥은, 당신은 아마 하나 만들 수 또는 unbothered 토끼의 도로 설정하는 동안 몇 가지 '미끼 토끼'바쁜 파쇄기를 유지하기 위해
orlp

RNG를 시뮬레이션하거나 특정 시드에 대해 RNG를 기록 할 수 없습니다. 파쇄기는 결정하지 수 있도록 설계, 그래서 이것은하는 전략 만들 수있는 코드 도전하는 가능성 을 피할합니다. '미끼 토끼'아이디어는 확실히 괜찮습니다. 나는 희생 토끼와 관련된 몇 가지 전략을 기대하고 있습니다.
논리 기사

시드가 0이면 각 런이 임의의 시드를 사용하지 않습니까?
TheNumberOne

예. 컨트롤러 시드가 0이면 각 실행에 대해 임의의 시드를 사용하고 테스트 프로그램에 발행합니다. 이 시드는 실행 점수와 함께보고됩니다. 이 시드를 컨트롤러에 다시 공급하면 정확한 런 (및 스코어)을 확인할 수 있습니다. 약간 복잡하지만 실행 복제 (결정적)를 활성화하고 컨트롤러 및 테스트 프로그램 동작에서 임의성을 허용하는 가장 좋은 방법이었습니다.
논리 기사

답변:


2

크레이지, 파이썬 45

나는 무작위 시드로 25 회 실행을했는데, 내 컴퓨터는 1000에 갈 정도로 빠르지 않습니다 (누군가 점수를 수정하려는 경우) 파이썬의 첫 번째 프로그램, 그것은 나를 위해 디버그하는 고통이었습니다. 또한 잘 사용했는지 모르겠습니다.

출구에서 너비 우선 알고리즘을 사용합니다. 하나는 크러셔를 고려하고 다른 하나는 제외합니다. 시간 초과가 많기 때문에 더 복잡한 무언가를 원치 않았습니다 (분쇄기 제거 등). 토끼는 길을 잘못 가고 싶어서 분쇄기가 근처에 있다면 미쳐 버린다.

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

평균 달리기 애니메이션


들여 쓰기에 오류가있을 수 있습니다. 선행 공백은 Python에서 중요합니다. 예 : walkables.add((b,a))선.
로직 나이트

지금은 잘 작동합니다
히트

1

버니, 자바, 26.385

나는 평균 점수를 1 점에서 1000 점으로 평균하고 5를 곱하여 점수를 얻었다. 나는 이것이 표준 옵션으로 가능한 모든 게임의 평균 점수와 동등한 것이라고 확신합니다.

점수

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

}

테스트 된 최고의 실행에는 seed 1000이 있습니다. GIF는 다음과 같습니다.

여기에 이미지 설명을 입력하십시오

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.