Mendesain dan Memecahkan Labirin [ditahan sambil sandboxing]

14

Tugas Anda adalah memainkan peran kedua karakter dalam adegan ini dari Inception. Di dalamnya, Cobb memberi Ariadne tantangan:

Anda memiliki dua menit untuk mendesain labirin yang membutuhkan waktu satu menit untuk menyelesaikannya.

Beberapa kebebasan akan diambil pada deskripsi itu. Yang paling penting, tantangan ini bukan berdasarkan waktu, tetapi skor didasarkan pada efektivitas labirin dan pemecah labirin Anda.

Saya minta maaf atas banyaknya suntingan untuk tantangan ini karena kami beralih ke format yang mudah dan adil ..

Bagian I: Format labirin

Semua labirin berbentuk bujur sangkar. Sel di maze diwakili sebagai tuple yang diindeks nol row column.

Dinding diwakili oleh dua string biner: satu untuk dinding horizontal (yang menghalangi pergerakan antar baris) dan dinding vertikal (sebaliknya). Pada NxNlabirin, ada Nx(N-1)kemungkinan dinding dari masing-masing jenis. Mari kita ambil contoh 3x3 di mana sel diberi label:

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

semua dinding vertikal yang mungkin adalah: AB BC DE EF GH HI. Diterjemahkan menjadi string, dinding yang ditampilkan adalah 011001untuk dinding vertikal dan 010010untuk dinding horizontal. Juga, dengan "string biner" Maksudku "karakter '0' dan '1'".

Format labirin lengkap adalah string yang berisi, dalam urutan ini:

  • lebar
  • mulai tupel sel
  • tuple sel ujung
  • dinding horisontal
  • dinding vertikal

Misalnya, labirin ini:

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

diformat untuk ini:

5
4 2
0 2
00001011101110001100
10100110000100010010

Bagian II: Arsitek

Program Arsitek menciptakan labirin. Itu harus bermain sesuai aturan dan memberikan labirin yang valid (yang ada solusinya, dan akhirnya tidak di atas permulaan).

Input: Dua bilangan bulat positif:

size [random seed]

Di mana sizeakan berada [15, 50]. Anda didorong untuk menggunakan benih acak sehingga pertandingan dapat diputar ulang, meskipun tidak diperlukan.

Keluaran: Ukuran x ukuran (kuadrat) yang valid menggunakan format yang diuraikan dalam Bagian I. "valid" berarti ada solusi, dan sel awal tidak sama dengan sel akhir.

Nilai seorang Arsitek pada labirin yang diberikan adalah

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

Jadi arsitek dihargai untuk labirin yang kompleks, tetapi dihukum untuk setiap dinding yang dibangun (ini adalah pengganti pembatasan waktu Ariadne). The dist()Fungsi memastikan bahwa labirin tanpa dinding tidak mendapatkan nilai yang tak terbatas. Batas luar labirin tidak berkontribusi pada penghitungan dinding.

Bagian III: The Solver

The Solver mencoba memecahkan labirin yang dihasilkan oleh arsitek orang lain. Ada semacam kabut perang: hanya dinding yang berdekatan dengan sel yang dikunjungi yang dimasukkan (yang lainnya diganti dengan '?')

input: format labirin yang sama, tetapi dengan '?' di mana dinding tidak diketahui, garis tambahan untuk lokasi saat ini, dan daftar pilihan valid yang dipisahkan koma dari lokasi ini. (Ini adalah suntingan besar yang dimaksudkan untuk membuatnya lebih mudah untuk menulis fungsi parsing-labirin)

contoh (sama seperti labirin 5x5 di atas setelah mengambil satu langkah ke kiri)

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

Yang sesuai dengan sesuatu seperti ini, di mana ?kabut:

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

output: Salah satu tupel dari daftar pilihan yang valid

Setiap skor Solver adalah kebalikan dari skor Arsitek.

Bagian IV: King of the Hill

Arsitek dan Pemecah diberi skor terpisah, sehingga berpotensi ada dua pemenang.

Setiap pasangan arsitek dan pemecah akan memiliki banyak peluang untuk saling mengecoh. Skor akan dirata-rata untuk semua tes dan lawan. Berlawanan dengan konvensi kode golf, skor rata-rata tertinggi menang!

Saya bermaksud agar ini terus berlangsung, tetapi saya tidak dapat menjamin pengujian yang berkelanjutan untuk selamanya! Katakanlah untuk saat ini bahwa seorang pemenang akan diumumkan dalam satu minggu.

Bagian V: Menyerahkan

  • Saya mempertahankan hak veto atas semua pengiriman - kepintaran dianjurkan, tetapi tidak jika itu merusak kompetisi atau komputer saya! (Jika saya tidak tahu apa kode Anda, saya mungkin akan memveto)
  • Munculkan nama untuk pasangan Architect / Solver Anda. Posting kode Anda bersama dengan instruksi tentang cara menjalankannya.

