Festa de busca de filmes de terror

21

Sinopse : Jimmy está desaparecido; nós temos que encontrá-lo. Nós devemos nos separar.

Trama : Jimmy já está morto.

Mas, nosso elenco não sabe disso, então eles precisam pesquisar toda a área de qualquer maneira. Há uma grade de N colunas x M linhas (1 <= M, N <= 256) de células, marcadas como "S" para o ponto de partida "." para espaço aberto ou "#" para um obstáculo. Este é o mapa .

Existem 0 <= p <= 26 costars , 0 <= q <= 26 extras e 1 estrela . Todo mundo está inicialmente na célula marcada com S.

As regras

Cada pessoa tem um raio de visão mostrado abaixo:

 ...
.....
..@..
.....
 ...

A estrela é denotada por "@", os costars por letras maiúsculas, começando com "A", e os extras por letras minúsculas, começando por "a". Inicialmente, o raio da mira em torno do ponto inicial já está marcado como pesquisado. Se isso constitui todo o espaço aberto do mapa, o jogo termina. Cada turno, na seguinte ordem :

  1. Cada pessoa faz simultaneamente um rei se mover (parado ou movendo-se para uma das oito células vizinhas).
  2. Todas as células no raio de visão ao redor de cada pessoa são contadas como pesquisadas.
  3. Se um co-ator não pode ver mais ninguém, ela morre. Se um extra não pode ver um co-estrela, a estrela ou pelo menos dois outros extras, ele morre. Isso acontece simultaneamente - ou seja, não pode haver reação em cadeia das mortes em um único turno; as condições acima são verificadas e todo mundo que morre morre imediatamente.
  4. Se todo o espaço aberto no mapa tiver sido pesquisado, a pesquisa terminou.

Notas

Várias pessoas podem estar na mesma praça a qualquer momento, e elas podem se ver.

Os obstáculos nunca impedem a visão, apenas o movimento; as pessoas podem se ver através da lava?

Os espaços abertos no mapa são garantidos para serem conectados por movimentos de rei.

O "S" inicial também é considerado espaço aberto, e não um obstáculo.

Qualquer movimento do rei que cair em um espaço aberto é válido. Por exemplo, a seguinte jogada é legal:

....      ....
.@#. ---> ..#.
.#..      .#@.
....      ....

Entrada

A entrada estará no formato

N M p q
[N cols x M rows grid with characters ".", "#", and "S"]

Entradas de amostra:

6 5 0 0
......
......
..S...
......
......

e

9 9 1 1
S.......#
.......##
......##.
..#####..
...##....
...##....
...#.....
....#..#.
.........

p e q são os números de co-figurantes e extras, respectivamente.

Saída

A saída deve ser, para cada turno, os movimentos realizados, com as direções indicadas por

789
456
123

A ordem dos movimentos não importa, pois todos são encenados simultaneamente. Não listar uma jogada para uma pessoa é bom e é equivalente a movê-la na direção 5. As jogadas devem ser listadas no seguinte formato:

@9 A2 a2 B7.

"." indica o final de seus movimentos por um turno.

Depois que o mapa for pesquisado, a linha final de saída deverá ser três números inteiros, separados por espaços: o número de voltas que você levou para terminar de pesquisar no quadro, o número de personagens vivos e o número de extras vivos. Para o primeiro exemplo de entrada

6 5 0 0
......
......
..S...
......
......

o seguinte é saída válida:

@4.
@6.
@6.
@6.
4 0 0

Uma observação final: a estrela não pode morrer e é garantido que o espaço aberto no mapa esteja conectado, portanto a pesquisa sempre será bem-sucedida eventualmente.

Pontuação

Sua pontuação é o número total de turnos realizados em um conjunto de testes de benchmark; você pode enviar seu próprio caso de teste junto com sua resposta. A soma do número de coadjuvantes vivos sobre o conjunto de benchmarks será usada como desempate e, no caso de ainda haver empate, será usada a soma do número de extras vivos.

