Fête de recherche de films d'horreur

21

Résumé : Jimmy est manquant; nous devons le trouver. Nous devons nous séparer.

Intrigue : Jimmy est déjà mort.

Mais, notre casting ne le sait pas, ils doivent donc fouiller toute la zone de toute façon. Il y a N colonnes x M lignes (1 <= M, N <= 256) grille de cellules, soit marquées "S" pour le point de départ, "." pour un espace ouvert, ou "#" pour un obstacle. Voici la carte .

Il y a 0 <= p <= 26 costars , 0 <= q <= 26 figurants et 1 étoile . Tout le monde est initialement dans la cellule marquée S.

Les règles

Chaque personne a un rayon de vision indiqué ci-dessous:

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

L'étoile est désignée par «@», les costars par des majuscules, commençant par «A», et les extras par des lettres minuscules, commençant par «a». Initialement, le rayon de visée entourant le point de départ est déjà marqué comme recherché. Si cela constitue tout l'espace ouvert de la carte, la partie se termine. Chaque tour, dans l'ordre suivant :

  1. Chaque personne fait simultanément un mouvement de roi (soit en restant immobile, soit en se déplaçant vers l'une des 8 cellules voisines).
  2. Toutes les cellules dans le rayon de vision autour de chaque personne sont comptées comme fouillées.
  3. Si une costar ne peut voir personne d'autre, elle meurt. Si un extra ne peut pas voir un costar, l'étoile ou au moins 2 autres figurants, il meurt. Celles-ci se produisent simultanément - c'est-à-dire qu'il ne peut y avoir de réaction en chaîne de décès sur un seul tour; les conditions ci-dessus sont vérifiées et toute personne qui va mourir meurt d'un coup.
  4. Si tout l'espace ouvert de la carte a été recherché, la recherche est terminée.

Remarques

Plusieurs personnes peuvent être sur le même carré à tout moment et ces personnes peuvent se voir.

Les obstacles n'entravent jamais la vue, seulement le mouvement; les gens peuvent se voir à travers la, euh ... lave?

Les espaces ouverts sur la carte sont assurés d'être connectés par les mouvements du roi.

Le «S» initial est également considéré comme un espace ouvert, plutôt qu'un obstacle.

Tout mouvement de roi qui atterrit sur un espace ouvert est valide. Par exemple, la décision suivante est légale:

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

Contribution

L'entrée sera au format

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

Exemples d'entrées:

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

et

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

p et q sont les nombres de costars et de figurants, respectivement.

Production

La sortie doit être, pour chaque tour, les mouvements effectués, avec les directions indiquées par

789
456
123

L'ordre des mouvements n'a pas d'importance, car ils sont tous exécutés simultanément. Ne pas répertorier un mouvement pour une personne est correct et équivaut à le déplacer dans la direction 5. Les mouvements doivent être répertoriés dans le format suivant:

@9 A2 a2 B7.

"." indique la fin de vos mouvements pour un tour.

Une fois la carte recherchée, la dernière ligne de sortie doit être composée de trois entiers, séparés par des espaces: le nombre de tours qu'il vous a fallu pour terminer la recherche sur le tableau, le nombre de costars vivants et le nombre d'extras vivants. Pour le premier exemple d'entrée

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

ce qui suit est une sortie valide:

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

Une dernière note: l'étoile ne peut pas mourir et l'espace ouvert sur la carte est garanti pour être connecté, donc la recherche réussira toujours finalement.

Notation

Votre score est le nombre total de tours effectués sur un ensemble de tests de référence; vous êtes invités à soumettre votre propre cas de test avec votre réponse. La somme du nombre de costars vivants sur l'ensemble de référence sera utilisée comme bris d'égalité, et dans le cas où il y a toujours une égalité, la somme du nombre d'extras vivants sera utilisée.

Ensemble de test et contrôleur

Actuellement, 5 cartes sont en ligne sur https://github.com/Tudwell/HorrorMovieSearchParty/ . Toute personne soumettant une réponse peut également soumettre un cas de test, que je me réserve le droit de rejeter pour une raison quelconque (si je rejette votre carte pour une raison quelconque, vous pouvez en soumettre une autre). Ceux-ci seront ajoutés à l'ensemble de test à ma discrétion.

Un contrôleur Python (testé en 2.7.5) est fourni sur github en tant que controller.py . Un deuxième contrôleur là-bas, controller_disp.py , est identique sauf qu'il affiche une sortie graphique pendant la recherche (nécessite la bibliothèque Pygame).

Sortie du contrôleur graphique

Utilisation :

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

C'est à dire:

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

Le contrôleur a sorti (au stdin de votre programme ) de la forme

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

Ceci est le numéro du tour (le tour 1 est avant que vous ayez bougé), une liste terminée par '.' De tous les acteurs et leurs coordonnées x, y (le caractère supérieur gauche est (0,0)), une représentation de l'ensemble planche, et une ligne avec 40 '. Il attend ensuite la saisie (depuis la sortie standard de votre programme ) du formulaire

@9 A2 B7.

Il s'agit du format de sortie spécifié ci-dessus. Le contrôleur émet un «o» pour l'espace ouvert qui a été recherché, «.» pour un espace ouvert qui n'a pas été recherché et «#» pour les obstacles. Il inclut uniquement les personnes vivantes dans sa liste de personnes et leurs coordonnées, et suit toutes les règles du jeu. Le contrôleur se fermera si un mouvement illégal est tenté. Si les mouvements pour un tour donné terminent la recherche, la sortie n'est pas comme ci-dessus; c'est plutôt de la forme

Finished in 4 turns
4 1 0

"4 1 0" indique ici 4 tours totaux, 1 costar vivant et 0 figurants vivants. Vous n'avez pas besoin d'utiliser le contrôleur; n'hésitez pas à l'utiliser ou à le modifier pour votre propre entrée. Si vous décidez de l'utiliser et rencontrez des problèmes, faites le moi savoir.

Merci à @githubphagocyte de m'avoir aidé à écrire le contrôleur.

Modifier: pour une entrée aléatoire, vous pouvez choisir n'importe quelle course sur une carte particulière comme score pour cette carte. Notez qu'en raison des exigences de score, vous devez toujours choisir le moins de tours, puis le moins de costars morts, puis le moins de figurants morts pour chaque carte.

Eric Tressler
la source
7
la deuxième ligne doit être entre les balises spoilers!
Averroes

Réponses:

8

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

Je ne sais pas comment vous noterez les soumissions aléatoires. Si toutes les réponses doivent être déterministes, faites-le moi savoir et je choisirai une graine ou me débarrasserai complètement de l'aléatoire.

Quelques fonctionnalités et lacunes de cette solution:

  • Personne ne meurt jamais. Au début, je regroupe tous les acteurs de sorte que tout le monde soit en sécurité. Les personnages de chacun de ces groupes se déplacent à l'unisson. C'est bon pour garder tout le monde en vie, mais pas de manière optimale.
  • Chacun des groupes recherche le point inexploré le plus proche sur la carte via une recherche en largeur et prend le premier coup de cette branche de la recherche. S'il y a égalité entre plusieurs mouvements optimaux, un mouvement aléatoire est sélectionné. Il s'agit de s'assurer que tous les groupes ne se dirigent pas toujours dans la même direction.
  • Ce programme ne connaît pas le champ de vision. Il essaie en fait de se déplacer vers chaque cellule inexplorée. En tenant compte de cela, vous pouvez augmenter considérablement les performances, car vous pouvez également quantifier la qualité de chaque mouvement en fonction du nombre de cellules qu'il découvrira.
  • Le programme ne conserve pas d'informations entre les tours (sauf les groupes d'acteurs). Cela le rend assez lent sur les plus grands cas de test. map5.txtdure entre 1 et 13 minutes.

Quelques résultats

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

Voici maintenant le 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)

Il y a quelques sorties de débogage commentées. En particulier, le if group['@']bloc est assez intéressant car il imprime une visualisation des données BFS.

Edit: Amélioration significative de la vitesse, en arrêtant le BFS si un meilleur mouvement a déjà été trouvé (ce qui était un peu le point d'utiliser BFS en premier lieu).

Martin Ender
la source
est-il sûr de s'attendre à ce que votre entrée ait toujours accès au fichier de carte?
Sparr
Oui; le fichier de carte est toujours là, et si vous utilisez le contrôleur, vous en obtenez une copie mise à jour à chaque tour.
Eric Tressler