Progetta e risolvi un labirinto [in attesa durante il sandboxing]


14

Il tuo compito è interpretare i ruoli di entrambi i personaggi in questa scena di Inception. In esso, Cobb presenta ad Ariadne una sfida:

Hai due minuti per progettare un labirinto che richiede un minuto per risolversi.

Alcune libertà saranno prese su quella descrizione. Ancora più importante, questa sfida non è basata sul tempo, ma i punteggi si basano sull'efficacia dei tuoi labirinti e risolutori di labirinti.

Mi scuso per le numerose modifiche a questa sfida, mentre ripetiamo un formato semplice ed equo.

Parte I: formato labirinto

Tutti i labirinti sono quadrati. Una cella nel labirinto è rappresentata come una tupla con indice zero row column.

Le pareti sono rappresentate da due stringhe binarie: una per le pareti orizzontali (che bloccano il movimento tra le file) e le pareti verticali (viceversa). Su un NxNlabirinto, ci sono Nx(N-1)possibili pareti di ogni tipo. Facciamo un esempio 3x3 in cui le celle sono etichettate:

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

tutti i possibili pareti verticali sono: AB BC DE EF GH HI. Tradotti in una stringa, le pareti mostrate sono 011001per pareti verticali e 010010per pareti orizzontali. Inoltre, per "stringa binaria" intendo "i caratteri '0' e '1'".

Il formato completo del labirinto è una stringa che contiene, in questo ordine:

  • larghezza
  • avvia la tupla cellulare
  • tupla delle cellule terminali
  • pareti orizzontali
  • pareti verticali

Ad esempio, questo labirinto:

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

è formattato in questo modo:

5
4 2
0 2
00001011101110001100
10100110000100010010

Parte II: l'architetto