Conjunto de teste e controlador

Atualmente, 5 mapas estão online em https://github.com/Tudwell/HorrorMovieSearchParty/ . Qualquer pessoa que envie uma resposta também poderá enviar um caso de teste, que me reservo o direito de rejeitar por qualquer motivo (se eu rejeitar seu mapa por algum motivo, você poderá enviar outro). Estes serão adicionados ao conjunto de testes a meu critério.

Um controlador Python (testado no 2.7.5) é fornecido no github como controller.py . Um segundo controlador lá, controller_disp.py , é idêntico, exceto que mostra a saída gráfica durante a pesquisa (requer a biblioteca Pygame).

Saída gráfica do controlador

Uso :

python controller.py <map file> <your execution line>

Ou seja:

python controller.py map1.txt python solver.py map1.txt

O controlador produz uma saída (para o stdin do seu programa ) no formato

Turn 1
@:2,3 A:2,3 B:2,3.
##...##
#ooo..#
ooooo..
ooooo..
ooooo..
#ooo...
##.....
###....
----------------------------------------

Este é o número do turno (o turno 1 é antes de você se mudar), uma lista terminada em '.' De todos os atores e suas coordenadas x, y (o caractere superior esquerdo é (0,0)), uma representação de todo board e uma linha com 40 '-'s. Em seguida, aguarda a entrada (do stdout do seu programa ) do formulário

@9 A2 B7.

Este é o formato de saída especificado acima. O controlador gera um 'o' para o espaço aberto que foi pesquisado, '.' para espaço aberto que não foi pesquisado e '#' para obstáculos. Inclui apenas pessoas vivas em sua lista de pessoas e suas coordenadas e rastreia todas as regras do jogo. O controlador irá sair se uma tentativa ilegal for tentada. Se os movimentos para um determinado turno concluírem a pesquisa, a saída não será a mesma; em vez disso, é da forma

Finished in 4 turns
4 1 0

"4 1 0" aqui indica 4 turnos totais, 1 costar vivo e 0 extras vivos. Você não precisa usar o controlador; fique à vontade para usá-lo ou modificá-lo para sua própria entrada. Se você decidir usá-lo e encontrar problemas, entre em contato.

Agradeço ao @githubphagocyte por me ajudar a escrever o controlador.

Editar: para uma entrada aleatória, você pode escolher qualquer corrida em um mapa específico como sua pontuação para esse mapa. Observe que, devido aos requisitos de pontuação, você sempre deve escolher o menor número de turnos, o menor número de costars mortos e o menor número de extras mortos para cada mapa.

Eric Tressler
fonte
7
a segunda linha deve estar entre as tags de spoilers!
Averroes

Respostas:

8

Ruby, segurança em primeiro lugar + BFS + aleatoriedade, pontuação ≤ 1458

Não tenho certeza de como você receberá pontuações aleatórias. Se todas as respostas tiverem que ser determinísticas, avise-me e eu vou pegar uma semente ou me livrar completamente da aleatoriedade.

Alguns recursos e deficiências desta solução:

  • Ninguém nunca morre. No começo, agrupo todos os atores para que todos estejam seguros. Os personagens de cada um desses grupos se movem em uníssono. Isso é bom para manter todos vivos, mas não é otimamente eficiente.
  • Cada um dos grupos pesquisa o ponto inexplorado mais próximo no mapa por meio da pesquisa de amplitude inicial e executa o primeiro movimento desse ramo da pesquisa. Se houver empate entre vários movimentos ótimos, um aleatório é escolhido. Isso é para garantir que nem todos os grupos sempre sigam na mesma direção.
  • Este programa não conhece o campo de visão. Na verdade, ele tenta se mover para todas as células inexploradas. Levar isso em consideração pode aumentar consideravelmente o desempenho, pois você também pode quantificar a qualidade de cada movimento por quantas células ele descobrirá.
  • O programa não controla informações entre turnos (exceto os grupos de atores). Isso torna bastante lento nos casos de teste maiores. map5.txtleva entre 1 e 13 minutos para ser concluído.

