Entwerfen und lösen Sie ein Labyrinth


14

Ihre Aufgabe ist es, die Rollen beider Charaktere in dieser Szene aus Inception zu spielen. Darin fordert Cobb Ariadne heraus:

Sie haben zwei Minuten Zeit, um ein Labyrinth zu entwerfen, dessen Lösung eine Minute dauert.

Einige Freiheiten werden bei dieser Beschreibung übernommen. Am wichtigsten ist jedoch, dass diese Herausforderung nicht auf der Zeit basiert, sondern auf der Effektivität Ihrer Labyrinthe und Labyrinthlöser.

Ich entschuldige mich für die vielen Änderungen an dieser Herausforderung, während wir uns für ein einfaches und faires Format einsetzen.

Teil I: Labyrinthformat

Alle Irrgärten sind quadratisch. Eine Zelle im Labyrinth wird als ein Tupel mit Nullindex dargestellt row column.

Wände werden durch zwei binäre Zeichenfolgen dargestellt: eine für horizontale Wände (die die Bewegung zwischen Zeilen blockieren) und eine für vertikale Wände (umgekehrt). In einem NxNLabyrinth gibt es Nx(N-1)mögliche Wände jedes Typs. Nehmen wir ein 3x3-Beispiel, in dem die Zellen beschriftet sind:

A   B | C
   ---
D | E   F
   ---
G   H | I

alle möglichen vertikalen Wände sind: AB BC DE EF GH HI. Übersetzt in eine Schnur sind die gezeigten Wände 011001für vertikale Wände und 010010für horizontale Wände. Mit "Binärzeichenfolge" meine ich auch "die Zeichen" 0 "und" 1 "".

Das vollständige Labyrinthformat ist eine Zeichenfolge, die in dieser Reihenfolge Folgendes enthält:

  • Breite
  • Starte das Zellentupel
  • end cell tuple
  • horizontale Wände
  • vertikale Wände

Zum Beispiel dieses Labyrinth:

   0 1 2 3 4
   _________
0 | |  E|  _|
1 |  _|_|_  |
2 |_ _ _  | |
3 |  _ _  | |
4 |____S|___|
start:(4,2)
end:(0,2)

ist wie folgt formatiert:

5
4 2
0 2
00001011101110001100
10100110000100010010

Teil II: Der Architekt

Das Architect-Programm erstellt das Labyrinth. Es muss sich an die Regeln halten und ein gültiges Labyrinth bereitstellen (eines, bei dem eine Lösung existiert und das Ende nicht über dem Anfang liegt).

Eingabe: Zwei positive ganze Zahlen:

size [random seed]

Wo sizewird in sein [15, 50]. Es wird empfohlen, den Zufallsstartwert zu verwenden, damit Übereinstimmungen wiederholt werden können, obwohl dies nicht erforderlich ist.

Ausgabe: Ein gültiges Labyrinth der Größe x Größe (Quadrat) mit dem in Teil I beschriebenen Format. "Gültig" bedeutet, dass eine Lösung vorhanden ist und die Startzelle nicht mit der Endzelle übereinstimmt.

Die Punktzahl eines Architekten für ein bestimmtes Labyrinth ist

   # steps taken to solve
