Вечеринка по поиску фильмов ужасов

21

Сюжет : Джимми пропал; мы должны найти его. Мы должны расстаться.

Поворот сюжета : Джимми уже мертв.

Но наш актерский состав этого не знает, поэтому им все равно нужно искать всю область. Существует N столбцов x M рядов (1 <= M, N <= 256) сеток ячеек, либо помеченных как «S» для начальной точки, «.» для открытого пространства, или "#" для препятствия. Это карта .

Есть 0 <= p <= 26 костюмов , 0 <= q <= 26 дополнений и 1 звезда . Каждый изначально находится в клетке, помеченной S.

Правила

У каждого человека радиус прицеливания показан ниже:

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

Звезда обозначается буквой «@», костары - заглавными буквами, начиная с «А», а дополнительные - строчными буквами, начиная с «а». Первоначально радиус прицела, окружающий начальную точку, уже помечен как искомый. Если это составляет все открытое пространство карты, игра заканчивается. Каждый ход в следующем порядке :

  1. Каждый человек одновременно делает ход короля (либо стоит на месте, либо переходит в одну из 8 соседних клеток).
  2. Все ячейки в радиусе обзора вокруг каждого человека считаются найденными.
  3. Если коллега больше никого не видит, она умирает. Если дополнительный не может видеть ни Costar, звезду, или по крайней мере 2 других дополнительных, он умирает. Это происходит одновременно, то есть не может быть цепной реакции смертей за один ход; вышеуказанные условия проверены, и каждый, кто умрет, умирает сразу.
  4. Если все открытое пространство на карте было найдено, поиск окончен.

Заметки

Несколько человек могут находиться на одной площади в любой точке, и эти люди могут видеть друг друга.

Препятствия никогда не мешают зрению, только движению; люди могут видеть друг друга через ... лаву?

Открытые пространства на карте гарантированно связаны королевскими ходами.

Начальная буква «S» также считается открытым пространством, а не препятствием.

Любой ход короля, который приземляется на открытом пространстве, действителен. Например, следующий ход является законным:

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

вход

Ввод будет в формате

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

Примеры входных данных:

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

а также

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

p и q - количество костюмов и дополнений соответственно.

Выход

Выходные данные должны быть для каждого хода выполненными с указанием направления

789
456
123

Порядок ходов не имеет значения, так как все они принимаются одновременно. Недопустимо перечислять ход для человека, и это равносильно перемещению его в направлении 5. Движения должны быть перечислены в следующем формате:

@9 A2 a2 B7.

"" обозначает конец ваших ходов за ход.

После того, как на карте был произведен поиск, последняя строка выходных данных должна состоять из трех целых чисел, разделенных пробелами: количество ходов, которое потребовалось вам, чтобы завершить поиск на доске, количество живых существ и количество живых дополнений. Для первого примера ввода

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

следующий действительный вывод:

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

Последнее замечание: звезда не может умереть, и открытое пространство на карте гарантированно будет связано, поэтому поиск всегда будет успешным.

счет

Ваша оценка - это общее количество ходов, выполненных за набор тестов; Вы можете отправить свой тестовый пример вместе со своим ответом. Сумма количества живых карнавалов в наборе эталонов будет использоваться в качестве тай-брейка, а в случае, если все еще есть ничья, будет использоваться сумма количества живущих статистов.

Тестовый набор и контроллер

В настоящее время 5 карт онлайн на https://github.com/Tudwell/HorrorMovieSearchParty/ . Любой, кто отправляет ответ, может также представить тестовый пример, который я оставляю за собой право отклонить по любой причине (если я отклоню вашу карту по какой-либо причине, вы можете отправить другой). Они будут добавлены в набор тестов по моему усмотрению.

Контроллер Python (протестирован в 2.7.5) предоставляется на github как controller.py . Второй контроллер, controller_disp.py , идентичен, за исключением того, что он показывает графический вывод во время поиска (требуется библиотека Pygame).

Графический контроллер вывода

Использование :

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

То есть:

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

Контроллер имеет вывод (в stdin вашей программы ) вида

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

Это номер хода (ход 1 - до того, как вы переместились), список всех актеров с окончанием «.» И их координаты x, y (верхний левый символ (0,0)), представление всего доска и линия с 40-х годов. Затем он ожидает ввода (от вашей программы на стандартный вывод ) вида

@9 A2 B7.

Это формат вывода, указанный выше. Контроллер выводит «o» для открытого пространства, в котором был произведен поиск, «.» для открытого пространства, которое не было обыскано, и '#' для препятствий. В список людей и их координаты входят только живые люди, а также отслеживаются все правила игры. Контроллер выйдет, если будет предпринята попытка незаконного перемещения. Если ходы для данного хода завершают поиск, результат не такой, как указано выше; вместо этого он имеет форму

Finished in 4 turns
4 1 0

«4 1 0» здесь обозначает 4 полных хода, 1 жилой костюм и 0 живых дополнений. Вам не нужно использовать контроллер; не стесняйтесь использовать это или изменить это для своей собственной записи. Если вы решите использовать его и столкнетесь с проблемами, дайте мне знать.

Спасибо @githubphagocyte за помощь в написании контроллера.

Изменить: Для рандомизированной записи, вы можете выбрать любой прогон на конкретной карте в качестве вашей оценки для этой карты. Обратите внимание, что из-за требований к оценке вы всегда должны выбирать наименьшее количество ходов, затем наименьшее количество мертвых костаров, а затем наименьшее количество мертвых дополнений для каждой карты.

Эрик Тресслер
источник
7
вторая строка должна быть между тегами спойлеров!
Аверроэс

Ответы:

8

Рубин, Безопасность прежде всего + BFS + Случайность, Счет ≤ 1458

Я не уверен, как вы будете забивать случайные представления. Если все ответы должны быть детерминированными, дайте мне знать, и я соберу семя или полностью избавлюсь от случайности.

Некоторые особенности и недостатки этого решения:

  • Никто никогда не умирает. В начале я группирую всех актеров так, чтобы все были в безопасности. Персонажи в каждой из этих групп движутся в унисон. Это хорошо для поддержания жизни всех, но не оптимально эффективно.
  • Каждая из групп ищет ближайшую неисследованную точку на карте с помощью поиска в ширину и делает первый ход этой ветви поиска. Если есть связь между несколькими оптимальными ходами, выбирается случайный. Это сделано для того, чтобы не все группы всегда двигались в одном направлении.
  • Эта программа не знает о поле зрения. На самом деле он пытается переместиться в каждую неизведанную клетку. Принимая это во внимание, вы можете значительно повысить производительность, так как тогда вы также можете количественно оценить качество каждого движения по количеству ячеек, которые оно обнаружит.
  • Программа не отслеживает информацию между поворотами (кроме групп актеров). Это делает его довольно медленным в больших тестовых случаях. map5.txtзанимает от 1 до 13 минут.

Некоторые результаты

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

Теперь вот код:

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)

Там есть несколько закомментированных выводов отладки. Особенно if group['@']интересен блок, потому что он печатает визуализацию данных BFS.

Изменить: Значительное улучшение скорости, останавливая BFS, если лучший ход уже был найден (что было своего рода смысл использования BFS в первую очередь).

Мартин Эндер
источник
безопасно ли ожидать, что ваша запись всегда будет иметь доступ к файлу карты?
Спарр
Да; файл карты всегда там, и если вы используете контроллер, вы получаете обновленную копию его каждый ход.
Эрик Тресслер