Il programma Architect crea il labirinto. Deve essere conforme alle regole e fornire un labirinto valido (uno in cui esiste una soluzione e la fine non è in cima all'inizio).

Input: due numeri interi positivi:

size [random seed]

Dove sizesarà [15, 50]. Sei incoraggiato a fare uso del seme casuale in modo che le partite possano essere riprodotte, anche se non è richiesto.

Output: un labirinto di dimensioni x dimensioni (quadrato) valido utilizzando il formato descritto nella Parte I. "valido" significa che esiste una soluzione e che la cella iniziale non è uguale alla cella finale.

Il punteggio di un architetto su un determinato labirinto è

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

Quindi gli architetti vengono premiati per labirinti complessi, ma penalizzati per ogni muro costruito (questo è un sostituto della restrizione temporale di Arianna). La dist()funzione assicura che un labirinto senza pareti non ottenga un punteggio infinito. I bordi esterni del labirinto non contribuiscono al conteggio delle pareti.

Parte III: Il Risolutore

Il Risolutore tenta di risolvere i labirinti generati dagli architetti di altri. Esiste una sorta di nebbia di guerra: sono incluse solo le pareti adiacenti alle celle visitate (tutte le altre sono sostituite da "?")

input: lo stesso formato labirinto, ma con '?' dove i muri sono sconosciuti, una linea aggiuntiva per la posizione corrente e un elenco separato da virgole di scelte valide da questa posizione. (Questa è una grande modifica che ha lo scopo di rendere più semplice la scrittura di una funzione di analisi del labirinto)

esempio (uguale al labirinto 5x5 sopra dopo aver fatto un passo a sinistra)

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

Che corrisponde a qualcosa del genere, dov'è la ?nebbia:

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

output: una delle tuple dall'elenco di scelte valide

Il punteggio di ciascun Risolutore è l'inverso del punteggio dell'architetto.

Parte IV: King of the Hill

A Architects e Solvers vengono assegnati punteggi separati, quindi potrebbero esserci potenzialmente due vincitori.

Ciascuna coppia di architetti e solutori avrà molte possibilità di battersi a vicenda. I punteggi saranno calcolati in media su tutti i test e gli avversari. Contrariamente alle convenzioni del codice golf, vince il punteggio medio più alto!

Intendo che ciò avvenga, ma non posso garantire che i test continui per sempre! Diciamo per ora che un vincitore verrà dichiarato tra una settimana.

Parte V: Invio

  • Mantengo il potere di veto su tutti gli invii: l'intelligenza è incoraggiata, ma non se rompe la concorrenza o il mio computer! (Se non riesco a dire cosa fa il tuo codice, probabilmente lo annullerò)
  • Trova un nome per la tua coppia Architect / Solver. Pubblica il tuo codice insieme alle istruzioni su come eseguirlo.

Prossimamente: un kit di test Python aggiornato per il nuovo formato. Grandi cambiamenti sono avvenuti per consentire l'invio di qualsiasi lingua.


10
Invece di limitarlo a Python, non potresti definire un formato labirinto che deve essere creato / letto dai concorrenti? Ciò probabilmente susciterebbe l'interesse di più persone.
Geobits

Avevo due motivi per essere restrittivo: il primo è quello di automatizzare facilmente e in sicurezza le partite in corso. Il secondo è evitare di richiedere una libreria di lettura e scrittura per ogni lingua. Immagino che se nessuno vuole usare Python dovrò rinunciare a uno o entrambi ...
sbagliato

1
Attualmente sto scrivendo un wrapper che esegue un sottoprogramma e comunica su stdin / stdout. In questo modo puoi usare qualsiasi lingua tu voglia. Lo permetteresti?
IchBinKeinBaum

Assolutamente! Ero nel mezzo di riscrivere l'intero formato della domanda. Dovrei aspettare?
wrongu,

1
Non sapevo che fosse una cosa. Immagino che lo metterò in sospeso per ora ..
male,

Risposte:


1

BuildFun e SolveFun

Bene, ci è voluto un po 'di tempo e non sono del tutto sicuro se il risolutore stia barando o no. Sebbene abbia sempre accesso all'intero labirinto, osserva solo la cella in cui si trova, i muri che lo circondano e, se non c'è un muro tra loro, le celle adiacenti. Se questo è contro le regole per favore fatemelo sapere e proverò a cambiarlo.

Comunque, ecco il codice:

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

Mi rendo conto che è ridicolmente lungo e non particolarmente facile da leggere, ma sono pigro, quindi è così che sta.

BuildFun

L'architetto, BuildFun, è un programma di generazione di labirinti abbastanza semplice che creerà sempre un labirinto "perfetto" (uno senza anelli e in cui due punti avranno sempre esattamente un percorso tra di loro). Basa la sua logica sull'input del seme, il che significa che i labirinti generati sono pseudo-casuali con quelli che spesso sembrano schemi ripetitivi e, con lo stesso seme e le stesse dimensioni, verrà creato lo stesso labirinto.

Una volta generato il labirinto, il programma tenterà di massimizzare il punteggio del labirinto cercando il punto iniziale e finale che si traducono nel percorso più lungo tra di loro. Per fare ciò, attraversa tutti i punti di partenza, allarga i traccianti per trovare il punto finale più lontano da esso e sceglie la combinazione con il percorso più lungo.

Dopo questo, disegna il labirinto, conta le pareti e produce le informazioni del labirinto. Questo è il punto iniziale, il punto finale, la distanza tra loro, il numero di pareti e il punteggio. Inoltre formatta queste informazioni nello stile sopra descritto per dimensioni, inizio e fine, pareti orizzontali e pareti verticali e le salva in un file di testo chiamato Maze.txt per un utilizzo successivo.

SolveFun

Il solutore, SolveFun, utilizza il file di testo Maze.txt come input e funziona in modo molto simile all'architetto. Per ogni mossa, sceglierà una direzione in cui vuole andare in base alla sua posizione relativa fino alla fine e poi guarderà le pareti che la circondano. Se un muro non è presente, controllerà se è stato nella cella adiacente ad esso e, in caso contrario, verrà aggiunto come opzione possibile. Si sposterà quindi nella direzione più vicina alla sua direzione preferita, a condizione che abbia opzioni. Se non ha opzioni, tornerà indietro fino a quando non lo farà. Questo continua fino a raggiungere la fine.

Mentre si sposta, registra il percorso che sta seguendo nel percorso variabile che viene utilizzato alla fine per generare il numero totale di passaggi. Registra anche il numero di volte che ha dovuto tornare indietro utilizzato per calcolare i passi sprecati alla fine. Quando raggiunge la fine, emetterà il labirinto con il percorso più breve dall'inizio alla fine contrassegnato con *s.

Come correre

A causa del metodo di emissione del labirinto (che include la sottolineatura di alcuni caratteri), questo deve essere eseguito da una riga di comando nel modulo

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

e

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

dove Dimensione è un numero intero compreso tra 15 e 50 (incluso) e Seed è un numero intero compreso tra 4 e Dimensione (incluso).

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.