Alguns resultados

Map     Min turns    Max turns
map1        46           86
map2        49          104
map3       332          417
map4       485          693
map5       546          887

Agora, aqui está o código:

start = Time.now

map_file = ARGV.shift
w=h=p=q=0
File::open(map_file, 'r') do |file|
    w,h,p,q = file.gets.split.map(&:to_i)
end

costars = p > 0 ? (1..p).map {|i| (i+64).chr} : []
extras = q > 0 ? (1..q).map {|i| (i+96).chr} : []
groups = []

costars.zip(extras).each do |costar, extra|
    break unless extra
    groups << (costar + extra)
    costars.delete(costar)
    extras.delete(extra)
end

costars.each_slice(2) {|c1, c2| groups << (c1 + (c2 || '@'))} unless costars.empty?
extras.each_slice(3) {|c1, c2, c3| groups << (c1 + (c2 || '') + (c3 || '@'))} unless extras.empty?
groups << '@' unless groups.join['@']

#$stderr.puts groups.inspect


directions = {
    1 => [-1, 1],
    2 => [ 0, 1],
    3 => [ 1, 1],
    4 => [-1, 0],
    5 => [ 0, 0],
    6 => [ 1, 0],
    7 => [-1,-1],
    8 => [ 0,-1],
    9 => [ 1,-1]
}

loop do
    break unless gets # slurp turn number
    coords = {}
    input = gets
    input.chop.chop.split.each{|s| actor, c = s.split(':'); coords[actor] = c.split(',').map(&:to_i)}
    #$stderr.puts input
    #$stderr.puts coords.inspect
    map = []
    h.times { map << gets.chomp }

    gets # slurp separator
    moves = groups.map do |group|
        x, y = coords[group[0]]
        distances = {[x,y] => 0}
        first_moves = {[x,y] => nil}
        nearest_goal = Float::INFINITY
        best_move = []
        active = [[x,y]]
        while !active.empty?
            coord = active.shift
            dist = distances[coord]
            first_move = first_moves[coord]
            next if dist >= nearest_goal
            [1,2,3,4,6,7,8,9].each do |move|
                dx, dy = directions[move]
                x, y = coord
                x += dx
                y += dy
                next if x < 0 || x >= w || y < 0 || y >= h || map[y][x] == '#'
                new_coord = [x,y]
                if !distances[new_coord]
                    distances[new_coord] = dist + 1
                    first_moves[new_coord] = first_move || move
                    active << new_coord if map[y][x] == 'o'
                end

                if dist < distances[new_coord]
                    distances[new_coord] = dist + 1
                    first_moves[new_coord] = first_move || move
                end

                if map[y][x] == '.'
                    if dist + 1 < nearest_goal
                        nearest_goal = dist + 1
                        best_move = [first_moves[new_coord]]
                    elsif dist + 1 == nearest_goal
                        best_move << first_moves[new_coord]
                    end
                end
            end
        end

        #if group['@']
        #    distances.each{|k,v|x,y=k;map[y][x]=(v%36).to_s(36)}
        #    $stderr.puts map
        #end

        dir = best_move.sample
        group.chars.map {|actor| actor + dir.to_s}
    end * ' '
    #$stderr.puts moves
    puts moves
    $stdout.flush
end

#$stderr.puts(Time.now - start)

Existem algumas saídas de depuração comentadas lá. Especialmente, o if group['@']bloco é bastante interessante porque imprime uma visualização dos dados BFS.

Edit: Melhoria significativa da velocidade, interrompendo o BFS se uma mudança melhor já foi encontrada (que era o ponto de usar o BFS em primeiro lugar).

Martin Ender
fonte
é seguro esperar que sua entrada sempre tenha acesso ao arquivo de mapa?
Sparr
Sim; o arquivo de mapa está sempre lá e, se você usar o controlador, você recebe uma cópia atualizada a cada turno.
22814 Eric Tressler