–––––––––––––––––––––––––––––
max(dist(start,end),(# walls))

So werden Architekten für komplexe Irrgärten belohnt, aber für jede errichtete Mauer bestraft (dies ist ein Ersatz für die zeitliche Beschränkung von Ariadne). Die dist()Funktion stellt sicher, dass ein Labyrinth ohne Wände keine unendliche Punktzahl erhält. Die Außenränder des Labyrinths tragen nicht zur Anzahl der Wände bei.

Teil III: Der Löser

The Solver versucht Labyrinthe zu lösen, die von den Architekten anderer erstellt wurden. Es gibt eine Art Nebel des Krieges: Es werden nur Wände eingeschlossen, die an besuchte Zellen angrenzen (alle anderen werden durch '?' Ersetzt).

Eingabe: das gleiche Labyrinthformat, aber mit '?' Wobei Wände unbekannt sind, eine zusätzliche Zeile für den aktuellen Standort und eine durch Kommas getrennte Liste der gültigen Auswahlmöglichkeiten für diesen Standort. (Dies ist eine umfangreiche Bearbeitung, die das Schreiben einer Labyrinth-Parsing-Funktion vereinfachen soll.)

Beispiel (wie das obige 5x5-Labyrinth nach einem Schritt nach links)

5
4 2
0 2
???????????????011??
????????????????001?
4 1
4 0,4 2

Was so etwas entspricht, wo ?ist Nebel:

   0 1 2 3 4
   _________
0 |????E????|
1 |?????????|
2 |?????????|
3 | ?_?_????|
4 |__C_S|_?_|

output: Eines der Tupel aus der Liste der gültigen Auswahlmöglichkeiten

Die Punktzahl jedes Solvers ist die Umkehrung der Punktzahl des Architekten.

Teil IV: König des Hügels

Architekten und Solver erhalten separate Bewertungen, sodass es möglicherweise zwei Gewinner gibt.

Jedes Paar von Architekten und Solvern hat viele Chancen, sich gegenseitig zu überlisten. Die Punktzahl wird über alle Tests und Gegner gemittelt. Im Gegensatz zu Code-Golf-Konventionen gewinnt die höchste Durchschnittspunktzahl!

Ich beabsichtige dies fortzusetzen, aber ich kann nicht garantieren, dass die Tests für immer fortgesetzt werden! Angenommen, in einer Woche wird ein Gewinner ermittelt.

Teil V: Einreichen

  • Ich behalte mein Vetorecht bei allen Einsendungen bei - Klugheit wird gefördert, aber nicht, wenn sie die Konkurrenz oder meinen Computer bricht! (Wenn ich nicht sagen kann, was Ihr Code tut, werde ich wahrscheinlich ein Veto einlegen)
  • Überlegen Sie sich einen Namen für Ihr Architect / Solver-Paar. Veröffentlichen Sie Ihren Code zusammen mit Anweisungen zur Ausführung.

In Kürze: ein aktualisiertes Python-Testkit für das neue Format. Es gab große Änderungen, die das Einreichen von Sprachen erlaubten.


10
Könnten Sie nicht ein Labyrinthformat definieren, das von Teilnehmern erstellt / gelesen werden soll, anstatt es auf Python zu beschränken? Das würde wahrscheinlich mehr Leute interessieren.
Geobits

Ich hatte zwei Gründe, restriktiv zu sein: Erstens, laufende Matches einfach und sicher zu automatisieren. Zweitens soll vermieden werden, dass für jede Sprache eine Lese- und Schreibbibliothek benötigt wird. Ich denke, wenn niemand Python benutzen will, muss ich eines oder beide aufgeben ...
falsch

1
Ich schreibe gerade einen Wrapper, der ein Unterprogramm ausführt und über stdin / stdout kommuniziert. Auf diese Weise können Sie jede gewünschte Sprache verwenden. Würden Sie das erlauben?
IchBinKeinBaum

Absolut! Ich war gerade dabei, das gesamte Fragenformat umzuschreiben. Soll ich warten?
Falsch

1
Ich wusste nicht, dass das eine Sache ist. Ich schätze, ich werde es
vorerst

Antworten:


1

BuildFun und SolveFun

Nun, das hat eine Weile gedauert und ich bin mir nicht ganz sicher, ob der Solver schummelt oder nicht. Während es die ganze Zeit über Zugriff auf das gesamte Labyrinth hat, betrachtet es nur die Zelle, in der es sich befindet, die Wände, die es umgeben, und, wenn es keine Wand zwischen ihnen gibt, die angrenzenden Zellen. Wenn dies gegen die Regeln verstößt, lass es mich bitte wissen und ich werde versuchen, es zu ändern.

Wie auch immer, hier ist der Code:

#Architect function
def BuildFun(size,seed):
   #Initialise grid and ensure inputs are valid
   if size<15:size=15
   if size>50:size=50
   if seed<4:seed=4
   if seed>size:seed=size
   grid=[]
   for x in range(size):
      gridbuilder=[]
      for y in range(size):gridbuilder.append([0,1,1])
      grid.append(gridbuilder)
   coords=[0,0]
   grid[0][0][0]=1
   #Generate maze
   while 1:
      #Choose a preffered direction based on location in grid and seed
      pref=((((coords[0]+coords[1]+2)*int(size/2))%seed)+(seed%(abs(coords[0]-coords[1])+1)))%4
      #Find legal moves
      opt=[]
      if coords[0]>0:opt+=[0] if grid[coords[0]-1][coords[1]][0]==0 else []
      if coords[1]<size-1:opt+=[1] if grid[coords[0]][coords[1]+1][0]==0 else []
      if coords[0]<size-1:opt+=[2] if grid[coords[0]+1][coords[1]][0]==0 else []
      if coords[1]>0:opt+=[3] if grid[coords[0]][coords[1]-1][0]==0 else []
      #There are legal moves
      if len(opt)>0:
         moved=False
         while not moved:
            #Try to move in preffered direction
            if pref in opt:
               if pref==0:
                  coords[0]-=1
                  grid[coords[0]][coords[1]][0]=1
                  grid[coords[0]][coords[1]][2]=0
               elif pref==1:
                  grid[coords[0]][coords[1]][1]=0
                  coords[1]+=1
                  grid[coords[0]][coords[1]][0]=1
               elif pref==2:
                  grid[coords[0]][coords[1]][2]=0
                  coords[0]+=1
                  grid[coords[0]][coords[1]][0]=1
               else:
                  coords[1]-=1
                  grid[coords[0]][coords[1]][0]=1
                  grid[coords[0]][coords[1]][1]=0
               moved=True
            #Change preferred direction if unable to move
            else:
               pref+=1
               if pref==4:pref=0
      #There aren't legal moves
      else:
         moved=False
         #Return to a previously visited location
         if not moved:
            try:
               if grid[coords[0]-1][coords[1]][0]==1 and grid[coords[0]-1][coords[1]][2]==0:
                  grid[coords[0]][coords[1]][0]=2
                  coords[0]-=1
                  moved=True
            except:pass
         if not moved:
            try:
               if grid[coords[0]][coords[1]+1][0]==1 and grid[coords[0]][coords[1]][1]==0:
                  grid[coords[0]][coords[1]][0]=2
                  coords[1]+=1
                  moved=True
            except:pass
         if not moved:
            try:
               if grid[coords[0]+1][coords[1]][0]==1 and grid[coords[0]][coords[1]][2]==0:
                  grid[coords[0]][coords[1]][0]=2
                  coords[0]+=1
                  moved=True
            except:pass
         if not moved:
            try:
               if grid[coords[0]][coords[1]-1][0]==1 and grid[coords[0]][coords[1]-1][1]==0:
                  grid[coords[0]][coords[1]][0]=2
                  coords[1]-=1
                  moved=True
            except:pass
      #Check if finished
      fin=True
      for x in grid:
         for y in x:
            if y[0]==0:
               fin=False
               break
         if not fin:break
      if fin:break
   for x in grid:
      for y in x:
         y[0]=0
   #Find positions for start and finish such that the route between them is as long as possible
   lsf=[[0,0],[0,0],0]
   for y in range(size):
      for x in range(size):
         #Check all start positions
         lengths=[]
         coords=[[y,x,4,0]]
         while len(coords)>0:
            #Spread tracers out from start to the rest of the maze
            for coord in coords:
               opt=[]
               if coord[0]>0:opt+=[0] if grid[coord[0]-1][coord[1]][2]==0 else []
               opt+=[1] if grid[coord[0]][coord[1]][1]==0 else []
               opt+=[2] if grid[coord[0]][coord[1]][2]==0 else []
               if coord[1]>0:opt+=[3] if grid[coord[0]][coord[1]-1][1]==0 else []
               try:opt.remove(coord[2])
               except:pass
               #Dead end, tracer dies and possible end point is recorded along with length
               if len(opt)==0:
                  lengths.append([coord[0],coord[1],coord[3]])
                  coords.remove(coord)
               else:
                  #Create more tracers at branch points
                  while len(opt)>1:
                     if opt[0]==0:coords.append([coord[0]-1,coord[1],2,coord[3]+1])
                     elif opt[0]==1:coords.append([coord[0],coord[1]+1,3,coord[3]+1])
                     elif opt[0]==2:coords.append([coord[0]+1,coord[1],0,coord[3]+1])
                     else:coords.append([coord[0],coord[1]-1,1,coord[3]+1])
                     del opt[0]
                  if opt[0]==0:
                     coord[0]-=1
                     coord[2]=2
                     coord[3]+=1
                  elif opt[0]==1:
                     coord[1]+=1
                     coord[2]=3
                     coord[3]+=1
                  elif opt[0]==2:
                     coord[0]+=1
                     coord[2]=0
                     coord[3]+=1
                  else:
                     coord[1]-=1
                     coord[2]=1
                     coord[3]+=1
         #Find furthest distance and, if it's longer than the previous one, the start/end positions get updated
         lengths=sorted(lengths,key=lambda x:x[2],reverse=True)
         if lengths[0][2]>lsf[2]:lsf=[[y,x],[lengths[0][0],lengths[0][1]],lengths[0][2]]
   #Find number of walls and output maze
   w=draw(grid,size,lsf[0],lsf[1])
   #Output maze information
   print('Start = '+str(lsf[0]))
   print('End = '+str(lsf[1]))
   print('Distance = '+str(lsf[2]))
   print('Walls = '+str(w))
   print('Score = '+str(float(lsf[2])/float(w))[:5])
   #Convert array grid to binary strings horizontal and vertical
   horizontal=vertical=''
   for y in range(size):
      for x in range(size-1):vertical+=str(grid[y][x][1])
   for y in range(size-1):
      for x in range(size):horizontal+=str(grid[y][x][2])
   #Save maze information to text file for use with SolveFun
   save=open('Maze.txt','w')
   save.write(str(size)+'\n'+str(lsf[0][0])+' '+str(lsf[0][1])+'\n'+str(lsf[1][0])+' '+str(lsf[1][1])+'\n'+horizontal+'\n'+vertical)
   save.close()
#Solver function
def SolveFun():
   try:
      #Get maze information from text file
      save=open('Maze.txt','r')
      data=save.readlines()
      save.close()
      size=int(data[0])
      s=data[1].rsplit(' ')
      start=[int(s[0]),int(s[1])]
      e=data[2].rsplit(' ')
      end=[int(e[0]),int(e[1])]
      horizontal=data[3].rstrip('\n')
      vertical=data[4]
      #Build maze from information
      grid=[]
      for y in range(size):
         grid.append([])
         for x in range(size):
            grid[y].append([0,1,1])
      for y in range(size):
         for x in range(size-1):
            grid[y][x][1]=int(vertical[y*(size-1)+x])
      for y in range(size-1):
          for x in range(size):
            grid[y][x][2]=int(horizontal[y*size+x])
      path=''
      cpath=''
      bs=0
      pos=start[:]
      grid[pos[0]][pos[1]][0]=1
      while pos!=end:
         #Want to move in direction of finish
         if end[0]<pos[0] and pos[0]-end[0]>=abs(pos[1]-end[1]):pref=0
         elif end[1]>pos[1] and end[1]-pos[1]>=abs(pos[0]-end[0]):pref=1
         elif end[0]>pos[0] and end[0]-pos[0]>=abs(pos[1]-end[1]):pref=2
         else:pref=3
         #Find legal moves
         opt=[]
         if pos[0]>0:
            if grid[pos[0]-1][pos[1]][2]==0:opt+=[0]if grid[pos[0]-1][pos[1]][0]==0 else[]
         if pos[1]>0:
            if grid[pos[0]][pos[1]-1][1]==0:opt+=[3]if grid[pos[0]][pos[1]-1][0]==0 else[]
         if grid[pos[0]][pos[1]][2]==0:opt+=[2]if grid[pos[0]+1][pos[1]][0]==0 else[]
         if grid[pos[0]][pos[1]][1]==0:opt+=[1]if grid[pos[0]][pos[1]+1][0]==0 else[]
         if len(opt)>0:
            moved=False
            while not moved:
               #Try to move in preferred direction
               if pref in opt:
                  if pref==0:
                     pos[0]-=1
                     path+='0'
                     cpath+='0'
                  elif pref==1:
                     pos[1]+=1
                     path+='1'
                     cpath+='1'
                  elif pref==2:
                     pos[0]+=1
                     path+='2'
                     cpath+='2'
                  else:
                     pos[1]-=1
                     path+='3'
                     cpath+='3'
                  grid[pos[0]][pos[1]][0]=1
                  moved=True
               #Change preferred direction by 1
               else:
                  pref=(pref+1)%4
         #No legal moves, backtrack
         else:
            bs+=1
            grid[pos[0]][pos[1]][0]=2
            if int(cpath[len(cpath)-1])==0:
               pos[0]+=1
               path+='2'
            elif int(cpath[len(cpath)-1])==1:
               pos[1]-=1
               path+='3'
            elif int(cpath[len(cpath)-1])==2:
               pos[0]-=1
               path+='0'
            else:
               pos[1]+=1
               path+='1'
            cpath=cpath[:len(cpath)-1]
      #Output maze with solution as well as total steps and wasted steps
      draw(grid,size,start,end)
      print('\nPath taken:')
      print(str(len(path))+' steps')
      print(str(bs)+' backsteps')
      print(str(bs*2)+' wasted steps')
   except:print('Could not find maze')
def draw(grid,size,start,end):
   #Build output in string d
   d='   '
   for x in range(size):d+=' '+str(x)[0]
   d+='\n   '
   for x in range(size):d+='  ' if len(str(x))==1 else ' '+str(x)[1]
   d+='\n    '+'_'*(size*2-1)
   w=0
   for y in range(size):
      d+='\n'+str(y)+'  |' if len(str(y))==1 else '\n'+str(y)+' |'
      for x in range(size):
         if grid[y][x][2]:
            if start==[y,x]:d+=UL.S+'S'+UL.E
            elif end==[y,x]:d+=UL.S+'F'+UL.E
            elif grid[y][x][0]==1:d+=UL.S+'*'+UL.E
            else:d+='_'
            w+=1
         else:
            if start==[y,x]:d+='S'
            elif end==[y,x]:d+='F'
            elif grid[y][x][0]==1:d+='*'
            else:d+=' '
         if grid[y][x][1]:
            d+='|'
            w+=1
         else:d+=' '
   #Output maze and return number of walls
   print(d)
   w-=size*2
   return w
#Underlines text
class UL:
   S = '\033[4m'
   E = '\033[0m'

Mir ist klar, dass dies lächerlich lang und nicht besonders einfach zu lesen ist, aber ich bin faul, also bleibt es so.

BuildFun

Der Architekt BuildFun ist ein relativ einfaches Programm zur Erzeugung von Labyrinthen, das immer ein 'perfektes' Labyrinth erzeugt (eines ohne Schleifen und bei dem zwei beliebige Punkte immer genau einen Pfad zwischen sich haben). Es basiert seine Logik auf der Sameneingabe, was bedeutet, dass die generierten Labyrinthe pseudozufällig sind und häufig sich wiederholende Muster aufweisen. Mit demselben Samen und derselben Größe wird dasselbe Labyrinth erstellt.

Sobald das Labyrinth generiert wurde, versucht das Programm, die Punktzahl des Labyrinths zu maximieren, indem es nach dem Start- und Endpunkt sucht, die den längsten Pfad zwischen ihnen ergeben. Dazu durchläuft es jeden Startpunkt, verteilt die Tracer, um den am weitesten entfernten Endpunkt zu finden, und wählt die Kombination mit dem längsten Pfad aus.

Danach zeichnet es das Labyrinth, zählt die Wände und gibt die Informationen des Labyrinths aus. Dies ist der Startpunkt, der Endpunkt, der Abstand zwischen ihnen, die Anzahl der Wände und die Punktzahl. Außerdem werden diese Informationen in den oben beschriebenen Stil für Größe, Anfang und Ende, horizontale Wände und vertikale Wände formatiert und zur späteren Verwendung in einer Textdatei mit dem Namen Maze.txt gespeichert.

SolveFun

Der Solver SolveFun verwendet die Textdatei Maze.txt als Eingabe und funktioniert ähnlich wie der Architekt. Für jede Bewegung wählt es eine Richtung, die es gehen möchte, basierend auf seiner relativen Position zum Ende, und dann schaut es auf die Wände, die es umgeben. Wenn eine Wand nicht vorhanden ist, wird geprüft, ob sie sich in der angrenzenden Zelle befindet. Andernfalls wird sie als mögliche Option hinzugefügt. Es bewegt sich dann in die Richtung, die seiner Vorzugsrichtung am nächsten kommt, sofern es über Optionen verfügt. Wenn es keine Optionen gibt, wird es zurückverfolgt, bis dies der Fall ist. Dies geht so lange weiter, bis das Ende erreicht ist.

Während der Bewegung zeichnet es den Pfad, den es nimmt, in dem variablen Pfad auf, der am Ende zur Ausgabe der Gesamtzahl der Schritte verwendet wird. Es zeichnet auch auf, wie oft es zurückverfolgt werden musste, um die verschwendeten Schritte am Ende zu berechnen. Wenn es das Ende erreicht, gibt es das Labyrinth mit dem kürzesten Weg vom Anfang zum Ende aus, der mit *s markiert ist .

Wie man läuft

Aufgrund der Methode zur Ausgabe des Labyrinths (die das Unterstreichen bestimmter Zeichen umfasst) muss dies über eine Befehlszeile im Formular ausgeführt werden

python -c 'import filename;filename.BuildFun(Size, Seed)'

und

python -c 'import filename;filename.SolveFun()'

Dabei ist Size eine ganze Zahl zwischen 15 und 50 (einschließlich) und Seed eine ganze Zahl zwischen 4 und Size (einschließlich).

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.