Diseñar y resolver un laberinto [en espera durante el sandboxing]

14

Su tarea es interpretar los roles de ambos personajes en esta escena desde el inicio. En él, Cobb le da a Ariadne un desafío:

Tienes dos minutos para diseñar un laberinto que lleva un minuto resolver.

Se tomarán algunas libertades con esa descripción. Lo más importante, este desafío no se basa en el tiempo, sino que los puntajes se basan en la efectividad de sus laberintos y solucionadores de laberintos.

Pido disculpas por las muchas ediciones a este desafío mientras iteramos hacia un formato fácil y justo.

Parte I: formato de laberinto

Todos los laberintos son cuadrados. Una celda en el laberinto se representa como una tupla indexada a cero row column.

Las paredes están representadas por dos cadenas binarias: una para paredes horizontales (que bloquean el movimiento entre filas) y paredes verticales (viceversa). En un NxNlaberinto, hay Nx(N-1)posibles paredes de cada tipo. Tomemos un ejemplo de 3x3 donde las celdas están etiquetadas:

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

todas las posibles paredes verticales son: AB BC DE EF GH HI. Traducido a una cadena, las paredes que se muestran son 011001para paredes verticales y 010010para paredes horizontales. Además, por "cadena binaria" me refiero a "los caracteres '0' y '1'".

El formato de laberinto completo es una cadena que contiene, en este orden:

  • anchura
  • iniciar la tupla celular
  • tupla celular final
  • paredes horizontales
  • paredes verticales

Por ejemplo, este laberinto:

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

está formateado para esto:

5
4 2
0 2
00001011101110001100
10100110000100010010

Parte II: El Arquitecto

El programa Arquitecto crea el laberinto. Debe cumplir con las reglas y proporcionar un laberinto válido (uno donde exista una solución y el final no esté encima del inicio).

Entrada: dos enteros positivos:

size [random seed]

¿Dónde sizeestará [15, 50]? Se le recomienda que utilice la semilla aleatoria para poder reproducir las coincidencias, aunque no es obligatorio.

Salida: Un laberinto válido de tamaño x tamaño (cuadrado) usando el formato descrito en la Parte I. "válido" significa que existe una solución, y la celda inicial no es igual a la celda final.

La puntuación de un arquitecto en un laberinto dado es

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

Por lo tanto, los arquitectos son recompensados ​​por laberintos complejos, pero penalizados por cada muro construido (esto es un sustituto de la restricción de tiempo de Ariadne). La dist()función garantiza que un laberinto sin paredes no obtenga una puntuación infinita. Los bordes exteriores del laberinto no contribuyen al conteo de paredes.

Parte III: El Solucionador

El Solver intenta resolver laberintos generados por los arquitectos de otros. Hay una especie de niebla de guerra: solo se incluyen las paredes adyacentes a las celdas visitadas (todas las demás se reemplazan con '?')

entrada: el mismo formato de laberinto, pero con '?' donde los muros son desconocidos, una línea adicional para la ubicación actual y una lista separada por comas de opciones válidas de esta ubicación. (Esta es una gran edición que pretende simplificar la escritura de una función de análisis de laberintos)

ejemplo (igual que el laberinto 5x5 anterior después de dar un paso a la izquierda)

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

Lo que corresponde a algo como esto, donde ?está la niebla:

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

salida: una de las tuplas de la lista de opciones válidas

El puntaje de cada Solucionador es el inverso del puntaje del Arquitecto.

Parte IV: Rey de la colina

Los arquitectos y los solucionadores reciben puntuaciones separadas, por lo que podría haber dos ganadores.

Cada par de arquitectos y solucionadores tendrán muchas oportunidades de burlarse mutuamente. Los puntajes se promediarán en todas las pruebas y oponentes. Al contrario de las convenciones de golf codificadas, ¡el puntaje promedio más alto gana!

¡Tengo la intención de que esto continúe, pero no puedo garantizar que continúen las pruebas para siempre! Digamos por ahora que se declarará un ganador en una semana.

Parte V: Presentación

  • Mantengo el poder de veto sobre todas las presentaciones: se recomienda la inteligencia, ¡pero no si se rompe la competencia o mi computadora! (Si no puedo decir qué hace su código, probablemente lo vetaré)
  • Proponga un nombre para su par Arquitecto / Solucionador. Publique su código junto con instrucciones sobre cómo ejecutarlo.

