Party di ricerca di film horror


21

Trama : manca Jimmy; dobbiamo trovarlo. Dovremmo dividerci.

Trama : Jimmy è già morto.

Ma il nostro cast non lo sa, quindi devono comunque cercare in tutta l'area. C'è una N colonne x M righe (1 <= M, N <= 256) griglia di celle, entrambe contrassegnate come "S" per il punto iniziale, "." per spazio aperto o "#" per un ostacolo. Questa è la mappa .

Ci sono 0 <= p <= 26 costar , 0 <= q <= 26 extra e 1 stella . Ognuno è inizialmente nella cella contrassegnata con S.

Le regole

Ogni persona ha un raggio visivo mostrato di seguito:

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

La stella è indicata con "@", le costar con lettere maiuscole, che iniziano con "A", e gli extra con lettere minuscole, che iniziano con "a". Inizialmente, il raggio visivo che circonda il punto iniziale è già contrassegnato come cercato. Se questo costituisce l'intero spazio aperto della mappa, il gioco termina. Ogni turno, nel seguente ordine :

  1. Ogni persona fa simultaneamente una mossa del re (fermandosi o spostandosi su una delle 8 celle vicine).
  2. Tutte le celle nel raggio visivo intorno a ogni persona vengono contate come cercate.
  3. Se una costar non può vedere nessun altro, muore. Se un extra non riesce a vedere né una costar, la stella o almeno altri 2 extra, muore. Questi accadono simultaneamente - cioè, non può esserci alcuna reazione a catena di morti in un singolo turno; le condizioni di cui sopra vengono verificate e tutti coloro che stanno per morire muoiono immediatamente.
  4. Se è stato cercato tutto lo spazio aperto sulla mappa, la ricerca è terminata.

Gli appunti

Più persone possono trovarsi sullo stesso quadrato in qualsiasi momento e queste persone possono vedersi.

Gli ostacoli non ostacolano mai la vista, solo il movimento; le persone possono vedersi attraverso, ehm ... lava?

Gli spazi aperti sulla mappa sono garantiti per essere collegati da mosse re.

Anche la "S" iniziale è considerata come spazio aperto, piuttosto che un ostacolo.

Qualsiasi mossa del re che atterra su uno spazio aperto è valida. Ad esempio, la seguente mossa è legale:

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

Ingresso

L'input sarà nel formato

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

Ingressi campione:

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

e

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

p e q sono il numero di costar ed extra, rispettivamente.

Produzione

L'uscita dovrebbe essere, per ogni giro, le mosse che vengono effettuate, con le direzioni indicate da

789
456
123

L'ordine delle mosse non ha importanza, poiché sono tutte messe in atto simultaneamente. Non elencare una mossa per una persona va bene ed equivale a muoverla nella direzione 5. Le mosse devono essere elencate nel seguente formato:

@9 A2 a2 B7.

"" indica la fine delle tue mosse per un turno.

Dopo che la mappa è stata cercata, la riga finale di output dovrebbe essere tre numeri interi, separati da spazi: il numero di turni necessari per completare la ricerca nel tabellone, il numero di costar viventi e il numero di extra viventi. Per il primo esempio di input

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

il seguente è un output valido:

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

Un'ultima nota: la stella non può morire e lo spazio aperto sulla mappa è garantito per essere collegato, quindi alla fine la ricerca avrà sempre successo.

punteggio

Il tuo punteggio è il numero totale di turni effettuati su una serie di test di riferimento; sei invitato a inviare il tuo test case insieme alla tua risposta. La somma del numero di costar viventi rispetto al set di riferimento verrà utilizzata come pareggio e, nel caso in cui vi sia ancora un pareggio, verrà utilizzata la somma del numero di extra viventi.

Set di test e controller

Attualmente, 5 mappe sono online su https://github.com/Tudwell/HorrorMovieSearchParty/ . Chiunque presenti una risposta può anche inviare un caso di prova, che mi riservo il diritto di rifiutare per qualsiasi motivo (se rifiuto la tua mappa per qualche motivo, puoi inviarne un'altra). Questi saranno aggiunti al set di test a mia discrezione.

