Horrorfilmsuchparty

21

Inhalt : Jimmy fehlt; wir müssen ihn finden. Wir sollten uns trennen.

Handlungswechsel : Jimmy ist bereits tot.

Aber unsere Darsteller wissen das nicht, deshalb müssen sie sowieso die ganze Gegend durchsuchen. Es gibt ein Gitter von N Spalten × M Zeilen (1 <= M, N <= 256) von Zellen, die entweder als "S" für den Startpunkt markiert sind. für Freiflächen oder "#" für ein Hindernis. Das ist die Karte .

Es gibt 0 <= p <= 26 Costars , 0 <= q <= 26 Extras und 1 Stern . Jeder ist anfangs in der mit S markierten Zelle.

Die Regeln

Jede Person hat einen der folgenden Sichtradien:

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

Der Stern wird mit "@" gekennzeichnet, die Costars mit Großbuchstaben, beginnend mit "A", und die Extras mit Kleinbuchstaben, beginnend mit "a". Der den Startpunkt umgebende Visierradius ist zunächst bereits als gesucht markiert. Wenn dies den gesamten offenen Bereich der Karte ausmacht, endet das Spiel. Jede Runde in der folgenden Reihenfolge :

  1. Jede Person zieht gleichzeitig einen König (entweder stillstehend oder in eine der 8 Nachbarzellen).
  2. Alle Zellen im Sichtradius um jede Person werden als durchsucht gezählt.
  3. Wenn ein Costar niemanden sehen kann, stirbt sie. Wenn ein Extra weder einen Costar, den Stern noch mindestens zwei weitere Extras sehen kann, stirbt er. Diese geschehen gleichzeitig - das heißt, es kann keine Kettenreaktion von Todesfällen in einer einzigen Runde geben; Die oben genannten Bedingungen werden überprüft, und jeder, der sterben wird, stirbt sofort.
  4. Wenn der gesamte offene Bereich auf der Karte durchsucht wurde, ist die Suche beendet.

Anmerkungen

An jedem Punkt können sich mehrere Personen auf demselben Feld befinden und diese Personen können sich sehen.

Hindernisse behindern niemals das Sehen, nur die Bewegung; Menschen können sich über die, äh ... Lava hinweg sehen?

Die offenen Felder auf der Karte werden garantiert durch Königszüge verbunden.

Das anfängliche "S" wird auch als offener Raum und nicht als Hindernis angesehen.

Jeder Zug eines Königs, der auf einem offenen Feld landet, ist gültig. Zum Beispiel ist der folgende Schritt zulässig:

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

Eingang

Die Eingabe erfolgt im Format

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

Beispieleingaben:

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

und

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

p und q sind die Anzahl der Costars bzw. Extras.

Ausgabe

Die Ausgabe sollte für jede Runde die Bewegungen sein, die durchgeführt werden, wobei die Richtungen durch angegeben sind

789
456
123

Die Reihenfolge der Züge spielt keine Rolle, da sie alle gleichzeitig ausgeführt werden. Es ist in Ordnung, einen Zug für eine Person nicht aufzulisten, und dies entspricht dem Bewegen in Richtung 5. Züge sollten im folgenden Format aufgeführt werden:

@9 A2 a2 B7.

"." bezeichnet das Ende deiner Züge für eine Runde.

Nachdem die Karte durchsucht wurde, sollte die letzte Ausgabezeile aus drei Ganzzahlen bestehen, die durch Leerzeichen getrennt sind: Die Anzahl der Runden, die Sie zum Durchsuchen des Brettes benötigt haben, die Anzahl der lebenden Kosten und die Anzahl der lebenden Extras. Für die erste Beispieleingabe

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

Folgendes ist eine gültige Ausgabe:

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

Ein letzter Hinweis: Der Stern kann nicht sterben und der offene Bereich auf der Karte ist garantiert verbunden, sodass die Suche letztendlich immer erfolgreich sein wird.

Wertung

Ihre Punktzahl ist die Gesamtanzahl der Umdrehungen, die in einer Reihe von Benchmark-Tests durchgeführt wurden. Sie können gerne Ihren eigenen Testfall zusammen mit Ihrer Antwort einreichen. Die Summe der Anzahl der Lebenshaltungskosten über dem Benchmark-Set wird als Auslöser verwendet, und falls es immer noch ein Unentschieden gibt, wird die Summe der Anzahl der Lebenshaltungskosten verwendet.

Test Set und Controller

Derzeit sind 5 Karten unter https://github.com/Tudwell/HorrorMovieSearchParty/ online . Jeder, der eine Antwort einreicht, kann auch einen Testfall einreichen, den ich aus irgendeinem Grund ablehnen kann (wenn ich Ihre Karte aus irgendeinem Grund ablehne, können Sie einen anderen einreichen). Diese werden nach meinem Ermessen dem Testset hinzugefügt.