Próximamente: un kit de prueba de Python actualizado para el nuevo formato. Se produjeron grandes cambios para permitir el envío de cualquier idioma.

wrongu
fuente
10
En lugar de restringirlo a Python, ¿no podría definir un formato de laberinto para ser creado / leído por los concursantes? Eso probablemente atraería a más personas interesadas.
Geobits
Tenía dos razones para ser restrictivo: la primera es automatizar de forma fácil y segura los partidos en ejecución. El segundo es evitar que se requiera una biblioteca de lectura y escritura para cada idioma. Supongo que si nadie quiere usar pitón Voy a tener que renunciar a uno o ambos ...
wrongu
1
Actualmente estoy escribiendo un contenedor que ejecuta un subprograma y se comunica a través de stdin / stdout. De esta manera, puede usar el idioma que desee. ¿Permitirías eso?
IchBinKeinBaum
¡Absolutamente! Estaba en medio de reescribir todo el formato de la pregunta. ¿Debería esperar?
wrongu
1
No sabía que era una cosa. Supongo que lo pondré en espera por ahora ...
wrongu

Respuestas:

1

BuildFun y SolveFun

Bueno, esto tomó bastante tiempo y no estoy completamente seguro de si el solucionador está haciendo trampa o no. Si bien tiene acceso a todo el laberinto todo el tiempo, solo mira la celda en la que se encuentra, las paredes que lo rodean y, si no hay una pared entre ellas, las celdas adyacentes. Si esto va en contra de las reglas, avíseme e intentaré cambiarlo.

De todos modos, aquí está el código:

#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'

Me doy cuenta de que esto es ridículamente largo y no es particularmente fácil de leer, pero soy vago, así es como se mantiene.

BuildFun

El arquitecto, BuildFun, es un programa generador de laberintos bastante simple que siempre creará un laberinto 'perfecto' (uno sin bucles y donde dos puntos siempre tendrán exactamente un camino entre ellos). Basa su lógica en la entrada de semilla, lo que significa que los laberintos generados son seudoaleatorios con lo que a menudo parecen ser patrones repetitivos y, con la misma semilla y tamaño, se creará el mismo laberinto.

Una vez que se genera el laberinto, el programa intentará maximizar la puntuación del laberinto buscando el punto de inicio y el punto final que resulten en el camino más largo entre ellos. Para hacer esto, recorre cada punto de inicio, extiende los trazadores para encontrar el punto final más alejado de él y selecciona la combinación con el camino más largo.

Después de esto, dibuja el laberinto, cuenta las paredes y genera la información del laberinto. Este es el punto de inicio, el punto final, la distancia entre ellos, el número de paredes y la puntuación. También formatea esta información en el estilo descrito anteriormente para tamaño, inicio y final, paredes horizontales y paredes verticales, y la guarda en un archivo de texto llamado Maze.txt para su uso posterior.

SolveFun

El solucionador, SolveFun, utiliza el archivo de texto Maze.txt como entrada y funciona de manera muy similar al arquitecto. Para cada movimiento, elegirá una dirección en la que quiere ir en función de su posición relativa hasta el final y luego mirará las paredes que lo rodean. Si no hay un muro allí, verificará si ha estado en la celda adyacente y, de lo contrario, se agregará como posible opción. Luego se moverá en la dirección más cercana a su dirección preferida, siempre que tenga opciones. Si no tiene opciones, retrocederá hasta que lo tenga. Esto continúa hasta que llega al final.

A medida que se mueve, registra la ruta que está tomando en la ruta variable que se utiliza al final para generar el número total de pasos. También registra la cantidad de veces que tuvo que retroceder para calcular los pasos desperdiciados al final. Cuando llegue al final, dará salida al laberinto con el camino más corto de principio a fin marcado con *s.

Como correr

Debido al método de salida del laberinto (que incluye subrayar ciertos caracteres), esto debe ejecutarse desde una línea de comando en el formulario

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

y

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

donde Size es un número entero entre 15 y 50 (inclusive) y Seed es un número entero entre 4 y Size (inclusive).

Anónimo No Lifer
fuente