Segera hadir: kit uji python yang diperbarui untuk format baru. Perubahan besar terjadi untuk memungkinkan pengiriman bahasa apa pun.

salah
sumber
10
Daripada membatasi ke python, tidak bisakah Anda mendefinisikan format labirin yang akan dibuat / dibaca oleh kontestan? Itu mungkin akan membuat lebih banyak orang tertarik.
Geobits
Saya memiliki dua alasan untuk membatasi: pertama adalah untuk secara otomatis dan aman mengotomatiskan pertandingan lari. Kedua adalah untuk menghindari membutuhkan perpustakaan membaca dan menulis untuk setiap bahasa. Saya kira jika tidak ada yang ingin menggunakan python saya harus menyerah satu atau keduanya ...
salah
1
Saat ini saya sedang menulis pembungkus yang menjalankan sub program dan berkomunikasi melalui stdin / stdout. Dengan cara ini Anda dapat menggunakan bahasa apa pun yang Anda inginkan. Apakah Anda mengizinkannya?
IchBinKeinBaum
Benar! Saya sedang menulis ulang seluruh format pertanyaan. Haruskah saya menunggu?
wrongu
1
Saya tidak tahu itu sesuatu. Saya kira saya akan menahannya untuk saat ini ..
salah

Jawaban:

1

BuildFun dan SolveFun

Yah, ini butuh waktu cukup lama dan saya tidak sepenuhnya yakin apakah pemecahnya curang atau tidak. Meskipun memiliki akses ke seluruh labirin sepanjang waktu, ia hanya melihat sel yang ada di dalamnya, dinding yang mengelilinginya, dan jika tidak ada dinding di antara mereka, sel-sel yang bersebelahan dengannya. Jika ini melanggar aturan, beri tahu saya dan saya akan mencoba mengubahnya.

Bagaimanapun, ini kodenya:

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

Saya menyadari bahwa ini sangat panjang dan tidak mudah dibaca, tapi saya malas jadi begini caranya.

BuildFun

Arsiteknya, BuildFun, adalah program penghasil labirin yang cukup sederhana yang akan selalu membuat labirin 'sempurna' (satu tanpa loop dan di mana dua titik akan selalu memiliki tepat satu jalur di antara mereka). Ini mendasarkan logikanya dari input benih yang berarti bahwa labirin yang dihasilkan adalah pseudo-acak dengan apa yang sering tampak sebagai pola berulang dan, dengan benih dan ukuran yang sama, labirin yang sama akan dibuat.

Setelah labirin dihasilkan, program akan berusaha memaksimalkan skor labirin dengan mencari titik awal dan titik akhir yang menghasilkan jalur terpanjang di antara mereka. Untuk melakukan ini, ia berjalan melalui setiap titik awal, menyebar pelacak untuk menemukan titik akhir terjauh dari itu, dan memilih kombinasi dengan jalur terpanjang.

Setelah ini, ia menarik labirin, menghitung dinding dan menampilkan informasi labirin. Ini adalah titik awal, titik akhir, jarak di antara mereka, jumlah dinding dan skor. Ini juga memformat informasi ini menjadi gaya yang dijelaskan di atas untuk ukuran, mulai dan akhir, dinding horizontal dan dinding vertikal dan menyimpannya ke dalam file teks yang disebut Maze.txt untuk digunakan nanti.

SolveFun

Solver, SolveFun, menggunakan file teks Maze.txt sebagai input dan bekerja dengan cara yang sangat mirip dengan arsitek. Untuk setiap gerakan, ia akan memilih arah yang ingin ia tempuh berdasarkan posisi relatifnya sampai akhir dan kemudian ia akan melihat dinding yang mengelilinginya. Jika dinding tidak ada di sana, itu akan memeriksa untuk melihat apakah telah di sel yang berdekatan dengannya dan, jika tidak, itu akan ditambahkan sebagai opsi yang memungkinkan. Kemudian akan bergerak ke arah yang paling dekat dengan arah pilihannya asalkan memiliki opsi. Jika tidak memiliki opsi, ia akan mundur hingga tersedia. Ini berlanjut sampai mencapai akhir.

Saat bergerak, ia mencatat jalur yang diambilnya di jalur variabel yang digunakan di akhir untuk menampilkan jumlah langkah. Itu juga mencatat berapa kali harus mundur digunakan untuk menghitung langkah-langkah yang terbuang pada akhirnya. Ketika mencapai akhir, itu akan menampilkan labirin dengan jalur terpendek dari awal hingga akhir ditandai dengan *s.

Cara Menjalankan

Karena metode menghasilkan labirin (yang termasuk menggarisbawahi karakter tertentu), ini harus dijalankan dari baris perintah dalam formulir

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

dan

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

di mana Ukuran adalah bilangan bulat antara 15 dan 50 (inklusif) dan Seed adalah bilangan bulat antara 4 dan Ukuran (inklusif).

Anonim Tanpa Lifer
sumber