Ein Python- Controller (getestet in 2.7.5) wird auf github als controller.py bereitgestellt . Ein zweiter Controller dort, controller_disp.py , ist identisch mit der Ausnahme, dass er während der Suche eine grafische Ausgabe zeigt (erfordert Pygame-Bibliothek).

Grafische Controller-Ausgabe

Verwendung :

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

Dh:

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

Der Controller hat die Ausgabe des Formulars (an die Standardeingabe Ihres Programms) gesendet

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

Dies ist die Zugnummer (Zug 1 ist, bevor Sie umgezogen sind), eine mit '.'- abgeschlossene Liste aller Akteure und ihrer x, y-Koordinaten (das linke obere Zeichen ist (0,0)), eine Darstellung des gesamten Brett und eine Linie mit 40 '. Es wartet dann auf die Eingabe (von der Standardausgabe Ihres Programms ) des Formulars

@9 A2 B7.

Dies ist das oben angegebene Ausgabeformat. Die Steuerung gibt ein 'o' für den durchsuchten freien Raum aus, '.' nach freiem Raum, der nicht durchsucht wurde, und '#' nach Hindernissen. Es enthält nur lebende Personen in der Liste der Personen und ihrer Koordinaten und verfolgt alle Spielregeln. Der Controller wird beendet, wenn ein unzulässiger Zug versucht wird. Wenn die Bewegungen für eine gegebene Runde die Suche beenden, ist die Ausgabe nicht wie oben; stattdessen ist es von der Form

Finished in 4 turns
4 1 0

"4 1 0" bedeutet hier 4 Runden insgesamt, 1 lebender Costar und 0 lebende Extras. Sie müssen den Controller nicht verwenden. Fühlen Sie sich frei, es zu verwenden oder für Ihren eigenen Eintrag zu ändern. Wenn Sie es verwenden möchten und auf Probleme stoßen, lassen Sie es mich wissen.

Vielen Dank an @githubphagocyte für die Hilfe beim Schreiben des Controllers.

Bearbeiten: Für einen zufälligen Eintrag können Sie einen beliebigen Lauf auf einer bestimmten Karte als Punktzahl für diese Karte auswählen. Beachten Sie, dass Sie aufgrund der Bewertungsanforderungen immer die wenigsten Runden, dann die wenigsten toten Costars und dann die wenigsten toten Extras für jede Karte auswählen sollten.

Eric Tressler
quelle
7
Die zweite Zeile sollte zwischen den Spoiler-Tags stehen!
Averroes

Antworten:

8

Ruby, Safety First + BFS + Randomness, Punktzahl ≤ 1458

Ich bin mir nicht sicher, wie Sie zufällige Einsendungen bewerten werden. Wenn alle Antworten deterministisch sein müssen, lassen Sie es mich wissen und ich werde einen Samen aussuchen oder die Zufälligkeit insgesamt loswerden.

Einige Merkmale und Mängel dieser Lösung:

  • Niemand stirbt jemals. Zu Beginn gruppiere ich alle Schauspieler so, dass jeder in Sicherheit ist. Die Charaktere in jeder dieser Gruppen bewegen sich im Einklang. Das ist gut, um alle am Leben zu erhalten, aber nicht optimal.
  • Jede der Gruppen sucht über die Breitensuche nach dem nächsten unerforschten Punkt auf der Karte und nimmt den ersten Zug dieses Suchzweigs vor. Wenn es einen Zusammenhang zwischen mehreren optimalen Zügen gibt, wird ein zufälliger ausgewählt. Dies soll sicherstellen, dass nicht alle Gruppen immer in die gleiche Richtung gehen.
  • Dieses Programm kennt das Sichtfeld nicht. Es versucht tatsächlich, sich in jede unerforschte Zelle zu bewegen. Wenn Sie dies berücksichtigen, kann die Leistung erheblich gesteigert werden, da Sie dann auch die Qualität jeder Bewegung anhand der Anzahl der Zellen quantifizieren können, die sie aufdeckt.
  • Das Programm verfolgt keine Informationen zwischen den Runden (mit Ausnahme der Akteursgruppen). Das macht es auf den größeren Testfällen ziemlich langsam. map5.txtdauert zwischen 1 und 13 Minuten.

Einige Ergebnisse

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

Hier ist der Code:

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)

Dort sind einige Debug-Ausgaben auskommentiert. Besonders der if group['@']Block ist sehr interessant, da er eine Visualisierung der BFS-Daten ausgibt.

Bearbeiten: Deutliche Geschwindigkeitsverbesserung, indem das BFS gestoppt wird, wenn bereits ein besserer Zug gefunden wurde (was eigentlich der Grund war, warum BFS verwendet wurde).

Martin Ender
quelle
Ist zu erwarten, dass Ihr Eintrag immer Zugriff auf die Kartendatei hat?
Sparr
Ja; Die Kartendatei ist immer vorhanden. Wenn Sie den Controller verwenden, erhalten Sie in jeder Runde eine aktualisierte Kopie.
Eric Tressler