Un controller Python (testato in 2.7.5) è fornito su github come controller.py . Un secondo controller lì, controller_disp.py , è identico, tranne per il fatto che mostra un output grafico durante la ricerca (richiede la libreria Pygame).

Uscita del controller grafico

Utilizzo :

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

Vale a dire:

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

Il controller ha emesso (allo stdin del tuo programma ) il modulo

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

Questo è il numero del turno (il turno 1 è prima che tu ti sia spostato), un elenco '.' Terminato di tutti gli attori e le loro coordinate x, y (il carattere in alto a sinistra è (0,0)), una rappresentazione dell'intero tavola e una linea con 40 '. Quindi attende l'input (dallo stdout del programma ) del modulo

@9 A2 B7.

Questo è il formato di output sopra specificato. Il controller emette una "o" per lo spazio aperto che è stato cercato "." per lo spazio aperto che non è stato cercato e '#' per gli ostacoli. Include solo le persone viventi nel suo elenco di persone e le loro coordinate e tiene traccia di tutte le regole del gioco. Il controller uscirà se si tenta di effettuare una mossa illegale. Se le mosse per un dato turno terminano la ricerca, l'output non è come sopra; invece è della forma

Finished in 4 turns
4 1 0

"4 1 0" indica qui 4 turni totali, 1 costar vivente e 0 extra viventi. Non è necessario utilizzare il controller; sentiti libero di usarlo o modificarlo per la tua voce. Se decidi di utilizzarlo e riscontri problemi, fammi sapere.

Grazie a @githubphagocyte per avermi aiutato a scrivere il controller.

Modifica: per una voce casuale, puoi scegliere qualsiasi corsa su una particolare mappa come punteggio per quella mappa. Nota che, a causa dei requisiti di punteggio, dovresti sempre scegliere il minor numero di curve, quindi il minor numero di costar morti, quindi il minor numero di extra morti per ogni mappa.


7
la seconda riga dovrebbe essere tra i tag spoiler!
Averroè,

Risposte:


8

Rubino, sicurezza prima + BFS + casualità, punteggio ≤ 1458

Non sono sicuro di come otterrai punteggi casuali. Se tutte le risposte devono essere deterministiche, fammi sapere e sceglierò un seme o mi libererò del tutto dalla casualità.

Alcune caratteristiche e carenze di questa soluzione:

  • Nessuno muore mai. All'inizio raggruppo tutti gli attori in modo tale che tutti siano al sicuro. I personaggi di ciascuno di questi gruppi si muovono all'unisono. È buono per mantenere tutti in vita ma non in modo ottimale efficiente.
  • Ognuno dei gruppi cerca il punto inesplorato più vicino sulla mappa tramite l'ampiezza della ricerca e prende la prima mossa di quel ramo della ricerca. Se c'è un pareggio tra più mosse ottimali, ne viene scelta una casuale. Questo per garantire che non tutti i gruppi procedano sempre nella stessa direzione.
  • Questo programma non conosce il campo visivo. In realtà cerca di spostarsi in ogni cella inesplorata. Tenendo conto di ciò, si potrebbe aumentare considerevolmente le prestazioni, da allora si potrebbe anche quantificare la qualità di ogni mossa in base a quante celle scoprirà.
  • Il programma non tiene traccia delle informazioni tra i turni (tranne i gruppi di attori). Ciò rende piuttosto lento nei casi di test più grandi. map5.txtil completamento richiede tra 1 e 13 minuti.

Alcuni risultati

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

Ora ecco il codice:

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)

Ci sono alcuni output di debug commentati. Soprattutto il if group['@']blocco è piuttosto interessante perché stampa una visualizzazione dei dati BFS.

Modifica: Significativo miglioramento della velocità, interrompendo il BFS se è già stata trovata una mossa migliore (che era in un certo senso il punto di utilizzare BFS in primo luogo).


è sicuro aspettarsi che la tua voce abbia sempre accesso al file della mappa?
Sparr,

Sì; il file della mappa è sempre presente e se si utilizza il controller, si ottiene una copia aggiornata di ogni turno.
Eric Tressler,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.